Sunday, May 1, 2016

Components in angular 1.5.x

In my previous article I described my view on a component based architecture. In this post I want to focus on how I applied this in a real life application with angular. Since the 1.5 version was released, components have become first class citizens just like in Angular 2, Ember 2, React,… On the other hand components aren’t so revolutionary. We have already known them for a long time as directives, but we never used them like this. Generally, we would only use directives for manipulating the DOM directly or in some cases to build reusable parts.
The components in angular 1.5 are a special kind of directive with a bit more restrictions and a simpler configuration. The main differences between directives and components are:
  • Components are restricted to elements. You can’t write a component as an attribute.
  • Components always have an isolated scope. This enforces a clearer dataflow. No longer will we have code that will change data on shared scopes.
  • No more link functions, but only a well-defined API on the controller.
  • A component is defined by an object and no longer by a function call.

Well-defined Lifecycle

Components have a well-defined lifecycle:
  • $onInit: This callback is called on each controller after all controllers for the element are constructed and their bindings initialized. In this callback you are also sure that all the controllers you require on are initialized and can be used. (since angular 1.5.0)
  • $onChanges: This callback is called each time the one-way bindings are updated.  The callback provides an object containing the changed bindings with their current- and previous value. Initially this callback is called before the $onInit with the original values of the bindings at initialization time. This is why this is the ideal place for cloning your objects passed through the bindings to ensure modifications will only affect the inner state.
    Please be aware that the changes callback on the one-way bindings for objects will only be triggered if the object reference changes. If a property inside the object changes, the changes callback won’t be called. This avoids adding a watch to monitor the changes made on the parent scope (works correctly since angular 1.5.5)
  • $postLink: This is called once all child elements are linked. This is similar to the (post) link function inside directives. Setting up DOM handlers or direct DOM manipulation can be done here. (since angular 1.5.3)
  • $onDestroy: This is the equivalent of the $destroy event emitted by the scope of a directive/controller. The ideal place to clean up external references and avoid memory leaks. (since angular 1.5.3)

Well-defined structure

Components also have a clean structure. They exist out of 3 parts:
  • A view, this can be a template or an external file.
  • A controller which describes the behaviour of the component.
  • Bindings, the in- and outputs of the component. The inputs will receive the data from a parent component and by using callbacks, will inform the parent component of any changes made to the component.

Bindings

Because components should only be allowed to modify their internal state and should never modify any data directly outside its component and scope. We should no longer make use of the commonly used two-way binding (bindings: { twoWay: “=” } ). Instead since angular 1.5 we now have a one-way binding for expressions (bindings: { oneWay: “<” } ).
The main difference with the two-way binding is the fact that the bound properties won’t be watched inside the component. So if you assign a new value to the property, it won’t affect the object on the parent. But be careful, this doesn’t apply on the fields of the property, that is why you always should clone the objects passed through the bindings if you want to change them. A good way to do this is working with named bindings, this way you can reuse the name of the bound property inside the component without affecting the object in the parent scope.
   1: module.component("component",{
   2:     template: "<div>{{$ctrl.item}}</div>"
   3:     bindings: {
   4:         inputItem: "<item"
   5:     }
   6:     controller: function(){
   7:         var $ctrl = this;
   8:         this.$onChanges = function(changeObj){
   9:             if(changeObj.inputItem){
  10:                 this.item = 
  11:                     angular.clone(changeObj.inputItem.currentValue);
  12:             }
  13:         }
  14:     }
  15: });
Another way to pass data is by using the “@” binding. This can be used if the value you are passing is a string value. The last way is using a “&” binding or a callback to retrieve data through a function call on the parent. This can be useful if you want to provide an auto complete component with search results data.
For exposing the changes inside the component we can only make use of the “&” binding. Calling a callback in the parent scope is the only way that we can and should pass data to the parent scope/component.
Let’s rephrase a little:
  • “=” binding (two-way) should no longer be used to avoid unwanted changes on the parent’s scope.
  • “<” binding (one-way) should be used to retrieve data from the parent scope passed as an expression.
  • “@” binding (string) should be used to retrieve string values from the parent scope.
  • “&” binding (callback) can be used to either retrieve data from the parent scope or be used as the only way to pass data to the parent scope.

Summary

The components way of working in angular 1.5 closes the gap for migrating your code to an angular 2 application a bit more. By building your apps in the angular 2 style you are already thinking on the same level. This will ease the migration path by shifting away from the controller way of working.
Directives will still have an existence beside the components. You will still have some cases where you will want to use attributes instead of elements or have the need of a shared scope for the UI state. But in most cases components will do the trick.