parent
92574937f3
commit
92f1690b73
|
@ -0,0 +1 @@
|
||||||
|
**/*.js
|
|
@ -0,0 +1,4 @@
|
||||||
|
import {bootstrap} from 'angular2/platform/browser';
|
||||||
|
import {StructuralDirectivesComponent} from './structural-directives.component';
|
||||||
|
|
||||||
|
bootstrap(StructuralDirectivesComponent);
|
|
@ -0,0 +1,35 @@
|
||||||
|
// #docregion
|
||||||
|
import {Component, Input, Output} from 'angular2/core';
|
||||||
|
|
||||||
|
let nextId = 1;
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'heavy-loader',
|
||||||
|
template: '<span>heavy loader #{{id}} on duty!</span>'
|
||||||
|
})
|
||||||
|
export class HeavyLoaderComponent {
|
||||||
|
id = nextId++;
|
||||||
|
@Input() logs: string[];
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
// Mock todo: get 10,000 rows of data from the server
|
||||||
|
this._log(`heavy-loader ${this.id} initialized,
|
||||||
|
loading 10,000 rows of data from the server`);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
// Mock todo: clean-up
|
||||||
|
this._log(`heavy-loader ${this.id} destroyed, cleaning up`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _log(msg: string) {
|
||||||
|
this.logs.push(msg);
|
||||||
|
this._tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Triggers the next round of Angular change detection
|
||||||
|
// after one turn of the JavaScript cycle
|
||||||
|
// ensuring display of msg added in onDestroy
|
||||||
|
private _tick() { setTimeout(() => { }, 0); }
|
||||||
|
}
|
||||||
|
// #enddocregion
|
|
@ -0,0 +1,109 @@
|
||||||
|
<!-- #docplaster -->
|
||||||
|
<!-- #docregion -->
|
||||||
|
<h1>Structural Directives</h1>
|
||||||
|
|
||||||
|
<!-- #docregion structural-directives -->
|
||||||
|
<!-- #docregion asterisk -->
|
||||||
|
<div *ngIf="hero">{{hero}}</div>
|
||||||
|
<div *ngFor="#hero of heroes">{{hero}}</div>
|
||||||
|
<!-- #enddocregion asterisk -->
|
||||||
|
<!-- #docregion ngSwitch -->
|
||||||
|
<div [ngSwitch]="status">
|
||||||
|
<template [ngSwitchWhen]="'in-mission'">In Mission</template>
|
||||||
|
<template [ngSwitchWhen]="'ready'">Ready</template>
|
||||||
|
<template ngSwitchDefault>Unknown</template>
|
||||||
|
</div>
|
||||||
|
<!-- #enddocregion ngSwitch -->
|
||||||
|
<!-- #enddocregion structural-directives -->
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<button
|
||||||
|
(click)="condition = !condition"
|
||||||
|
[style.background] = "condition ? 'orangered': 'lightgreen'"
|
||||||
|
>
|
||||||
|
Set 'condition' to {{condition ? 'False': 'True'}}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- #docregion ngIf -->
|
||||||
|
<p *ngIf="condition">
|
||||||
|
condition is true and ngIf is true.
|
||||||
|
</p>
|
||||||
|
<p *ngIf="!condition">
|
||||||
|
condition is false and ngIf is false.
|
||||||
|
</p>
|
||||||
|
<!-- #enddocregion ngIf -->
|
||||||
|
<!-- #docregion myUnless-->
|
||||||
|
<p *myUnless="condition">
|
||||||
|
condition is false and myUnless is true.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p *myUnless="!condition">
|
||||||
|
condition is true and myUnless is false.
|
||||||
|
</p>
|
||||||
|
<!-- #enddocregion myUnless-->
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<!-- #docregion message-log -->
|
||||||
|
<div><!-- Visibility -->
|
||||||
|
<button (click)="isVisible = !isVisible">show | hide</button>
|
||||||
|
<heavy-loader [style.display]="isVisible ? 'inline' : 'none'" [logs]="logs"></heavy-loader>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div><!-- NgIf -->
|
||||||
|
<button (click)="condition = !condition">if | !if</button>
|
||||||
|
<heavy-loader *ngIf="condition" [logs]="logs"></heavy-loader>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>heavy-loader log:</h4>
|
||||||
|
<div *ngFor="#message of logs">{{message}}</div>
|
||||||
|
<!-- #enddocregion message-log -->
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<!-- #docregion template-tag -->
|
||||||
|
<p>
|
||||||
|
Hip!
|
||||||
|
</p>
|
||||||
|
<template>
|
||||||
|
<p>
|
||||||
|
Hip!
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
<p>
|
||||||
|
Hooray!
|
||||||
|
</p>
|
||||||
|
<!-- #enddocregion template-tag -->
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<!-- #docregion ngIf-template -->
|
||||||
|
<!-- Examples (A) and (B) are the same -->
|
||||||
|
<!-- (A) *ngIf paragraph -->
|
||||||
|
<p *ngIf="condition">
|
||||||
|
Our heroes are true!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- (B) [ngIf] with template -->
|
||||||
|
<template [ngIf]="condition">
|
||||||
|
<p>
|
||||||
|
Our heroes are true!
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
<!-- #enddocregion ngIf-template -->
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<!-- #docregion ngFor-template -->
|
||||||
|
<!-- Examples (A) and (B) are the same -->
|
||||||
|
|
||||||
|
<!-- (A) *ngFor div -->
|
||||||
|
<div *ngFor="#hero of heroes">{{ hero }}</div>
|
||||||
|
|
||||||
|
<!-- (B) ngFor with template -->
|
||||||
|
<template ngFor #hero [ngForOf]="heroes">
|
||||||
|
<div>{{ hero }}</div>
|
||||||
|
</template>
|
||||||
|
<!-- #enddocregion ngFor-template -->
|
||||||
|
<!-- #enddocregion -->
|
|
@ -0,0 +1,21 @@
|
||||||
|
// #docplaster
|
||||||
|
// #docregion
|
||||||
|
import {Component, Input, Output} from 'angular2/core';
|
||||||
|
import {UnlessDirective} from './unless.directive';
|
||||||
|
import {HeavyLoaderComponent} from './heavy-loader.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'structural-directives',
|
||||||
|
templateUrl: 'app/structural-directives.component.html',
|
||||||
|
styles: ['button { min-width: 100px; }'],
|
||||||
|
directives: [UnlessDirective, HeavyLoaderComponent]
|
||||||
|
})
|
||||||
|
export class StructuralDirectivesComponent {
|
||||||
|
heroes = ['Mr. Nice', 'Narco', 'Bombasto'];
|
||||||
|
hero = this.heroes[0];
|
||||||
|
condition = true;
|
||||||
|
isVisible = true;
|
||||||
|
logs: string[] = [];
|
||||||
|
status = 'ready';
|
||||||
|
}
|
||||||
|
//#enddocregion
|
|
@ -0,0 +1,33 @@
|
||||||
|
// #docplaster
|
||||||
|
// #docregion
|
||||||
|
// #docregion unless-declaration
|
||||||
|
import {Directive, Input} from 'angular2/core';
|
||||||
|
|
||||||
|
// #enddocregion unless-declaration
|
||||||
|
import {TemplateRef, ViewContainerRef} from 'angular2/core';
|
||||||
|
|
||||||
|
// #docregion unless-declaration
|
||||||
|
@Directive({ selector: '[myUnless]' })
|
||||||
|
export class UnlessDirective {
|
||||||
|
// #enddocregion unless-declaration
|
||||||
|
|
||||||
|
// #docregion unless-constructor
|
||||||
|
constructor(
|
||||||
|
private _templateRef: TemplateRef,
|
||||||
|
private _viewContainer: ViewContainerRef
|
||||||
|
) { }
|
||||||
|
// #enddocregion unless-constructor
|
||||||
|
|
||||||
|
// #docregion unless-set
|
||||||
|
@Input() set myUnless(condition: boolean) {
|
||||||
|
if (!condition) {
|
||||||
|
this._viewContainer.createEmbeddedView(this._templateRef);
|
||||||
|
} else {
|
||||||
|
this._viewContainer.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #enddocregion unless-set
|
||||||
|
// #docregion unless-declaration
|
||||||
|
}
|
||||||
|
// #enddocregion unless-declaration
|
||||||
|
// #enddocregion
|
|
@ -0,0 +1,25 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<!-- #docregion -->
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Angular 2 Structural Directives</title>
|
||||||
|
<!-- #docregion libraries -->
|
||||||
|
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||||
|
<script src="node_modules/angular2/bundles/angular2.dev.js"></script>
|
||||||
|
<!-- #enddocregion libraries -->
|
||||||
|
<!-- #docregion systemjs -->
|
||||||
|
<script>
|
||||||
|
System.config({
|
||||||
|
packages: {'app': {defaultExtension: 'js'}}
|
||||||
|
});
|
||||||
|
System.import('app/boot');
|
||||||
|
</script>
|
||||||
|
<!-- #enddocregion systemjs -->
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<structural-directives>Loading...</structural-directives>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -0,0 +1,42 @@
|
||||||
|
{
|
||||||
|
"name": "angular2-examples-master",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Master package.json, the superset of all dependencies for all of the _example package.json files.",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"tsc": "tsc",
|
||||||
|
"tsc:w": "tsc -w",
|
||||||
|
"lite": "lite-server",
|
||||||
|
"live": "live-server",
|
||||||
|
"start": "npm run lite",
|
||||||
|
"go": "concurrent \"npm run tsc:w\" \"npm run start\" ",
|
||||||
|
"test": "karma start karma.conf.js",
|
||||||
|
"build-and-test": "npm run tsc && npm run test"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"angular2": "2.0.0-alpha.53",
|
||||||
|
"systemjs": "0.19.6",
|
||||||
|
"es6-promise": "^3.0.2",
|
||||||
|
"es6-shim": "^0.33.3",
|
||||||
|
"reflect-metadata": "0.1.2",
|
||||||
|
"rxjs": "5.0.0-alpha.14",
|
||||||
|
"zone.js": "0.5.8",
|
||||||
|
"bootstrap": "^3.3.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"concurrently": "^1.0.0",
|
||||||
|
"lite-server": "^1.3.1",
|
||||||
|
"live-server": "^0.8.2",
|
||||||
|
"typescript": "^1.7.3",
|
||||||
|
"jasmine-core":"~2.1.0",
|
||||||
|
"karma": "^0.12.23",
|
||||||
|
"karma-chrome-launcher": "^0.1.4",
|
||||||
|
"karma-cli": "^0.0.4",
|
||||||
|
"karma-jasmine": "^0.3.6",
|
||||||
|
"rimraf": "^2.4.3"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"description": "Structural directives",
|
||||||
|
"files": ["!**/*.d.ts", "!**/*.js"],
|
||||||
|
"tags": [
|
||||||
|
"structural", "directives", "template", "ngIf",
|
||||||
|
"ngSwitch", "ngFor"
|
||||||
|
]
|
||||||
|
}
|
|
@ -22,7 +22,7 @@
|
||||||
"title": "User Input",
|
"title": "User Input",
|
||||||
"intro": "User input triggers DOM events. We listen to those events with EventBindings that funnel updated values back into our components and models."
|
"intro": "User input triggers DOM events. We listen to those events with EventBindings that funnel updated values back into our components and models."
|
||||||
},
|
},
|
||||||
|
|
||||||
"forms": {
|
"forms": {
|
||||||
"title": "Forms",
|
"title": "Forms",
|
||||||
"intro": "A form creates a cohesive, effective, and compelling data entry experience. An Angular form coordinates a set of data-bound user controls, tracks changes, validates input, and presents errors."
|
"intro": "A form creates a cohesive, effective, and compelling data entry experience. An Angular form coordinates a set of data-bound user controls, tracks changes, validates input, and presents errors."
|
||||||
|
@ -32,29 +32,34 @@
|
||||||
"title": "Dependency Injection",
|
"title": "Dependency Injection",
|
||||||
"intro": "Angular's dependency injection system creates and delivers dependent services \"just-in-time\"."
|
"intro": "Angular's dependency injection system creates and delivers dependent services \"just-in-time\"."
|
||||||
},
|
},
|
||||||
|
|
||||||
"template-syntax": {
|
"template-syntax": {
|
||||||
"title": "Template Syntax",
|
"title": "Template Syntax",
|
||||||
"intro": "How to write templates that display data and consume user events with the help of data binding."
|
"intro": "How to write templates that display data and consume user events with the help of data binding."
|
||||||
},
|
},
|
||||||
|
|
||||||
"pipes": {
|
"pipes": {
|
||||||
"title": "Pipes",
|
"title": "Pipes",
|
||||||
"intro": "Pipes transform displayed values within a template"
|
"intro": "Pipes transform displayed values within a template"
|
||||||
},
|
},
|
||||||
|
|
||||||
"attribute-directives": {
|
"attribute-directives": {
|
||||||
"title": "Attribute Directives",
|
"title": "Attribute Directives",
|
||||||
"intro": "Attribute directives attach behavior to elements."
|
"intro": "Attribute directives attach behavior to elements."
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"structural-directives": {
|
||||||
|
"title": "Structural Directives",
|
||||||
|
"intro": "Angular has a powerful template engine that lets us easily manipulate the DOM structure of our elements."
|
||||||
|
},
|
||||||
|
|
||||||
"hierarchical-dependency-injection": {
|
"hierarchical-dependency-injection": {
|
||||||
"title": "Hierarchical Injectors",
|
"title": "Hierarchical Injectors",
|
||||||
"intro": "Angular's hierarchical dependency injection system supports nested injectors in parallel with the component tree."
|
"intro": "Angular's hierarchical dependency injection system supports nested injectors in parallel with the component tree."
|
||||||
},
|
},
|
||||||
|
|
||||||
"glossary": {
|
"glossary": {
|
||||||
"title": "Glossary",
|
"title": "Glossary",
|
||||||
"intro": "Brief definitions of the most important words in the Angular 2 vocabulary"
|
"intro": "Brief definitions of the most important words in the Angular 2 vocabulary"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ include ../../../../_includes/_util-fns
|
||||||
The *Component* is really a directive with a template.
|
The *Component* is really a directive with a template.
|
||||||
It's the most common of the three directives and we write lots of them as we build our application.
|
It's the most common of the three directives and we write lots of them as we build our application.
|
||||||
|
|
||||||
The *Structural* directive changes the DOM layout by adding and removing DOM elements.
|
The [*Structural* directive](structural-directives.html) changes the DOM layout by adding and removing DOM elements.
|
||||||
[NgFor](template-syntax.html#ng-for) and [NgIf](template-syntax.html#ng-if) are two familiar examples.
|
[NgFor](template-syntax.html#ng-for) and [NgIf](template-syntax.html#ng-if) are two familiar examples.
|
||||||
|
|
||||||
The *Attribute* directive changes the appearance or behavior of an element.
|
The *Attribute* directive changes the appearance or behavior of an element.
|
||||||
|
|
|
@ -0,0 +1,335 @@
|
||||||
|
include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
|
:marked
|
||||||
|
One of the defining features of a single page application is its manipulation
|
||||||
|
of the DOM tree. Instead of serving a whole new page every time a user
|
||||||
|
navigates, whole sections of the DOM appear and disappear according
|
||||||
|
to the application state. In this chapter we'll to look at how Angular
|
||||||
|
manipulates the DOM and how we can do it ourselves in our own directives.
|
||||||
|
|
||||||
|
In this chapter we will
|
||||||
|
- [learn what structural directives are](#definition)
|
||||||
|
- [study *ngIf*](#ng-if)
|
||||||
|
- [discover the <template> element](#template)
|
||||||
|
- [understand the asterisk (\*) in **ngFor*](#asterisk)
|
||||||
|
- [write our own structural directive](#unless)
|
||||||
|
|
||||||
|
[Live example](/resources/live-examples/structural-directives/ts/plnkr.html)
|
||||||
|
|
||||||
|
<a id="definition"></a>
|
||||||
|
.l-main-section
|
||||||
|
:marked
|
||||||
|
## What are structural directives?
|
||||||
|
|
||||||
|
There are three kinds of Angular directives:
|
||||||
|
1. Components
|
||||||
|
1. Attribute directives
|
||||||
|
1. Structural directives
|
||||||
|
|
||||||
|
The *Component* is really a directive with a template.
|
||||||
|
It's the most common of the three directives and we write lots of them as we build our application.
|
||||||
|
|
||||||
|
The [*Attribute* directive](attribute-directives.html) changes the appearance or behavior of an element.
|
||||||
|
The built-in [NgStyle](template-syntax.html#ng-style) directive, for example,
|
||||||
|
can change several element styles at the same time.
|
||||||
|
We can use it to render text bold, italic, and lime green by binding to a
|
||||||
|
component property that requests such a sickening result.
|
||||||
|
|
||||||
|
A *Structural* directive changes the DOM layout by adding and removing DOM elements.
|
||||||
|
We've seen three of the built-in structural directives in other chapters: [ngIf](template-syntax.html#ngIf),
|
||||||
|
[ngSwitch](template-syntax.html#ngSwitch) and [ngFor](template-syntax.html#ngFor).
|
||||||
|
|
||||||
|
+makeExample('structural-directives/ts/app/structural-directives.component.html', 'structural-directives')(format=".")
|
||||||
|
|
||||||
|
|
||||||
|
<a id="ng-if"></a>
|
||||||
|
.l-main-section
|
||||||
|
:marked
|
||||||
|
## NgIf Case Study
|
||||||
|
|
||||||
|
Let’s focus on `ngIf`. It's a great example of a structural
|
||||||
|
directive: it takes a boolean and makes an entire chunk of DOM appear
|
||||||
|
or disappear.
|
||||||
|
|
||||||
|
+makeExample('structural-directives/ts/app/structural-directives.component.html', 'ngIf')(format=".")
|
||||||
|
|
||||||
|
:marked
|
||||||
|
The `ngIf` directive does not hide the element.
|
||||||
|
Using browser developer tools we can see that, when the condition is true, the top
|
||||||
|
paragraph is in the DOM and the bottom disused paragraph is completely
|
||||||
|
absent from the DOM! In its place are empty `<script>` tags.
|
||||||
|
|
||||||
|
figure.image-display
|
||||||
|
img(src='/resources/images/devguide/structural-directives/element-not-in-dom.png' alt="element not in dom")
|
||||||
|
|
||||||
|
:marked
|
||||||
|
### Why *remove* rather than *hide*?
|
||||||
|
We could hide the unwanted paragraph by setting its css `display` style to `none`.
|
||||||
|
The element would remain in the DOM while invisible. Instead we removed it with `ngIf`.
|
||||||
|
|
||||||
|
The difference matters. When we hide an element,
|
||||||
|
the component's behavior continues.
|
||||||
|
It remains attached to its DOM element. It continues to listen 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 descendent components —
|
||||||
|
tie up resources that might be more useful elsewhere.
|
||||||
|
The performance and memory burden can be substantial and the user may not benefit at all.
|
||||||
|
|
||||||
|
On the positive side, showing the element again is very quick.
|
||||||
|
The component's previous state is preserved and ready to display.
|
||||||
|
The component doesn't re-initialize — an operation that could be expensive.
|
||||||
|
|
||||||
|
`ngIf` is different.
|
||||||
|
Setting `ngIf` to false **does** affect the component's resource consumption.
|
||||||
|
Angular removes the element from DOM, stops change detection for the associated component,
|
||||||
|
detaches it from DOM events (the attachments that it made) and destroys the component.
|
||||||
|
The component can be garbage-collected (we hope) and free up memory.
|
||||||
|
|
||||||
|
Components often have child components which themselves have children.
|
||||||
|
All of them are destroyed when `ngIf` destroys the common ancestor.
|
||||||
|
This cleanup effort is usually a good thing.
|
||||||
|
|
||||||
|
Of course it isn't *always* a good thing.
|
||||||
|
It might be a bad thing if we need that particular component again soon.
|
||||||
|
|
||||||
|
The component's state might be expensive to re-construct.
|
||||||
|
When `ngIf` becomes `true` again, Angular recreates the component and its subtree.
|
||||||
|
Angular runs every component's initialization logic again. That could be expensive ... as when
|
||||||
|
a component re-fetches data that had been in memory just moments ago.
|
||||||
|
.l-sub-section
|
||||||
|
:marked
|
||||||
|
*Design thought*: minimize initialization effort and consider caching state in a
|
||||||
|
companion service.
|
||||||
|
:marked
|
||||||
|
Although there are pros and cons to each approach,
|
||||||
|
in general it is best to use `ngIf` to remove unwanted components rather than
|
||||||
|
hide them.
|
||||||
|
|
||||||
|
**These same considerations apply to every structural directive, whether built-in or custom.**
|
||||||
|
We should ask ourselves — and the users of our directives — to think carefully
|
||||||
|
about the consequences of adding and removing elements and of creating and destroying components.
|
||||||
|
|
||||||
|
Let's see these dynamics at work. For fun, we'll stack the deck *against*
|
||||||
|
our recommendation and consider a component called `heavy-loader` that
|
||||||
|
***pretends*** to load a ton of data when initialized.
|
||||||
|
|
||||||
|
We'll display two instances of the component. We toggle the visibility of the first one with CSS.
|
||||||
|
We toggle the second into and out of the DOM with `ngIf`.
|
||||||
|
|
||||||
|
+makeTabs(
|
||||||
|
`structural-directives/ts/app/structural-directives.component.html,
|
||||||
|
structural-directives/ts/app/heavy-loader.component.ts`,
|
||||||
|
'message-log,',
|
||||||
|
'template excerpt, heavy-loader.component.ts')
|
||||||
|
|
||||||
|
:marked
|
||||||
|
We also log when a component is created or destroyed
|
||||||
|
using the built-in `ngOnInit` and `ngOnDestroy` lifecycle hooks.
|
||||||
|
Here it is in action:
|
||||||
|
|
||||||
|
figure.image-display
|
||||||
|
img(src='/resources/images/devguide/structural-directives/heavy-loader-toggle.gif' alt="heavy loader toggle")
|
||||||
|
|
||||||
|
:marked
|
||||||
|
Both components are in the DOM at the start.
|
||||||
|
First we toggle the component's visibility repeatedly. The component never leaves the DOM.
|
||||||
|
When visible it's always the same instance and the log is quiet.
|
||||||
|
|
||||||
|
Then we toggle the second component with `ngIf`.
|
||||||
|
We create a new instance every time and the log shows that we're paying
|
||||||
|
a heavy price to create and destroy it.
|
||||||
|
|
||||||
|
If we really expected to "wink" the component like this, toggling visibility would be the better choice.
|
||||||
|
In most UIs, when we "close" a component we're unlikely see it again for a long time, if ever.
|
||||||
|
The `ngIf` would be preferred in that case.
|
||||||
|
|
||||||
|
<a id="template"></a>
|
||||||
|
.l-main-section
|
||||||
|
:marked
|
||||||
|
## The *<template>* tag
|
||||||
|
|
||||||
|
Structural directives, like `ngIf`, do their magic by using the
|
||||||
|
[HTML 5 template tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).
|
||||||
|
|
||||||
|
Outside of an Angular app, the `<template>` tag's default CSS `display` property is `none`.
|
||||||
|
It's contents are ***invisible*** within
|
||||||
|
a hidden [document fragment](https://developer.mozilla.org/en/docs/Web/API/DocumentFragment).
|
||||||
|
|
||||||
|
Inside of an app, Angular ***removes*** the`<template>` tags and their children.
|
||||||
|
The contents are gone — but not forgotten as we'll see soon.
|
||||||
|
|
||||||
|
We can confirm these effects by wrapping the middle "hip" of the phrase "Hip! Hip! Hooray!" within a `<template>` tag.
|
||||||
|
+makeExample('structural-directives/ts/app/structural-directives.component.html', 'template-tag')(format=".")
|
||||||
|
:marked
|
||||||
|
The display is a 'Hip!' short of perfect enthusiasm. The DOM effects are different when Angular is control.
|
||||||
|
figure.image-display
|
||||||
|
img(src='/resources/images/devguide/structural-directives/template-in-out-of-a2.png' alt="template outside angular")
|
||||||
|
|
||||||
|
:marked
|
||||||
|
Evidently Angular replaces the `<template>` tag and its contents with empty `<script>` tags.
|
||||||
|
That's just its default behavior.
|
||||||
|
It can do something different as we saw when applying a variety of `ngSwitch` directives to `<template>` tags:
|
||||||
|
|
||||||
|
+makeExample('structural-directives/ts/app/structural-directives.component.html', 'ngSwitch')(format=".")
|
||||||
|
:marked
|
||||||
|
When one of those `ngSwitch` conditions is true, Angular inserts the template's content into the DOM.
|
||||||
|
|
||||||
|
What does this have to do with `ngIf` and `ngFor`? We didn't use a `<template>` tag with those directives.
|
||||||
|
|
||||||
|
<a id="asterisk"></a>
|
||||||
|
.l-main-section
|
||||||
|
:marked
|
||||||
|
## The asterisk (\*) effect
|
||||||
|
Here are those directives again. See the difference?
|
||||||
|
|
||||||
|
+makeExample('structural-directives/ts/app/structural-directives.component.html', 'asterisk')(format=".")
|
||||||
|
:marked
|
||||||
|
We're prefixing these directive names with an asterisk (\*).
|
||||||
|
|
||||||
|
The asterisk is "syntactic sugar". It simplifies `ngIf` and `ngFor` for both the writer and the reader.
|
||||||
|
Under the hood, Angular replaces the asterisk version with a more verbose `<template>` form.
|
||||||
|
|
||||||
|
The next two `ngIf` examples are effectively the same and we may write in either style:
|
||||||
|
|
||||||
|
+makeExample('structural-directives/ts/app/structural-directives.component.html', 'ngIf-template')(format=".")
|
||||||
|
|
||||||
|
:marked
|
||||||
|
Most of us would rather write in style (A).
|
||||||
|
|
||||||
|
It's worth knowing that Angular expands style (A) into style (B).
|
||||||
|
It moves the paragraph and its contents inside a `<template>` tag.
|
||||||
|
It moves the directive up to the `<template>` tag where it becomes a property binding,
|
||||||
|
surrounded in square brackets. The boolean value of the host component's `condition` property
|
||||||
|
determines whether the templated content is displayed or not.
|
||||||
|
|
||||||
|
Angular transforms `*ngFor` in a similar manner:
|
||||||
|
|
||||||
|
+makeExample('structural-directives/ts/app/structural-directives.component.html', 'ngFor-template')(format=".")
|
||||||
|
:marked
|
||||||
|
The basic pattern is the same: create a `<template>`, relocate the content,
|
||||||
|
and move the directive onto the `<template>`.
|
||||||
|
|
||||||
|
There are extra nuances stemming from
|
||||||
|
Angular's [ngFor micro-syntax](template-syntax#micro-syntax) which expands
|
||||||
|
into an additional `ngForOf` property binding (the iterable) and
|
||||||
|
the `#hero` [local template variable](template-syntax#local-vars)
|
||||||
|
(the current item in each iteration).
|
||||||
|
|
||||||
|
<a id="unless"></a>
|
||||||
|
.l-main-section
|
||||||
|
:marked
|
||||||
|
## Make a structural directive
|
||||||
|
Let's write our own structural directive, an `Unless` directive, the not-so-evil twin of `ngIf`.
|
||||||
|
|
||||||
|
Unlike `ngIf` which displays the template content when `true`,
|
||||||
|
our directive displays the content when the condition is ***false***.
|
||||||
|
|
||||||
|
:marked
|
||||||
|
Creating a directive is similar to creating a component.
|
||||||
|
* import the `Directive` decorator.
|
||||||
|
|
||||||
|
* add a CSS **attribute selector** (in brackets) that identifies our directive.
|
||||||
|
|
||||||
|
* specify the name of the public `input` property for binding
|
||||||
|
(typically the name of the directive itself).
|
||||||
|
|
||||||
|
* apply the decorator to our implementation class.
|
||||||
|
|
||||||
|
Here is how we begin:
|
||||||
|
|
||||||
|
+makeExample('structural-directives/ts/app/unless.directive.ts', 'unless-declaration', 'unless.directive.ts (excerpt)')(format=".")
|
||||||
|
.l-sub-section
|
||||||
|
:marked
|
||||||
|
### Selector brackets [ ]
|
||||||
|
The CSS syntax for selecting an attribute is a name in square brackets.
|
||||||
|
We surround our directive name in square brackets. See *Directive configuration* on the [cheatsheet](cheatsheet).
|
||||||
|
|
||||||
|
### Selector name prefixes
|
||||||
|
|
||||||
|
We recommend picking a selector name with a prefix to ensure
|
||||||
|
that it cannot conflict with any standard HTML attribute, now or in the future.
|
||||||
|
|
||||||
|
We do **not** prefix our `unless` directive name with **`ng`**.
|
||||||
|
That prefix belongs to Angular and
|
||||||
|
we don't want to confuse our directives with their directives.
|
||||||
|
|
||||||
|
Our prefix is `my`.
|
||||||
|
:marked
|
||||||
|
We'll need access to the template *and* something that can render its contents.
|
||||||
|
We access the template with a `TemplateRef`. The renderer is a `ViewContainerRef`.
|
||||||
|
We inject both into our constructor as private variables.
|
||||||
|
|
||||||
|
+makeExample('structural-directives/ts/app/unless.directive.ts', 'unless-constructor')(format=".")
|
||||||
|
|
||||||
|
:marked
|
||||||
|
The consumer of our directive will bind a `true` | `false` value to our directive's `myUnless` input property.
|
||||||
|
The directive adds or removes the template based on that value.
|
||||||
|
|
||||||
|
Let's add the `myUnless` property now as a setter-only
|
||||||
|
[definedProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty).
|
||||||
|
|
||||||
|
+makeExample('structural-directives/ts/app/unless.directive.ts', 'unless-set')(format=".")
|
||||||
|
.l-sub-section
|
||||||
|
:marked
|
||||||
|
The `@Input()` annotation marks this property as an input for the directive.
|
||||||
|
:marked
|
||||||
|
Nothing fancy here: if the condition is false,
|
||||||
|
we render the template, otherwise we clear the element content.
|
||||||
|
|
||||||
|
The end result should look like below:
|
||||||
|
|
||||||
|
+makeExample('structural-directives/ts/app/unless.directive.ts', null, 'unless.directive.ts')
|
||||||
|
|
||||||
|
:marked
|
||||||
|
Now we add it to the `directives`array of the host component and try it.
|
||||||
|
First we add some test HTML to the template:
|
||||||
|
|
||||||
|
+makeExample('structural-directives/ts/app/structural-directives.component.html', 'myUnless')(format=".")
|
||||||
|
:marked
|
||||||
|
We run it and it behaves as expected, doing the opposite of `ngIf`.
|
||||||
|
When `condition` is `true`, the top paragraph is removed (replaced by `<script>` tags) and the bottom paragraph appears.
|
||||||
|
figure.image-display
|
||||||
|
img(src='/resources/images/devguide/structural-directives/myUnless-is-true.png' alt="myUnless is true" )
|
||||||
|
|
||||||
|
:marked
|
||||||
|
Our `myUnless` directive is dead simple. Surely we left something out.
|
||||||
|
Surely `ngIf` is more complex?
|
||||||
|
|
||||||
|
[Look at the source code](https://github.com/angular/angular/blob/master/modules/angular2/src/common/directives/ng_if.ts).
|
||||||
|
It's well documented and we shouldn't be shy
|
||||||
|
about consulting the source when we want to know how something works.
|
||||||
|
|
||||||
|
`ngIf` isn't much different! There are a few
|
||||||
|
additional checks to improve performance (don't clear or recreate the
|
||||||
|
view unless necessary) but otherwise it's much the same.
|
||||||
|
|
||||||
|
.l-main-section
|
||||||
|
:marked
|
||||||
|
## Wrap up
|
||||||
|
Here is the pertinent source for this chapter.
|
||||||
|
|
||||||
|
+makeTabs(`
|
||||||
|
structural-directives/ts/app/unless.directive.ts,
|
||||||
|
structural-directives/ts/app/heavy-loader.component.ts,
|
||||||
|
structural-directives/ts/app/structural-directives.component.ts,
|
||||||
|
structural-directives/ts/app/structural-directives.component.html
|
||||||
|
`,
|
||||||
|
null,
|
||||||
|
`unless.directive.ts,
|
||||||
|
heavy-loader.component.ts,
|
||||||
|
structural-directives.component.ts,
|
||||||
|
structural-directives.component.html
|
||||||
|
`)
|
||||||
|
:marked
|
||||||
|
We learned that we can manipulate our HTML layout with
|
||||||
|
structural directives like `ngFor` and `ngIf` and we
|
||||||
|
wrote our own structural directive, `myUnless`, to do something similar.
|
||||||
|
|
||||||
|
Angular offers more sophisticated techniques for managing layout
|
||||||
|
such as *structural components* that can take external content
|
||||||
|
and incorporate that content within their own templates.
|
||||||
|
Tab and tab pane controls are good examples.
|
||||||
|
|
||||||
|
We'll learn about structural components in a future chapter.
|
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 154 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
Loading…
Reference in New Issue