The code side of life

AngularJS - Detect / Notify when a page was rendered.

Introduction

user

Shlomi Assaf


AngularJS - Detect / Notify when a page was rendered.

Posted by Shlomi Assaf on .
Featured

AngularJS - Detect / Notify when a page was rendered.

Posted by Shlomi Assaf on .

Recently I had task that involved DOM manipulation after a page load.
The manipulation involved calculating a dynamic height for an element, using input from other elements then applying the calculated height on an element.

Quite a simple task... almost.

After writing the logic I found that it simply doesn't work, even tough the math is right I get totally unreasonable results.

A quick debug and the mystery unfolds, the elements are living in a completely different matrix :)

The calculation has to occur once all DOM elements are rendered and in final position with final Height/Width set.

So, I need to find a way to get a notification once the rendering process has completed, piece of cake... not at all.

Angular's rendering system doesn't notify when a rendering task is complete, this is true for every directive. Even if we do get a notification, page transitions usually involve animations that disrupt the positioning completely, hence different "matrix".

So, we need to know when an animation has ended, once done we are sure that our page is rendered and in position.

ngAnimate provides such events, before 1.4 it was bubbled through the $scope using events such as $animate:after, $animate:close etc... but this was deprecated.

In 1.4+ we need to use the $animate service to register a listener to an event on an specific element, yes we need a reference to the element and there is no more bubbling.

So, we need a reference to the element, this means that we need a directive.

My concern was page render, and a new directive applied in the less intrusive way possible.
Since ng-view is a top-level directive there are many ways to attack this.

I could fire some events using $scope, but I hate that and I hardly use scope in my controllers, they just pollute them.

I could require ngView from child directives but this is super intrusive and requires code change I don't want to do.

I could write a directive on the same element as ng-view and using $animate I can listen to animation events and act when needed. This is what I chose.

I named my new directive ng-view so I don't need to change my current HTML and registered a listener within the link function. The listeners job is to notify some service that a page has finished rendering.

angular.module('myApp', function($animate) {  
  return {
            restrict: 'EA',
            priority: 399, // just below ngView original priority (400)
            link : function(scope, element) {
                $animate.on('enter', element[0], (element, phase) => {
                    if (phase === "close") {
                        // NOTIFY A SERVICE HERE.
                    }
                });
            }
        }
});

So whats going on here?
My new ng-view directive is actually a sibling of angular's native ng-view directive.
The new directive get a priority of 399 because it must run after the original ngView runs.

I register a listener to the enter animation event so I will get a notification once it happens. Since enter happens twice (start, close) I only act when it close's.

This is a very simple example, you can alter it so it can be used on every directive that has ngAnimae support (ng-show, ng-hide, etc...).

You can also create a custom directive and add it directly in the HTML, providing metadata to you logic...

user

Shlomi Assaf