Understanding Angular 1.5 Lifecycle Hooks

Use the Angular 1.5 lifecycle hooks to maneuver around your Angular 1.5 components.

Free Course

Getting Started with Angular 2

Angular 2 is the shiny new framework that comes with a lot of new concepts. Learn all the great new features.

The release of Angular 1.5 has introduced some powerful features that have made Angular 1.x fun and easy to use. One of this features is the lifecycle hooks.

Lifecycle Hooks

Lifecycle hooks were introduced to Angular in Angular 2 alpha. They were more or less inspired by the custom elements lifecycle callbacks.

Angular 2 Lifecycle Hooks

This means that they are not actually the same so to speak. Angular 2 components come with lifecycle hooks like ngOnInit(), ngOnDestroy(), ngOnChanges(), and many more. In a nutshell, these lifecycle hooks do the following:

  • ngOnInit(): Initialize the component and the grab the necessary objects/variables we might need
  • ngOnDestroy(): Cleanup the component just before Angular destroys it. Useful for unsubscribing from observables to avoid memory leaks
  • ngOnChanges(): Do something when Angular sets a data-bound property

For detailed information visit the offfial docs.

Angular 1 Lifecycle Hooks

Angular 1.x is evolving in a way to keep the gap and migration to Angular 2 as small as possible. With the introduction of .component() method, some lifecycle callbacks have been backported to Angular 1.

Some of these hooks Include:

  • $onInit()
  • $onChanges()
  • $onDestroy()
  • $postLink()

Let's look at them one by one. Please note that Angular lifecycle-hooks were introduced in version 1.5.3.

Note: module as used in this tutorial refers to the angular module as shown below .

var module = angular.module('lifeCycleModule', []);

$onInit()

This lifecycle is executed when all controllers on the element have been constructed and after their bindings are initialized.

This hook is meant to be used for any kind of initialization work for the controller. Imagine you have a view which when visited should be populated with a list of users on load.

Normally you would write a function that would be called when the controller constructed, for example.

...
function MyController() {
  /**
   * Initializing using custom method init
   */

  this.init = function () {
    this.users = ['user1', 'user2', 'user3'];
  };

  /**
   * When the controller is constructed, this method will be called
   */

  this.init();
}
...

Another approach is to directly initialize the list of users to a variable:

...
/**
 * Initialize users directly in the controller
 */
function MyController() {
  this.users = ['user1', 'user2', 'user3'];
}
...

All the above examples work, but now imagine if you have to make HTTP request to the server to get our users, this means we'd have to mock this requests every time we construct this component or controller.

Wouldn't it be nice if we had some hook that would handle this for us? Now we have the $onInit lifecycle hook. Thanks to $onInit, we can now write the above code as below.

...
function MyController() {

  /**
   * [$onInit: Initializes users with an array of objects]
   *
   * We can also make http request in this hook if needed
   * especially when you are getting data from a server
   */
  this.$onInit = function () {
    this.users = [{
        "id": 1,
        "name": "Leanne Graham",
        "username": "Bret",
        "email": "Sincere@april.biz",
        "phone": "1-770-736-8031 x56442",
        "website": "hildegard.org",
        "company": {
          "name": "Romaguera-Crona",
          "catchPhrase": "Multi-layered client-server neural-net"
        }
      },
      ...

    ];
  };

}
....

we can access the users in our template as follows:

....
 <div class="container">
  <table class="bordered striped highlight">
    <thead>
      <tr>
        <th data-field="name">Name</th>
        <th data-field="username">username</th>
        <th data-field="email">E-mail</th>
        <th data-field="city">city</th>
        <th data-field="Phone">Phone Number</th>
        <th data-field="website">website</th>
        <th data-field="company">company</th>
      </tr>
    </thead>
    <tbody>
      <tr ng-repeat="user in users">
        <td>{{ user.name }}</td>
        <td>{{ user.username }}</td>
        <td>{{ user.email }}</td>
        <td>{{ user.address.city }}</td>
        <td>{{ user.phone }}</td>
        <td>{{ user.website }}</td>
        <td>{{ user.company.name }}</td>
      </tr>
    </tbody>
  </table>
</div>
....

Any data that we need before we begin processing any logic should be handled in this hook. This gives us better control of our component data flow.

In the above example, when the component is contructed, the $onInit lifecyle hook will run the function attached to it and populate the users variable with an array of users. Here is a codepen for this example.

Inter-component communication

