Angular Directives
Built-in Directives
Directives are classes that add additional behavior to elements in your Angular applications.
Attribute Directives
Attribute directives are a way of changing the appearance or behavior of a component or a native DOM element.
NgStyle
Angular provides a built-in directive, ngStyle, to modify a component or element's style attribute.
@Component({
selector: 'app-style-example',
template: `
<p style="padding: 1rem"
[ngStyle]="{
'color': 'red',
'font-weight': 'bold',
'borderBottom': borderStyle
}">
<ng-content></ng-content>
</p>
`
})
export class StyleExampleComponent {
borderStyle = '1px solid black';
}
Binding a directive works the exact same way as component attribute bindings. Here, we're binding an expression, an object literal, to the ngStyle directive so the directive name must be enclosed in square brackets. ngStyle accepts an object whose properties and values define that element's style. In this case, we can see that both kebab case and lower camel case can be used when specifying a style property. Also notice that both the html style attribute and Angular ngStyle directive are combined when styling the element.
We can remove the style properties out of the template into the component as a property object, which then gets assigned to ngStyle using property binding. This allows dynamic changes to the values as well as provides the flexibility to add and remove style properties.
@Component({
selector: 'app-style-example',
template: `
<p style="padding: 1rem"
[ngStyle]="alertStyles">
<ng-content></ng-content>
</p>
`
})
export class StyleExampleComponent {
borderStyle = '1px solid black';
alertStyles = {
'color': 'red',
'font-weight': 'bold',
'borderBottom': this.borderStyle
};
}
NgClass
The ngClass directive changes the class attribute that is bound to the component or element it's attached to. There are a few different ways of using the directive.
- Binding a string
- Binding an array
- Binding an object
-- Binding as string
We can bind a string directly to the attribute. This works just like adding an html class attribute.
@Component({
selector: 'app-class-as-string',
template: `
<p ngClass="centered-text" class="orange">
<ng-content></ng-content>
</p>
`,
styles: [`
.centered-text {
text-align: center;
}
.orange {
color: orange;
}
`]
})
export class ClassAsStringComponent {
}
In this case, we're binding a string directly so we avoid wrapping the directive in square brackets. Also notice that the ngClass works with the class attribute to combine the final classes.
-- Binding an array
@Component({
selector: 'app-class-as-array',
template: `
<p [ngClass]="['warning', 'big']">
<ng-content></ng-content>
</p>
`,
styles: [`
.warning {
color: red;
font-weight: bold;
}
.big {
font-size: 1.2rem;
}
`]
})
export class ClassAsArrayComponent {
}
Here, since we are binding to the ngClass directive by using an expression, we need to wrap the directive name in square brackets. Passing in an array is useful when you want to have a function put together the list of applicable class names.
-- Binding an object
An object can be bound to the directive. Angular applies each property name of that object to the component if that property's value is true.
@Component({
selector: 'app-class-as-object',
template: `
<p [ngClass]="{ card: true, dark: false, flat: flat }">
<ng-content></ng-content>
<br>
<button type="button" (click)="flat=!flat">Toggle Flat</button>
</p>
`,
styles: [`
.card {
border: 1px solid #eee;
padding: 1rem;
margin: 0.4rem;
font-family: sans-serif;
box-shadow: 2px 2px 2px #888888;
}
.dark {
background-color: #444;
border-color: #000;
color: #fff;
}
.flat {
box-shadow: none;
}
`]
})
export class ClassAsObjectComponent {
flat: boolean = true;
}
Structural Directives
Angular has a few built-in structural directives such as ngIf, ngFor, and ngSwitch.
Structural directives are responsible for HTML layout. They shape or reshape the DOM's structure, typically by adding, removing, or manipulating elements.
As with other directives, you apply a structural directive to a host element. The directive then does whatever it's supposed to do with that host element and its descendents.
Structural directives are easy to recognize. An asterisk (*) precedes the directive attribute name as in this example.
<div *ngIf="hero" >{{hero.name}}</div>
<ul>
<li *ngFor="let hero of heroes">{{hero.name}}</li>
</ul>
<div [ngSwitch]="hero?.emotion">
<happy-hero *ngSwitchCase="'happy'" [hero]="hero"></happy-hero>
<sad-hero *ngSwitchCase="'sad'" [hero]="hero"></sad-hero>
<confused-hero *ngSwitchCase="'confused'" [hero]="hero"></confused-hero>
<unknown-hero *ngSwitchDefault [hero]="hero"></unknown-hero>
</div>
No brackets. No parentheses. Just *ngIf set to a string. The ngIf directive doesn't hide elements with CSS. It adds and removes them physically from the DOM.
Why remove rather than hide?
The difference between hiding and removing doesn't matter for a simple paragraph. It does matter when the host element is attached to a resource intensive component. Such a component's behavior continues even when hidden. The component stays attached to its DOM element. It keeps listening to events. Angular keeps checking for changes that could affect data bindings. Whatever the component was doing, it keeps doing.
Although invisible, the component—and all of its descendant components—tie up resources. The performance and memory burden can be substantial, responsiveness can degrade, and the user sees nothing.
On the positive side, showing the element again is quick. The component's previous state is preserved and ready to display. The component doesn't re-initialize—an operation that could be expensive. So hiding and showing is sometimes the right thing to do.
These same considerations apply to every structural directive, whether built-in or custom. Before applying a structural directive, you might want to pause for a moment to consider the consequences of adding and removing elements and of creating and destroying components.
Behind Story : The asterisk () prefix*
Surely you noticed the asterisk (*) prefix to the directive name and wondered why it is necessary and what it does.
Here is *ngIf displaying the hero's name if hero exists.
<div *ngIf="hero" >{{hero.name}}</div>
The asterisk is "syntactic sugar" for something a bit more complicated. Internally, Angular desugars it in two stages. First, it translates the *ngIf="..." into a template attribute, template="ngIf ...", like this.
<div template="ngIf hero">{{hero.name}}</div>
Then it translates the template attribute into a template element, wrapped around the host element, like this.
<template [ngIf]="hero">
<div>{{hero.name}}</div>
</template>
- The *ngIf directive moved to the template element where it became a property binding,[ngIf].
- The rest of the div, including its class attribute, moved inside the template element.
None of these forms are actually rendered. Only the finished product ends up in the DOM.
Angular consumed the template content during its actual rendering and replaced the template with a diagnostic comment.
The NgFor and NgSwitch... directives follow the same pattern.