Reputation: 508
I'm using AngularJS for an application and encountered a certain problem with pushing of changes to the view.
Use case
When I do major updates to the view/data (xhr requests, restoring state and the lot), I inidicate that with a loader gif to the user. I use a flag which is bound to the img tag (using ngClass), so the loader gif is displayed when the flag is set. The flag is also used as a lock (not in a thread safe way, but good enough for the task at hand). I acquire the lock (set the flag) on starting a process and release it after the last promise ($q) of all xhr calls is resolved.
Issue
When I restore data using the HTML5 history API and load data from the server (on reload, forward or backwards), the view can take a noticeable time to reflect the changes made to the model. As I only rely on the promises of the http request, my loader gif is hidden before the view is (visibly) updated. Is there a way to detect when the changes are visible to the user instead of watching model changes? This is probably no longer an Angular issue but a general JS problem.
Time line
(1) Setting flag ->
(2) Executing xhr/model changes ->
(3) Releasing lock ->
(4) AngularJS finished applying ->
(5) Changes are visible
The issue mostly lies between steps 4 and 5 (also 3 and 4, but this is actually not really noticeable).
Example
As a user I can clearly see the table rows being generated, but the table cells are still empty. After some dozens milliseconds, the values appear. I'm not sure whether that's default Angular behavior or my browser having troubles rendering the changes to the HTML quick enough.
My thoughts
I guess the issue is intertwined with the way browsers update the viewport. I didn't really dig into that yet, but it seems to me that Angular has actually applied the changes but they're not yet rendered by the browser. Using a directive watching the main data table for the $last flag, I can detect a considerable delay between ng-repeat finishing and the view being rendered visibly. It might be that this is getting way too much into the specifics of a browser, but maybe somebody else has some thoughts on that to share :)
Upvotes: 3
Views: 833
Reputation: 18055
you can have directive with lowest priority... e.g. -1 and use that directive to show or hide the spinner/gif element
as the directive with the lowest priority, this will be executed last in the view updates. so the show-spinner can be false at part of this directive, hence spinner would hide only after all the changes are rendered...
Upvotes: 2
Reputation: 4801
This might be a bit hackish, but I had a situation where external lib would have to attach event listeners to elements generated by ngRepeat
. Obviously the elements wheren't there yet when the model watcher fired, so what I did was a custom directive on ngRepeat
element that would watch ngRepeat
's $last
property and then would $evalAsync
required callback. Here's the idea:
.directive('onRepeatFinish', function() {
return {
restrict: 'A',
link: function(scope, element, attributes) {
if (scope.$last === true) {
scope.$evalAsync(attributes.onRepeatFinish);
}
}
};
})
With that in mind, you could hide loader image not when the lock is released, but when some event, let's say renderComplete
, is sent by this directive.
template:
<div ng-repeat="record in data" on-repeat-finish="$emit('renderComplete')"></div>
handler in $rootScope:
.run(function($rootScope) {
$rootScope.$on('renderComplete', function(){
$rootScope.showLoader = false;
})
})
Upvotes: 2