Now imagine you need to access the parent component controllers, how would you go about this. With $onInit we can acccess controllers of the parent components through the require property.

This means we don't need link() functions to access other directives controllers - If you are using .directive() method helper to build components.

Lets use a scenario where we are building a tabs and tab component. The tab component being the child component needs to access the TabsController to register itself, we can simply use the require property and call it directly via the component instance. Lets see an example.

...
/**
 * [TabController]
 */
function TabController() {
  /**
   * [addTab description]
   * @param {component} tab [The component to be registered]
   */
  this.addTab = function (tab) {
    // some code to register component
  };
}

/**
 * [template: Tab component template]
 * @type {String}
 * [controller: Component controller]
 * @type { function}
 */
module.component('myTab', {
  template: '<div> <!-- some code for tabs--></div> ',
  controller: TabController
});
...

In the code snippet above we have built a Tab component, but a tab without panel is not what we want. We need to add add a TabPanel component which will be a child of myTab component.

...
/**
 * TabPanelController
 */
function TabPanelController() {
  /**
   * [$onInit registers panel to parent component]
   */
  this.$onInit = function () {
    this.parent.addTab(this);
  };

}

/**
 * [template panel template]
 * @type {String}
 *
 * [require gives access to the parent tab component]
 * @type {Object}
 */
module.component('TabPanel', {
  require: {
    'parent': '^myTabs'
  },
  template: '<div> <!-- template for tab--></div>',
  controller: TabPanelController
});
...

Simple and clean.

$onChanges()

