Components and Multiple Transclusion in Angular 1.5

With the release of Angular 2, one could thing we are all done with
version 1, different but the Angular team keep surprising us with new features and improvements for all of us who still keep our hopes in this
instance of the framework. With the final release of 1.5v, a lot of
new features, bug fixes and improvements are available for us to
keep us busy and creating really cool stuff. This time we are going
to talk about two of them, let's start with Components.

According to the Angular’s site, a component is a special kind of directive that uses a simpler configuration which is suitable for a component-based application structure.

This makes it easier to write an app in a way that’s similar to using Web Components or using Angular 2’s style of application architecture.

Ok, let’s find out what this means with some code.

angular. 
.module('myApp')
.component('testComponent', {
template: "<div>Hi there, i am a component</div>",
controllerAs: "vm", //if not angular will set it as $ctrl
controller: function() {
var vm = this; vm.foo = bar;
}
});

What are the differences?

The main different is that Components are accountable only for their on View and Data; this means that, it is not possible to modify any data or DOM outside their own scope. Normally, in the world of Angular we can access anda modify data everywhere using scope inheritance and watches, as you might guess, this is similar to the isolate scope in directives. This also means we cannot access to the two-way-bindings { foo : “=”}, to synchronise a parent scope property with the inner of scope like in directives.

New lifecycle hooks

Components have a well defined lifecycle, they introduce 4 different methods that can be defined as:

  • $onInit() : Difference the instantiation from the initialisation, this allow the controller to do not interact with any other service or network when the instance is created.
  • $onDetroy() : It’s called when its scope is destroyed; For example, when you access to a new route.
  • $onChanges(changesObjs) : Whenever on-way binding are updated. Basically when something (inbound bindings) changes in the controller. The changesObjs is a hash whose keys are the names of the properties that have changed.
  • $postLink() : This works direct against the DOM low level, meaning, when the controller’s element and its children have been linked.

Writing a component is very similar to the controller or directive syntax, only difference is that we do not need to attach a controller in the template, therefore, this:

<div ng-controller="TestController">
<testDirective></testDirective>
</div>

This is all include already included in:

<testComponent></testComponent>

Pretty neat, eh?

Components vs Directives

After seeing this, we might find some time saving on this, but let’s wait a second… components lack of some of the functionality that directive have, we cannot decide if we want to have an isolated scope or inherit from a controller scope, also we cannot decide if we use an element or a attribute, components already create a isolate scope into a new component.

Isolate scopes are really great to re-use directives, for example, we can just to pass and object of a collection into a ng-repeat and attach to it to the directive using the “=” definition, and the directive runs an instance for this particular object, however, it does not work exactly like that on components, we would use the bindings and the < sign property instead as follow:

angular. 
.module('myApp')
.component('testComponent', {
// the rest of the information is omitted for
//convenience
bindings : {
foo : "<", // objects
bar : "@", //strings
baz : "&" //events
}
});
};

And my component would looks like:

<li ng-repeat="bar in bars"> 
<testComponent foo="myObj.name"bar="myObj"></testComponent>
</li>

Angular takes the value of the expression and assign it to the component controller as a property when is it’s instantiated. Components also support the very basic transclusion as well.

angular. 
.module('myApp')
.component('testComponent', {
// the rest of the information is omitted for
//convenience
transclude : true
});
};

And finally we place the directive in our template wherever we want to place it, if you are interested about all the differences between a directive and a component, you can always refer to the Angular documentation.

Advantages of Components:

* simpler configuration than plain directives * promote sane
defaults and best practices
* optimised for component-based
architecture
* writing component directives will make it easier to
upgrade to Angular 2

When not to use Components:

* for directives that rely on DOM manipulation, adding event
listeners etc, because the compile and link functions are
unavailable
* when you need advanced directive definition options like priority, terminal,
multi-element
* when you want a directive that is triggered by an attribute or CSS class,
rather than an element

Unit Testing

It is time for some testing (you do test your code, don’t you?). In order to test controllers into components we have to use $componentController in order to access to the hooks, as follows:

describe("Testing my TestComponent", function() {
beforeEach(module("myApp"));
        var testComponentController;
beforeEach(inject(function($componentController){
testComponentController =
$componentController("testComponent", {
// all the dependencies we want to mock
$scope : {}
});
}));
it("can be tested", function() {
expect(testComponentController).toBeDefined();
expect(testComponentController.$onInit).toBeDefined();
expect(testComponentController.$onChanges).toBeDefined();
expect(testComponentController.$onDestroy).toBeDefined();
});
});

