Angular2 Tips & Tricks

A few weeks ago, I was on the AngularAir podcast with Olivier, Mike and Justin. We went over some pretty cool stuff that you might not know in Angular2.

I wanted to take a quick second to write up a blog post on some of the content I presented…

Hosts => Listeners & Bindings

First, why are these important? In Angular2 there is not a replace attribute on directives like we have in Angular1 that allows us to be able to replace the definition with a new component. So in order to hook up to events and properties on the Host element they gave us HostListeners and HostBindings.

HostListeners

HostListener’s allow us to listen to events on the Host element. See: Documentation

Let’s say we have a simple button that we want to bind to the click event on.

import {Component,Input,HostListener} from '@angular/core';
@Component({
selector: 'my-button',
template: `
<div>{{time}}</div>
`
})
export class ButtonComponent {

time: any;

@HostListener('click', ['$event.target'])
onClick(target) {
this.time = new Date().toString();
}

}

So in my ButtonComponent we have a @HostListener that is listening for click events on the component itself. When its clicked, it will set the time to the current date and time.

You also might notice in my @HostListener I have a second argument that is ['$event.target']. That is a super cool feature that allows me setup the arguments of my function call to have specific arguments derived from the event.

Below is a demo of the above in plunkr so you can play around:

HostListener’s are also available via the host attribute in the component decorator metadata like:

import {Component,Input,HostBinding} from '@angular/core';
@Component({
selector: 'my-button',
template: `
<div>Hi!</div>
`,
host: {
'(click)': 'onClick($event.target)'
}
})
export class AppComponent {
onClick(target) { }
}

HostBinding

HostBinding’s allow us to bind attributes or css classes to our host component. See: Documentation

Let’s say we have a button, that we want to bind the width of the button to an input property of the button like:

@Component({
selector: 'my-button',
template: `
<div>
My width is {{150}}
</div>
`
})
export class ButtonComponent {

@HostBinding('style.width.px')
@Input()
boxWidth: number;

constructor() {
this.boxWidth = 150;
}

}

In this example, not only did I do a HostBinding but I attached that to the Input of boxWidth! You might also notice that I didn’t just say style.width to bind it, I also added px to define the unit of width. This is one of those silly little things that I ❤ about Angular2! You can do that type of syntax with any unit of measurement you can do in CSS too!

Below is a demo in plunkr for you to play around with this:

You can do this same style.width.px in the templates too!

@Component({
selector: 'my-button',
template: `
<div [style.width.px]="boxWidth">
My width is {{150}}
</div>
`
})
export class ButtonComponent {

@Input()
boxWidth: number;

constructor() {
this.boxWidth = 150;
}

}

Just like the HostListener’s you can declare these in the host decorator metadata of your component too.

@Component({
selector: 'my-button',
template: `
<div>
My width is {{150}}
</div>
`,
host: {
'[style.width.px']: 'boxWidth'
}
})
export class ButtonComponent {

@Input() boxWidth: number = 150;

}

Content Projection

Where Angular1 gave us transclusion, well Angular2 gives us content projection. This one dude called Todd Motto wrote a gist about this here but because I’m the one who told him it existed (😜 just kidding) but anyways I’ll tell you about it too…

Content project is the ability to have one or more slots to include describe a component’s content. Its extremely useful for building things like Sections or Dialogs where you have some fixed styles around the outside and then the inside needs to be some actual content.

Below is a common use case where I have a section and want a custom style around it, a header positioned at the top with the ability to toggle the content visibility and then I have my content I want inside the body of the section.

@Component({
selector: 'app',
template: `
<div>
<my-section>
<header>Hi</header>
<p>Some body</p>
</my-section>
</div>
`
})
export class AppComponent {

}

Now using content projection, we can describe a component like:

@Component({
selector: 'my-section',
template: `
<div>
<h1>
<a href="#" (click)="visible = !visible">
<ng-content select="header"></ng-content>
</a>
</h1>
<section *ngIf="visible">
<ng-content></ng-content>
</section>
</div>
`
})
export class MySectionComponent {

private visible: boolean = true;

}

You can see in the H1 tag I have <ng-content select="header">, this is going to match the header tag I described in my application component. Next we have just a plain ole <ng-content> that is going to be the default content in the body. In our application component, we just threw that paragraph tag below our header declaration and it will pick it up as the default since its not in a named slot. Magic!

Template Outlets

Ok, lets move onto something a bit more advanced…TemplateOutlet. So I was looking and I actually couldn’t even find this in the official documentation LOL.

A template outlet is a core directive that accepts an TemplateRef and a context object. So, wtf does that mean and why is it useful? As a component developer, you might want to create generic components that allow the developers consuming that component to pass custom view templates as inputs to that component.

Lets say you have a table and 99% of the time you want the rows to just have text but there was that one time where the East Coast Regional Sales ( for humor reference see Silicon Valley S03S02 clip) team told a customer they could have a spark line in one of their rows. Ugh, now I have to redo this AWESOME component you just built to have that chart.1% of the time. Well BAM! we can make a new input to this component for a TemplateRef and use a custom template for those glorious moments.

Let’s check out how we would do that…

@Component({
selector: 'app',
template: `
<div>
<template let-name="row" #myCustomRow>
{{row.name}} + <spark-component [data]="data" />
</template>
      <my-table
[rows]="rows"
[template]="myCustomRow">
</my-table>
</div>
`,
host: {
class: 'yellow'
}
})
export class AppComponent {
  @ViewChild('myCustomRow') myCustomRow: any;
  data: any[] = [ 1, 4, 10, 5 ];
rows: any[] = [ { name: 'Sprint' }, { name: 'ATT' } ]
}

In the aboveAppComponent I have template defined. In that template, I can pass context of the component projecting the template into the new slot by using the let keyword but I can also access current scope of the template declarator’s scope.

@Component({
selector: 'my-table',
template: `
<div class="yellow">
<table>
<tr *ngFor="row of rows">
<td>
<template
[ngTemplateOutlet]="template"
[ngOutletContext]="{ row: }">
</template>
</td>
</tr>
</table>
</div>
`,
host: {
class: 'yellow-table'
}
})
export class MyTableComponent {

@Input() rows: any[];
@Input() template: any;

}

In the my-template component snippet above, we use the template tag again but we declare the ngTemplateOutlet and ngOutletContext attributes on the element.

Now if we put the above 2 snippets together, we passed a custom template to a component and inside the component we can then project that template using scope from both the external and internal scope to render the template.

Here is a simplified demo of template outlets:

There is so many use cases for this as a component developer but also this could help with the times where you really think you gotta have that ng-include from Angular1.

How is this different from Content Projection you ask? Well with Content Projection you can only use the content once, you can’t use it in repeats and whatnot like we did with the rows.

This one is a bit intense, so read over it again LOL.


Thanks for reading, I hoped you enjoyed it!

I’m #ngPanda

Follow me on Twitter and Github for more JavaScript tips/opinions/projects/etc.

Show your support

Clapping shows how much you appreciated Austin’s story.