When building components at one point you will have data coming into your component from an external source e.g parent component. With `$onChanges we can react to this changes and update the child compoment data effectively.

Before we go ahead into examples it is important for us to understand, how, why and when this hooks is called. $onChanges hook is called in two scenarios, one being during component initialization, it passes down the initial changes that can be used right away through isFirstChange method.

function Mycontroller() {
  /**
   * Get the inital changes when the component is initilaized
   * Assuning we passed name from the parent
   * <our-component name="ValueOfName"></our-component>
   */

  this.$onChanges = function(changes) {

    if(changes.user.isFirstChange()) {

      this.name = changes.user.currentValue;
    }

  };
}

The other scenario is when changes occur in one way bindings < and evaluated DOM attributes @. It propagates this changes down to the child component which we would re-assign to our local component.

Lets look at an example. We have this two dudes who like to be greeted diffrently, Thomas likes Howdy and John the old Hello.

...
/**
 * [MyController: Greets user with an name]
 */
function MyController() {
  this.greeting = 'Hello ' + this.name;
}

module.component('onChanges', {
  template: '<h1>{{$ctrl.greeting}}</h1>',
  controller: MyController
});
...

If we run this component with <on-changes name="'John'"> we will get Hello John output. What do you this will happen if we change the name attribute value to Thomas?

    <on-changes name="'Thomas'"></on-changes>

We still get Hello Thomasas output. This will make Thomas angry and we don't want that to happen? To remedy this situation, we need to detect changes on the name attribute and respond with the appropriate greeting. Lets do that.

...
function MyController() {
  this.$onChanges = function (obj) {
    var prefix;
    /**
     * [if: checks when the name has changed and greet the persons appropriately]
     */

    if (obj.name && this.name) {
      (obj.name.currentValue.toLowerCase() === 'thomas') ? prefix = 'Howdy ': prefix = 'Hello ';
      this.greeting = prefix + this.name;
    }

    /**
     * If: checks if the name is not defined set greeting to empty string
     */

    if (!this.name) {
      this.greeting = '';
    }
  };
}

module.component('onChanges', {
  template: '<h1>{{$ctrl.greeting}}</h1>',
  controller: MyController
});
...

The $onChanges() passes down a hash with property of the bound attribute in this case name and a function isFirstChange(). The property contains two other properties currentValue which has the current value of the bound attribute and previousValue the value we have changed from. The function isFirstChange can be used to get the initial value when the component is initialized. Using these properties we can track the changes and reflect them on the view.

In the example above we have used obj.name.currentValue to acccess the current name from the attribute. We then get the current name from the attribute, check if its Thomas and if it is then we use Howdy to greet him. Otherwise, we will use Hello.

Here is the CodePen:

The big picture of one-way dataflow is to replace default two way data-binding syntax =. Ideally we can delegate functions from the parent component through & bindings, to which the child component will use $event to pass data back to the parent component. The combination of $onChanges + < + & brings to live the concept of uni-directional data flow.

Lets look at an example on how we can achieve this.


module.component('parentComponent', {
  controller: function () {
    /**
     * Initial name
     */

    this.name = 'Marcus Johns';

    /**
     * This method will be called when changes happen in the child component.
     */

    this.updateName = function (event) {
      this.name = event.name;
    };
  },

  template: `

    <p>The parent object: {{ $ctrl.name }} </p>
    <input type="text" ng-model="$ctrl.name">
    <child-component name="$ctrl.name" on-update="$ctrl.updateName($event)"><child-component>`
});

module.component('childComponent', {
  bindings: {
    name: '<',
    onUpdate: '&'
  },
  controller: function () {

    var self = this;

    self.$onChanges = function (changes) {
      self.name = angular.copy(changes.name.currentValue);
    };

    /**
     * When the button is clicked we can update the parent component
     */

    self.updateData = function () {
      self.onUpdate({
        $event: {
          name: self.name
        }
      });
    };
  },
  template: `
  <p> child component: {{ $ctrl.name }}</p>
  <input type="text" ng-model="$ctrl.name">
  <button class="btn" ng-click="$ctrl.updateData()"> Update </button>`
});

In the above example we have two components the parent and child component. Through one way data binding the parent component passed down the value of name to the child component.

The changes from the parent component will trigger the $onChanges hook wich will clone the value of the name that we get from the changes and re-assign it to self.name in our child component.

The reason why we are cloning the value is to treat the value from the parent component as immutable to avoid any nasty surprises. This enables us to manipulate the data passed down to us without affecting the parent.

So what happens when data changes in our child components?

In our bindings we specified one way data binding meaning we only get data coming in from the parent but nothing is getting out.

To notify the parent component of changes from the child component, we have delegated a function onUpdate. This function enables us to acccess this.updateName function in the parent component. When the update button is clicked, self.updateData will in turn call the parent this.updateName with the new value of name. This is achieved through the delegated function self.onUpdate.

Here the CodePen.

$onDestroy()

This hook is called when its containing scope is destroyed. We can use this hook to release external resources, watches and event handlers. This is basically the same as $scope.on($destroy, fn) when used in controllers.

...
  $scope.on('$destroy', function() {
    // do some cleaning in here.
  });
...

In a scenario where you have attached non-native angular event listeners or logic, we can use this hook to clean it up when the component is desttoyed.

...
function MyController($element) {
  /**
   * eventHandler: custom event handler to be attached to our element]
   */
  var eventHandler = function () {
    /**
     * Do something cool in here
     */
  };

  /**
   * [$onInit: Attach our eventHandler when the element is clicked]
   */

  this.$onPostLink = function () {

    // When the compoment DOM has been compiled attach you eventHandler.

  };

  /**
   * [$onDestroy: Destroy the eventHandler once the component is destroyed]
   */
  this.$onDestroy = function () {

    // Destroy all custom events or bondings when the component scope is destroyed.

  };

}
...

When our component is constructed, we attach a click event to the compoment element that does something to it when clicked, it can be wherever we want lets say show alert box saying I am clicked.

In the duration when this component is active we would be happy to have this event, but when this component is inactive (meaning we have destroyed it), we don' t need this event anymore it has to go.

Since this our custom event and it's not native to Angular, it will not be detached from our app along with the component, but it will be left snooping around for an element which does not exist.

We need to tell Angular through the $onDestroy hook, when you destroy this component detach this event too.

$postLink()

This hook is called aftet the controller's element and its children have been linked. When the component elements have been compiled and ready to go, this hook will be fired.

...
function MyController($element) {
  /**
   * When the element and its child nodes have been compiled
   */

  this.$postLink = function () {
    /**
     * Do something awesome in here
     */

  };

}
...

This hook can help us to implement some functionalities that depend on the component elements to be fully compiled.

Its important to note that this is not a complete replacement for DOM manipulation, this functionality should be handled by decorator directives.

Conclusion

Lifecycle hooks provides us with an easy way of invoking operation based on the lifecycle of our components. Using this hooks lets us provide our users with relevant information or action at the appropriate time.

For more details refer to understanding components docs. Its important that you practice what we have discussed above in order to have a deeper understanding on how and where to use these hooks.

Go out there and be creative.

Nyambati Thomas

I am a full stack developer with extensive experience working with web technologies. I am competent in MEAN stack, databases such as PostgreSQL, Firebase, MongoDB, and MySQl. I am currently exploring Golang and python.

Software Developer @Andela