In the previous chapters, we built several components that rendered data on screen with the help of input and output properties. We will leverage the knowledge in this chapter to take our components to the next level with the use of directives and pipes. In a nutshell, while pipes give us the opportunity to digest and transform the information we bind in our templates, directives allow us to conduct more ambitious functionalities where we can access the host element properties and also bind our very own custom event listeners and data bindings.
In this chapter, we will:
Angular 2 defines directives as components without views. In fact, a component is a directive with an associated template view. This distinction is used because directives are a prominent part of the Angular 2 core and each (plain directives and component directives) needs the other to exist. Directives can basically affect the way HTML elements or custom elements behave and display their content.
Let's take a closer look at the framework's core directives, and then you will learn how to build your own directives later on in this chapter.
As the official documentation states, the NgIf
directive removes or recreates a portion of the DOM tree based on an expression. If the expression assigned to the NgIf
directive evaluates to false, then the element is removed from the DOM. Otherwise, a clone of the element is reinserted into the DOM. We could enhance our countdown timer by leveraging this directive, like this:
<pomodoro-timer [seconds]="timeout"></pomodoro-timer> <p *ngIf="timeout === 0">Time up!</p>
When our pomodoro timer reaches 0
, the paragraph that displays the Time up!
text will be rendered on the screen. You have probably noticed that asterisk that prepends the directive. This is because Angular embeds the HTML control marked with the NgIf
directive (and all its HTML subtrees, if any) in a <template>
tag, which will be used later on to render the content on the screen. Covering how Angular treats templates is definitely out of the scope of this book, but let's just point out that this is syntactic sugar provided by Angular to act as a shortcut to that other, more verbose syntax based on template tags.
Perhaps you are wondering what difference does it make to render some chunk of HTML on screen with *ngIf="conditional"
rather than with [hidden]="conditional"
. The former will clone and inject pieces of templated HTML snippets in the markup, removing it from the DOM when the condition evaluates to false, while the latter does not inject or remove any markup from the DOM. It simply sets the visibility of the already existing chunk of HTML annotated with that DOM attribute.
The NgFor
directive allows us to iterate through a collection (or any other iterable object) and bind each of its items to a template of our choice, where we can define convenient placeholders to interpolate the item data. Each instantiated template is scoped to the outer context, where the loop directive is placed, so we can access other bindings. Let's imagine we have a component named Staff
: it features a field named employees
, which represents an array of Employee
objects. We can enlist those employees
and job titles in this way:
<ul> <li *ngFor="let employee of employees; let i = index; let last = last"> Employee #{{i}}: - {{employee.name}}, {{employee.position}} <span *ngIf="last"><br />End of list</span> </li> </ul>
As we can see in the example provided, we turn each item fetched from the iterable object on each loop into a local reference so that we can easily bind this item in our template. Here, we also use the syntax sugar that we used in the previous section, and Angular gives us the opportunity to assign index
to a scoped variable that will be set to the current loop iteration in the template context. We can also assign last
to a scoped variable that will inform whether the item is the last one in the iteration.
This directive observes changes in the underlying iterable object and will add, remove, or sort the rendered templates as items are added, removed, or reordered in the collection.
As you probably have guessed already, this directive allows us to bind CSS styles by evaluating a custom object or expression. We can bind an object whose keys and values map CSS properties, or just define specific properties and bind data to them:
<p [ngStyle]="{ 'color': myColor, 'font-weight': myFontWeight }">I am red and bold</p>
If our component defines the
myColor
and myFontWeight
properties with the red
and bold
values, respectively, the color and weight of the text will change accordingly. The directive will always reflect the changes made within the component, and we can also pass an object instead of binding data on a per property basis:
<p [ngStyle]="myCssConfig">I am red and bold</p>
Similar to NgStyle
, NgClass
allows us to define and toggle class names programmatically in a DOM element using a convenient declarative syntax. This syntax has its own intricacies, however. Let's see each one of the three case scenarios available for this example:
<p [ng-class]="{{myClassNames}}">Hello Angular!</p>
For instance, we can use a string type so that if myClassNames
contains a string with one or several classes delimited by a space, all of them will be bound to the paragraph.
We can use an array as well so that each element will be added.
Last but not least, we can use an object in which each key corresponds to a CSS class name referred to by a Boolean value. Each key name marked as true will become an active class. Otherwise, it will be removed. This is usually the preferred way of handling class names.
The NgSwitch
directive is used to switch templates within a specific set depending on the condition required for displaying each one. The implementation follows several steps, therefore three different directives are explained in this section.
NgSwitch
will evaluate a given expression and then toggle and display those child elements marked with an ngSwitchWhen
attribute directive, whose value matches the value thrown by the expression defined in the parent ngSwitch
element. A special mention is required about the children element marked with the ngSwitchDefault
directive attribute. This attribute qualifies the template that will be displayed when no other value defined by its ngSwitchWhen
siblings matches the parent conditional expression.
We'll see all of this in an example:
<div [ngSwitch]="weatherForecastDay"> <template ngSwitchWhen="today">{{weatherToday}}</template> <template ngSwitchWhen="tomorrow"> {{weatherTomorrow}}</template> <template ngSwitchDefault> Pick a day to see the weather forecast </template> </div>
The parent [ngSwitch]
parameter evaluates the weatherForecastDay
context variable, and each nested ngSwitchWhen
directive will be tested against it. We can use expressions instead, but we want to wrap ngSwitchWhen
in brackets so that Angular can properly evaluate its content as context variables instead of taking it as a text string.
At the time of closing the writing of this book, the Angular core team is discussing the convenience of renaming ngSwitchWhen
to ngSwitchCase
in order to keep consistent with JavaScript and TypeScript switch/case keywords, and also with other i18n directives such as NgPlural
and NgPluralCase
. It is quite likely that this breaking change will make it to Angular 2 final so please refer to the online documentation to double check the final syntax for NgSwitch
template cases.
Coverage for the NgPlural
and NgPluralCase
sits outside of the scope of this book, but basically provide a convenient way to render or remove templates DOM blocks that match a switch expression, either strictly numeric or just a string, in a similar fashion to how the NgSwitch
and NgSwitchWhen
directives do.