docs(architecture): move diagram up; elab on svcs; add LC Hooks links

This commit is contained in:
Ward Bell 2015-12-17 13:49:33 -08:00
parent f697e01b76
commit bf29846f3e
13 changed files with 585 additions and 480 deletions

View File

@ -2,11 +2,15 @@
import {Component} from 'angular2/core';
// #enddocregion import
import {HeroListComponent} from './hero-list.component';
import {SalesTaxComponent} from './sales-tax.component';
@Component({
selector: 'my-app',
template: '<hero-list></hero-list>',
directives: [HeroListComponent]
template: `
<hero-list></hero-list>
<sales-tax></sales-tax>
`,
directives: [HeroListComponent, SalesTaxComponent]
})
// #docregion export
export class AppComponent { }

View File

@ -1,18 +1,21 @@
import {Injectable} from 'angular2/core';
import {Injectable, Type} from 'angular2/core';
import {Logger} from './logger.service';
import {Hero} from './hero';
const HEROES = [
new Hero('Windstorm', 'Weather mastery'),
new Hero('Mr. Nice', 'Killing them with kindness'),
new Hero('Magneta', 'Manipulates metalic objects')
];
@Injectable()
export class BackendService {
constructor(private _logger: Logger) {}
getAll<T>(type: {new(...args:any[]): any }) : any[]{
getAll(type:Type) : PromiseLike<any[]>{
if (type === Hero) {
// TODO get from the database and return as a promise
return [
new Hero('Windstorm', 'Weather mastery'),
new Hero('Mr. Nice', 'Killing them with kindness'),
new Hero('Magneta', 'Manipulates metalic objects')];
// TODO get from the database
return Promise.resolve<Hero[]>(HEROES);
}
let err = new Error('Cannot get object of this type');
this._logger.error(err);

View File

@ -7,7 +7,5 @@ import {BackendService} from './backend.service';
import {Logger} from './logger.service';
// #docregion bootstrap
bootstrap(AppComponent, [
BackendService, HeroService, Logger
]);
bootstrap(AppComponent, [BackendService, HeroService, Logger]);
// #enddocregion bootstrap

View File

@ -1,9 +1,8 @@
// #docplaster
import {Component} from 'angular2/core';
import {Hero} from './hero';
import {Component, OnInit} from 'angular2/core';
import {Hero} from './hero';
import {HeroDetailComponent} from './hero-detail.component';
import {HeroService} from './hero.service'
import {HeroService} from './hero.service';
// #docregion metadata
// #docregion providers
@ -23,15 +22,18 @@ export class HeroesComponent { ... }
// #enddocregion metadata, providers
*/
// #docregion class
export class HeroListComponent {
export class HeroListComponent implements OnInit {
// #docregion ctor
constructor(service: HeroService) {
this.heroes = service.getHeroes();
}
constructor(private _service: HeroService){ }
// #enddocregion ctor
heroes:Hero[];
selectedHero:Hero;
selectedHero: Hero;
ngOnInit(){
this.heroes = this._service.getHeroes();
}
selectHero(hero: Hero) { this.selectedHero = hero; }
}
// #enddocregion class

View File

@ -3,16 +3,23 @@ import {Hero} from './hero';
import {BackendService} from './backend.service';
import {Logger} from './logger.service';
// #docregion class
@Injectable()
// #docregion class
export class HeroService {
constructor(private _backend: BackendService, private _logger:Logger){}
// #docregion ctor
constructor(
private _backend: BackendService,
private _logger: Logger) { }
// #enddocregion ctor
private _heroes:Hero[] = [];
getHeroes() {
// TODO return as a promise
let heroes = <Hero[]> this._backend.getAll(Hero);
this._logger.log(`Got ${heroes.length} heroes from the server.`);
return heroes;
this._backend.getAll(Hero).then( (heroes:Hero[]) => {
this._logger.log(`Fetched ${heroes.length} heroes.`);
this._heroes.push(...heroes); // fill cache
});
return this._heroes;
}
}
// #enddocregion class

View File

@ -1,8 +1,11 @@
// #docregion
import {Injectable} from 'angular2/core';
@Injectable()
// #docregion class
export class Logger {
log(msg: any) { console.log(msg); }
log(msg: any) { console.log(msg); }
error(msg: any) { console.error(msg); }
warn(msg: any) { console.warn(msg); }
}
warn(msg: any) { console.warn(msg); }
}
// #enddocregion class

View File

@ -0,0 +1,41 @@
// #docplaster
// #docregion
import {Component} from 'angular2/core';
import {SalesTaxService} from './sales-tax.service';
import {TaxRateService} from './tax-rate.service';
// #docregion metadata
// #docregion providers
@Component({
// #enddocregion providers
selector: 'sales-tax',
template: `
<h2>Sales Tax Calculator</h2>
Amount: <input #amountBox (change)="0">
<div *ngIf=amountBox.value>
The sales tax is
{{ getTax(amountBox.value) | currency:'USD':true:'1.2-2' }}
</div>
`,
// #docregion providers
providers: [SalesTaxService, TaxRateService]
})
// #enddocregion providers
// #enddocregion metadata
/*
// #docregion metadata, providers
export class SalesTaxComponent { ... }
// #enddocregion metadata, providers
*/
// #docregion class
export class SalesTaxComponent {
// #docregion ctor
constructor(private _salesTaxService: SalesTaxService) { }
// #enddocregion ctor
getTax(value:string | number){
return this._salesTaxService.getVAT(value);
}
}
// #enddocregion class

View File

@ -0,0 +1,19 @@
// #docregion
import {Injectable, Inject} from 'angular2/core';
import {TaxRateService} from './tax-rate.service';
// #docregion class
@Injectable()
export class SalesTaxService {
constructor(private _rateService: TaxRateService) { }
getVAT(value:string | number){
let amount:number;
if (typeof value === "string"){
amount = parseFloat(value);
} else {
amount = value;
}
return (amount || 0) * this._rateService.getRate('VAT');
}
}
// #enddocregion class

View File

@ -0,0 +1,9 @@
// #docregion
import {Injectable} from 'angular2/core';
// #docregion class
@Injectable()
export class TaxRateService {
getRate(rateName:string){return 0.10;} // always 10% everywhere
}
// #enddocregion class

View File

@ -14,14 +14,13 @@ include ../../../../_includes/_util-fns
<!-- figure img(src="/resources/images/devguide/architecture/airplane.png" alt="Us" align="left" style="width:200px; margin-left:-40px;margin-right:10px" ) -->
:marked
Of course there is more to it than this.
We're cruising at high altitude in this overview.
We're looking for landmarks. We should expect the object below to be fuzzy and obscured by occasional clouds.
Details become more clear and precise when we land in the chapters themselves.
<br clear="all">
Of course there is more to it than this. We'll learn the details when we dive into the guide chapters.
Let's get the big picture first.
figure
img(src="/resources/images/devguide/architecture/overview2.png" alt="overview" style="margin-left:-40px;" width="700")
:marked
An Angular 2 application rests on eight main building blocks:
The architecture diagram identifies the eight main building blocks of an Angular 2 application:
1. [Module](#module)
1. [Component](#component)
1. [Template](#template)
@ -30,10 +29,7 @@ include ../../../../_includes/_util-fns
1. [Service](#service)
1. [Directive](#directive)
1. [Dependency Injection](#dependency-injection)
figure
img(src="/resources/images/devguide/architecture/overview2.png" alt="overview" style="margin-left:-40px;" width="700")
:marked
Learn these eight and we're on our way.
.l-sub-section
@ -53,6 +49,18 @@ figure
A typical module is a cohesive block of code dedicated to a single purpose.
A module **exports** something of value in that code, typically one thing such as a class.
<br clear="all"><br>
.l-sub-section
:marked
### Modules are optional
We highly recommend modular design. TypeScript has great support for ES2015 module syntax and our chapters assume we're taking a modular
approach using that syntax. That's why we list *Module* among the basic building blocks.
Angular itself doesn't require a modular approach nor this particular syntax. Don't use it if you don't want it.
Each chapter has plenty to offer after you steer clear of the `import` and `export` statements.
Find setup and organization clues in the JavaScript track (select it from the combobox at the top of this page)
which demonstrates Angular 2 development with plain old JavaScript and no module system.
:marked
Perhaps the first module we meet is a module that exports a *component* class.
The component is one of the basic Angular blocks, we write a lot of them,
and we'll talk about components in the next segment. For the moment it is enough to know that a
@ -144,7 +152,7 @@ figure
+makeExample('architecture/ts/app/hero-list.component.ts', 'class', 'app/hero-list.component.ts')
:marked
Angular creates, updates, and destroys components as the user moves through the application.
The developer can take action at each moment in this lifecycle through optional [Lifecycle Hooks](#).
The developer can take action at each moment in this lifecycle through optional [Lifecycle Hooks](lifecycle-hooks.html).
.l-sub-section
:marked
We're not showing those hooks in this example
@ -233,7 +241,7 @@ code-example(language="html").
:marked
>Angular inserts an instance of the `HeroListComponent` view between those tags.
* `templateUrl` - the address of this component's template which we showed [above](#the-template).
* `templateUrl` - the address of this component's template which we showed [above](#template).
* `directives` - an array of the Components or Directives that *this* template requires.
We saw in the last line of our template that we expect Angular to insert a `HeroDetailComponent`
@ -387,13 +395,23 @@ figure
* tax calculator
* application configuration
There is nothing specifically "Angular" about services. Angular itself has no definition of a "service".
There is no service base class, no place to register a "service".
There is nothing specifically *Angular* about services. Angular itself has no definition of a *service*.
There is no *ServiceBase* class.
Yet services are fundamental to any Angular application. Our components are big consumers of services.
Yet services are fundamental to any Angular application.
Here's an example of a service class that logs to the browser console
+makeExample('architecture/ts/app/logger.service.ts', 'class', 'app/logger.service.ts (class only)')(format=".")
:marked
Here's a `HeroServce` that fetches heroes and returns them in a resolved [promise](http://www.html5rocks.com/en/tutorials/es6/promises/).
The `HeroService` depends on the `LoggerService` and another `BackendService` that handles the server communication grunt work.
+makeExample('architecture/ts/app/hero.service.ts', 'class', 'app/hero.service.ts (class only)')(format=".")
:marked
Services are everywhere.
We prefer our component classes lean. Our components don't fetch data from the server,
they don't validate user input, they don't log directly to the console. They delegate such tasks to services.
Our components are big consumers of services. They depend upon services to handle most chores.
They don't fetch data from the server, they don't validate user input, they don't log directly to the console.
They delegate such tasks to services.
A component's job is to enable the user experience and nothing more. It mediates between the view (rendered by the template)
and the application logic (which often includes some notion of a "model"). A good component presents
@ -499,7 +517,7 @@ figure
when to update the screen.
Learn how it uses **zones** to intercept asynchronous activity and run its change detection strategies.
>**Component Router** - With the Component Router service, users can navigate a multi-screen application
>**[Component Router](router.html)** - With the Component Router service, users can navigate a multi-screen application
in a familiar web browsing style using URLs.
>**Events** - The DOM raises events. So can components and services. Angular offers mechanisms for
@ -509,7 +527,7 @@ figure
>**HTTP** - Communicate with a server to get data, save data, and invoke server-side actions with this Angular HTTP client.
>**Lifecycle Hooks** - We can tap into key moments in the lifetime of a component, from its creation to its destruction,
>**[Lifecycle Hooks](lifecycle-hooks.html)** - We can tap into key moments in the lifetime of a component, from its creation to its destruction,
by implementing the "Lifecycle Hook" interfaces.
>**[Pipes](pipes.html)** - Services that transform values for display.

View File

@ -1,6 +1,6 @@
:marked
# Angular 2 Glossary
Angular 2 has a vocabulary of its own.
Most Angular 2 terms are everyday English words
with a specific meaning within the Angular system.
@ -9,7 +9,7 @@
and a few less familiar ones that have unusual or
unexpected definitions.
[A](#A) [B](#B) [C](#C) [D](#D) [E](#E) [F](#F) [G](#G) [H](#H) [I](#I)
[A](#A) [B](#B) [C](#C) [D](#D) [E](#E) [F](#F) [G](#G) [H](#H) [I](#I)
[J](#J) [K](#K) [L](#L) [M](#M) [N](#N) [O](#O) [P](#P) [Q](#Q) [R](#R)
[S](#S) [T](#T) [U](#U) [V](#V) [W](#W) [X](#X) [Y](#Y) [Z](#Z)
.l-main-section
@ -39,15 +39,15 @@
:marked
A **barrel** is an Angular library module consisting of a logical grouping of single-purpose modules
such as `Component` and `Directive`.
Familiar barrels include `angular2/core`, `angular2/common`, `angular2/platform/browser`,
`angular2/http`, and `angular2/router`.
Barrels are packaged and shipped as [**bundles**](#bundle) that
Barrels are packaged and shipped as [**bundles**](#bundle) that
we may load with script tags in our `index.html`.
The script, `angular2.dev.js`, is a bundle.
Learn more in "[Modules, barrels and bundles](https://github.com/angular/angular/blob/master/modules/angular2/docs/bundles/overview.md)".
:marked
@ -77,14 +77,14 @@
.l-sub-section
:marked
Angular JavaScript libraries are shipped in **bundles** within an **npm package**
such as [angular2](https://www.npmjs.com/package/angular2).
such as [angular2](https://www.npmjs.com/package/angular2).
The scripts `angular2.dev.js`, `http.js`, `router.js`, and `Rx.js` are
familiar examples of bundles.
A bundle contains one more more [**barrels**](#barrel)
and each barrel contains a collection of logically related [modules](#module)
Familiar barrels include `angular2/core`, `angular2/common`, `angular2/platform/browser`,
`angular2/http`, `angular2/router`.
@ -359,13 +359,13 @@
:marked
[Directives](#directive) and [Components](#component) have a lifecycle
managed by Angular as it creates, updates and destroys them.
Developers can tap into key moments in that lifecycle by implementing
one or more of the "Lifecycle Hook" interfaces.
one or more of the "Lifecycle Hook" interfaces.
Each interface has a single hook method whose name is the interface name prefixed with `ng`.
For example, the `OnInit` interface has a hook method names `ngOnInit`.
Angular calls these hook methods in the following order:
* `ngOnChanges` - called when an [input](#input)/[output](#output) binding values change
* `ngOnInit` - after the first `ngOnChanges`
@ -376,6 +376,7 @@
* `ngAfterViewChecked` - after every check of a component's view(s)
* `ngOnDestroy` - just before the directive is destroyed.
Learn more in the [Lifecycle Hooks](lifecycle-hooks.html) chapter.
.l-main-section
<a id="M"></a>
:marked
@ -388,29 +389,29 @@
and the ones we acquire from others.
A typical module is a cohesive block of code dedicated to a single purpose.
A module **exports** something of value in that code, typically one thing such as a class.
A module that needs that thing, **imports** it.
The structure of Angular modules and the import/export syntax
is based on the [ES2015](#es2015) module standard
The structure of Angular modules and the import/export syntax
is based on the [ES2015](#es2015) module standard
described [here](http://www.2ality.com/2014/09/es6-modules-final.html).
An application that adheres to this standard requires a module loader to
load modules on request and resolve inter-module dependencies.
load modules on request and resolve inter-module dependencies.
Angular does not ship with a module loader and does not have a preference
for any particular 3rd party library (although most samples use SystemJS).
Application developers may pick any module library that conforms to the standard
Modules are typically named after the file in which the exported thing is defined.
The Angular [DatePipe](https://github.com/angular/angular/blob/master/modules/angular2/src/common/pipes/date_pipe.ts)
class belongs to a feature module named `date_pipe` in the file `date_pipe.ts`.
Developers rarely access Angular feature modules directly.
Developers rarely access Angular feature modules directly.
We usually import them from public-facing **library modules**
called [**barrels**](#barrel). Barrels are groups of logically related modules.
The `angular2/core` barrel is a good example.
The `angular2/core` barrel is a good example.
Learn more in "[Modules, barrels and bundles](https://github.com/angular/angular/blob/master/modules/angular2/docs/bundles/overview.md)".
<a id="N"></a>
@ -477,13 +478,13 @@
## Routing Component
.l-sub-section
:marked
A [Component](#component) with an attached router.
A [Component](#component) with an attached router.
In most cases, the component became attached to a [router](#router) by means
of a `@RouterConfig` decorator that defined routes to views controlled by this component.
The component's template has a `RouterOutlet` element where it can display views produced by the router.
It likely has anchor tags or buttons with `RouterLink` directives that users can click to navigate.
.l-main-section

File diff suppressed because it is too large Load Diff

View File

@ -3,62 +3,62 @@ 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
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 &lt;template> element](#template)
- [discover the &lt;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.
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.
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.
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).
[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
.l-main-section
:marked
## NgIf Case Study
Lets focus on `ngIf`. It's a great example of a structural
directive: it takes a boolean and makes an entire chunk of DOM appear
Lets 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.
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
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")
@ -66,58 +66,58 @@ figure.image-display
### 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.
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 &mdash; and all of its descendent components &mdash;
Whatever the component was doing it keeps doing.
Although invisible, the component &mdash; and all of its descendent components &mdash;
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 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 &mdash; 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.
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
*Design thought*: minimize initialization effort and consider caching state in a
companion service.
:marked
Although there are pros and cons to each approach,
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 &mdash; and the users of our directives &mdash; 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
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`,
@ -125,22 +125,22 @@ figure.image-display
'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.
We also log when a component is created or destroyed
using the built-in `ngOnInit` and `ngOnDestroy` [lifecycle hooks](lifecycle-hooks.html).
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.
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`.
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.
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.
@ -149,33 +149,33 @@ figure.image-display
.l-main-section
:marked
## The *&lt;template>* tag
Structural directives, like `ngIf`, do their magic by using the
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
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 &mdash; 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=".")
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")
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.
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>
@ -186,20 +186,20 @@ figure.image-display
+makeExample('structural-directives/ts/app/structural-directives.component.html', 'asterisk')(format=".")
:marked
We're prefixing these directive names with an asterisk (\*).
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'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.
@ -208,37 +208,37 @@ figure.image-display
+makeExample('structural-directives/ts/app/structural-directives.component.html', 'ngFor-template')(format=".")
:marked
The basic pattern is the same:&nbsp; create a `<template>`, relocate the content,
and move the directive onto the `<template>`.
The basic pattern is the same:&nbsp; 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 `#hero` [local template variable](template-syntax#local-vars)
(the current item in each iteration).
<a id="unless"></a>
<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***.
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.
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
* 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
@ -251,17 +251,17 @@ figure.image-display
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 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 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
@ -270,67 +270,67 @@ figure.image-display
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.
The `@Input()` annotation marks this property as an input for the directive.
:marked
Nothing fancy here: if the condition is false,
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:
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`.
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?
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
.l-main-section
:marked
## Wrap up
Here is the pertinent source for this chapter.
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/heavy-loader.component.ts,
structural-directives/ts/app/structural-directives.component.ts,
structural-directives/ts/app/structural-directives.component.html
`,
null,
`,
null,
`unless.directive.ts,
heavy-loader.component.ts,
heavy-loader.component.ts,
structural-directives.component.ts,
structural-directives.component.html
`)
:marked
We learned that we can manipulate our HTML layout with
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.
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.