Avoiding UI Flicker / Suppression of DOM changes / Avoiding $digest

3,921 views
Skip to first unread message

Mark Perry

unread,
Mar 12, 2013, 10:49:38 AM3/12/13
to ang...@googlegroups.com
Hi

We have been using Angular for some weeks now to build out a wep application. After using it for some time I have just a few issues I could do with some help resolving.

We have controllers which are using for Admin screens which contain a lot of select boxes where the data is fetched using ngResource calls. Typically something akin to this:

.controller('AdminCustomController', ['$scope', '$routeParams', '$window', '$location','$q', 'countriesResource', 'titlesResource', 'townsResource', 'favcolorsResource', 'customersResource',
        function ($scope, $routeParams, $window, $location, $q, countriesResource,  titlesResource,  townsResource,  favcolorsResource, customersResource) {
            $scope.loading = true;
            $scope.customer = customersResource.get({id: 3});
            $scope.titles = titlesResource.query();
            $scope.towns = titlesResource.query();
            $scope.colors = favcolorsResource.query();
            $scope.countries = countriesResource.query();
            
            $q.all([$scope.customer, $scope.titles, $scope.towns, $scope.colors, $scope.countries]).then(function(){
                $scope.loading=false;
            });
}]);

Although I am am hiding the actual admin screen until all of the ajax calls complete that is no guarantee that the DOM changes have completed (especially on older browsers). Also as I understand things every call into $http (used by ngResource) will cause a Root level digest to occur. Now I don't need all these digest calls and DOM updates until all the ajax requests have completed as I am hiding the screen until everything is loaded.

Is there a way I can suppress $digest from running and just have it run at the end of the controller init (where I am pretty sure it will do a $digest anyway).

Also is there a way I can get a callback to fire when the last of the DOM updates has succeeded or the $digest cycle has completed making DOM changes? As it stands at the moment even if I show a loading spinner whilst ajax requests are completing and do a ng-hide="loading" on my body element there is no way to guarantee that the DOM changes have completed. So I end up showing the content mid way through some DOM changes and I still get dropdowns which show as "select..." and then jump to the actual selected value once the DOM changes have proliferated.

Any thoughts?

Thanks, Mark

Paul

unread,
Mar 12, 2013, 12:38:34 PM3/12/13
to ang...@googlegroups.com
I'm no expert on Angular, but one of the drawbacks of it in comparison to ember (I don't mean to start a flame war) is the dirty checking and extra time it takes when doing a $digest.  If you are really dealing with heavy resource calls, you may want to reconsider using ember -- I don't prefer ember, to be honest, because i love the way that angular weaves in to my html, but ember has been clocked at a much higher rate than angular (see http://jsperf.com/angular-vs-knockout-vs-ember/70).
Have you considered making your $scope call once, with multiple queries?
$scope.admin = {
            customer: customersResource.get({id: 3}),
            titles: titlesResource.query(),
            towns: titlesResource.query(),
            colors: favcolorsResource.query(),
            countries: countriesResource.query()
};
I'm not certain with 100%, but I do believe that this may prevent digest from occurring multiple times before all queries are completed, and then you can put it all into one view more easily.

Cheers and good luck.

Owen M

unread,
Mar 12, 2013, 1:17:02 PM3/12/13
to ang...@googlegroups.com
Why not move the scope assignments into your $q.all ? Right now when each of those resource promises come back they change the scope, which triggers UI updates. By assigning all at once in the all function you will only trigger a single digest loop once everything has resolved.

I haven't added anything to handle rejected promises, and you could move the top variable assignments directly into the promises call, but kept it this way to keep things clear.

Mark Perry

unread,
Mar 12, 2013, 1:17:46 PM3/12/13
to ang...@googlegroups.com
@Paul

Thanks for the reply. I haven't looked into ember to the same depth that I have Angular and I am really liking the speed at which I can get things done with Angular. Your suggestion doesn't help me much due to the fact that the $http service calls $rootscope.$apply (which in turn calls $digest) for every http call.

I think it's a fairly reasonable use case that for some screen you essentially don't want to show anything until all the data has been loaded and the DOM has been fully rendered. For my use cases I don't want my users to see the page flickers or dropdown menus changing options and flickering as the resource bindings complete async.

I just need a way to "hook" into the $digest pipeline and control when it runs. I suspect that $digest gets called asynchronously and as such a controller will have finished initialising well before the ajax calls return with data.

Perhaps some sort of "controller init promise" could be injected into controller parameters to be used with the $q service? Then I could attach a callback when everything had finished loading?

Pawel Kozlowski

unread,
Mar 12, 2013, 1:21:35 PM3/12/13
to ang...@googlegroups.com
Hi!

On Tue, Mar 12, 2013 at 6:17 PM, Mark Perry <markp...@gmail.com> wrote:

> I think it's a fairly reasonable use case that for some screen you
> essentially don't want to show anything until all the data has been loaded
> and the DOM has been fully rendered. For my use cases I don't want my users
> to see the page flickers or dropdown menus changing options and flickering
> as the resource bindings complete async.

In cases like this promises do wonders. You could use $q.all to
aggregate promises from several calls and only bind to scope variables
when data from all the calls have arrived.