Multiple Transclusion… wait… what?

As I mentioned before, we are talking about 2 features released on the 1.5 version of AngularJS framework, so let’s talk a little about this one.

Probably, if you are like me, you had to dedicated a little more time to understand the concept of transclusion but after you got it, you cannot life with it and you just feel like using it all the time cause the curse of knowledge or probably cause its really fancy name. Let’s review the concept:

According to Wikipedia:

In computer science, transclusion is the inclusion of part or all of an electronic document into one or more other documents by reference.

That’s mean in the world of Angular that we can take some piece of HTML code placed into the directive tag and insert it somewhere else inside the template of this directive.

Said this, if you have played with Angular good enough time, that means you already know this is a all-or-nothing approach, we take all the content we can to transclude and place it exactly where we wanted. Now we can set multiple places where we want out content to be transcluded, let’s see the differences:

Single Transclution

<task-item task="{ name: 'this is a task obj'}"> 
No selective transclusion, this will be transclude with options and placed
after the text
<hr />
<div class="options">
<span ng-click="save()">Save</span> -
<span ng-click="delete()">Delete</span>
</div>
</task-item>

The definition of the directive would looks as follows:

angular 
.module("myApp")
.directive('taskItem', function () {
return {
restrict: 'E',
transclude: true,
scope : {
task : "="
},
template: "<div class='task'>{{ task.name }} <hr />" +
"<ng-transclude></ng-transclude></div>"
}
});

And this will render:

<task-item task="{ name: 'this is a task obj'}" class="ng-isolate-scope"> 
<div class="task ng-binding">
this is a task obj
<hr> <span> No selective transclusion,
this will be transclude with options and placed after the text </span>
<hr>
<div class="options">
<span ng-click="save()">Save</span> -
<span ng-click="delete()">Delete</span>
</div>
</div>
</task-item>

Messing around multiple transclusion

As we saw it before, we need to take all the DOM element as it is, if for sample, we would want to place the options div and place above the previous text, this wouldn’t be possible, however, this is where multiple transclusion comes to the rescue; we just need to make a little tweak to our directive in order to achieve this, let’s make some code:

First the template, let’s add a element new to convenience where we are going to take the element, and change the hr tag and add numeration for convenience:

<task-item-multiple task="{ name: 'this is a task obj for sample2'}"> 
<span>
1. Using selective transclusion, this will be transcluded and magically
placed below the options
</span>
       <div class="options"> 
2. <span ng-click="save()">Save</span> -
<span ng-click="delete()">Delete</span>
</div>
        <hr /> 
</task-item-multiple>

Now let’s see how we can modify this using multiple transclusion:

angular 
.module("myApp")
.directive('taskItemMultiple', function() {
return {
restrict: 'E',
transclude: {
//we can set a new identifier
'new': 'span' //could be a div, why not?
},
scope: {
task: "="
},
//Let's change the order of elements
template: "<div ng-transclude></div>" +
"<div>{{task.name}}</div><hr>" +
"<div ng-transclude='new'></div>"
}
});

It will render:

<task-item-multiple task="{ name: 'this is a task obj for sample 2'}" 
class="ng-isolate-scope">
<div ng-transclude="">
<div class="options ng-scope">
2. <span ng-click="save()">Save</span> -
<span ng-click="delete()">Delete</span>
</div>
<hr class="ng-scope">
</div>
<div class="ng-binding">
this is a task obj for sample 2
</div>
<hr>
<div ng-transclude="new">
<span class="ng-scope">
1. Using selective transclusion, this will be transclude and
magically placed below the options
</span>
</div>
</task-item-multiple>

So, what happened here?, we basically took the span element into the directive tags, assign the “new” keyword and place into into a div tag using the directive ng-transclude which now receives and string ng-transclude=”new”.

Isn’t that cool? Check the code here.

We can even make transclusion slots optional by prefixing the element tag name with a ? like this:

transclude: { 'new' : '?span' }

What happened here? We just put “new” as text into our span element. This text will be replace with the transcluded DOM, if it is applied, if not, it’s ok anyway.

What do you think? now you have two really powerful new features to make your code even more delightful ;-)

Like what you read? Give Andrés Mijares a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.