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?

New lifecycle hooks

  • $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

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

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?

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

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 ;-)

I do serverless stuff 🤷🏻‍♂️

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store