>
> Perhaps some sort of "controller init promise" could be injected into
> controller parameters to be used with the $q service? Then I could attach a
> callback when everything had finished loading?

I would more look in this direction.

Cheers,
Pawel
--
Looking for bootstrap-based widget library for AngularJS?
http://angular-ui.github.com/bootstrap/

Owen M

unread,
Mar 12, 2013, 1:24:56 PM3/12/13
to ang...@googlegroups.com
Yep, that's exactly what my jsFiddle does. Although I just noticed, and now fixed two typos:

Mark Perry

unread,
Mar 12, 2013, 1:31:46 PM3/12/13
to ang...@googlegroups.com
@Owen

Thanks for the reply. I have changed my admin page following your advice and have now managed to get things a lot nicer in terms of the loading and DOM updates. Your tip regarding only assigning the data to $scope once all the resource promises have finished is definitely a stop in the right direction.

Mark Perry

unread,
Mar 12, 2013, 1:46:48 PM3/12/13
to ang...@googlegroups.com
Even with all the resource/http calls assigned into a single promise callback there is still no guarantee that the $digest cycle has completed and made all appropriate DOM changes. Admittedly the time delay is minimal but if there was a way to get another promise from the $digest call and get access to it I could then trigger a final $digest and show my page.

Pseudocode:

    $q.all([customer, titles, towns, colors, countries]).then(function (promises) {
        // Update the scope in one go
        $scope.customer = promises[0];
        $scope.titles = promises[1];
        $scope.towns = promises[2];
        $scope.colors = promises[3];
        $scope.countries = promises[4];
    }).$digestPromise.then(function(){
        $scope.loaded = true;
    });

Then I can be 100% sure that the DOM has been fully rendered before I show the page content. Currently I can only before sure that the data has been attached to $scope NOT that the $digest cycle has completed.

Thanks, Mark

Eddie Huang

unread,
Mar 12, 2013, 2:46:47 PM3/12/13
to ang...@googlegroups.com
Checkout ui-router https://github.com/angular-ui/ui-router

It appears that if controller/template is declared at the same level, most problem including flickering disappears. Versus say a template -> ng-controller based approach.

Pierrick Lespinasse

unread,
Mar 13, 2013, 12:48:57 PM3/13/13
to ang...@googlegroups.com
Maybe you should use resolve of $routeProvider thus your page will be renderer only when all data are fatched and ready to be displayed.



NB : When you use resolve, you have to specify your controller in your route cause ng-controller (DOM attribut declaration) doesn't use resolve.

Mark Perry

unread,
Mar 13, 2013, 2:20:58 PM3/13/13
to ang...@googlegroups.com
Good shout on the $routeProvider and using the "resolve" property for a promise based callback.

I'l give it some testing and see what happens.

Thanks, Mark

Mark Perry

unread,
Jun 17, 2013, 5:48:49 AM6/17/13
to ang...@googlegroups.com
After looking into the $routeProvider and the resolve argument I have managed to move some code around to make things easier on myself.

However I still feel like there should be a way to suspend $digest whilst a controller is being ran. For almost every one of the pages I am making for my application there is no point in making any DOM changes until all of the relevant "initialisation" code has run and all ajax requests have completed. For example there is no point in running a rootScope.$digest in the $http layer for every ajax call during my setup of the controllers.

I really want a way to return a $promise from a controller construction which as the last part then does a rootScope.$digest.

For example:

 .controller('MyController', ['$scope', 'aService', 'bService', 'cResource','$q',function($scope, aService, bService, cResource, $q){
   $scope.digest = false;
   var deferred = $q.defer();

   //Many service, resource and ajax calls which WOULD of caused a $digest at the rootscope and loads of unnecessary DOM updates
  deferred.then(function(){
    $scope.digest = true;
  })
  return deferred.promise; //Angular framework then does a digest once the controller has finished being constructed
   
 }]);


Thanks, Mark

Miguel Ping

unread,
Jun 17, 2013, 10:10:37 AM6/17/13
to ang...@googlegroups.com
I think better control of the digest lifecycle would be a nice feat, but it has to be thought out, because right now part of the angular magic is that everything just works; and the boundary is well defined. If you're using other lib, do a $scope.$apply and angular will kick in.

For example, some people would prefer to load the page bit by bit (think an infinite scroller); or what happens if one of your http requests fail? I would say for the average app, $digest speed is not really a concern.
I think a good option would be something similar to bindonce: https://github.com/Pasvaz/bindonce but definetely a communitty effort.

my 2c

Mark Perry

unread,
Nov 18, 2013, 6:21:44 AM11/18/13
to ang...@googlegroups.com
Hi

I was wondering if in the new versions of Angular there is now a way to suspend the $digest from running during controller initialisation.

Would opening a feature request on GitHub be a better way of trying to get this to happen?

Thanks, Mark

David Driscoll

unread,
Nov 18, 2013, 11:21:17 AM11/18/13
to ang...@googlegroups.com
Would it not just make sense to allow certain services the option to avoid calling $apply?  $http and $resource it kind of make sense that they don't always need to do work with the scope, now with the opt in solution, it then rests of the developer to appropriately call $apply after they have fetched their data.
Reply all
Reply to author
Forward
0 new messages