docs(pipes) clarify impure pipes; explain missing filter/orderBy pipes
adds flying-heroes example and its test adds random-pipe example
This commit is contained in:
parent
16b3c940dd
commit
5b27178083
|
@ -5,19 +5,19 @@ describe('Pipes', function () {
|
|||
});
|
||||
|
||||
it('should open correctly', function () {
|
||||
expect(element.all(by.css('h4')).get(0).getText()).toEqual('Hero Birthday v.1');
|
||||
expect(element(by.css('body > hero-birthday p')).getText()).toEqual("The hero's birthday is Apr 15, 1988");
|
||||
expect(element.all(by.tagName('h1')).get(0).getText()).toEqual('Pipes');
|
||||
expect(element(by.css('hero-birthday p')).getText()).toEqual("The hero's birthday is Apr 15, 1988");
|
||||
});
|
||||
|
||||
it('should show delayed message', function () {
|
||||
expect(element.all(by.css('hero-message')).get(0).getText()).toEqual('Message: You are my Hero!');
|
||||
it('should show an async hero message', function () {
|
||||
expect(element.all(by.tagName('hero-message')).get(0).getText()).toContain('hero');
|
||||
});
|
||||
|
||||
it('should show 4 heroes', function () {
|
||||
expect(element.all(by.css('hero-list div')).count()).toEqual(4);
|
||||
});
|
||||
|
||||
it('should show 4 heroes in json', function () {
|
||||
it('should show a familiar hero in json', function () {
|
||||
expect(element(by.cssContainingText('hero-list p', 'Heroes as JSON')).getText()).toContain('Bombasto');
|
||||
});
|
||||
|
||||
|
@ -27,9 +27,9 @@ describe('Pipes', function () {
|
|||
});
|
||||
|
||||
it('should be able to toggle birthday formats', function () {
|
||||
var birthDayEle = element(by.css('my-app > hero-birthday > p'));
|
||||
var birthDayEle = element(by.css('hero-birthday2 > p'));
|
||||
expect(birthDayEle.getText()).toEqual("The hero's birthday is 4/15/1988");
|
||||
var buttonEle = element(by.cssContainingText('my-app > hero-birthday > button', "Toggle Format"));
|
||||
var buttonEle = element(by.cssContainingText('hero-birthday2 > button', "Toggle Format"));
|
||||
expect(buttonEle.isDisplayed()).toBe(true);
|
||||
buttonEle.click().then(function() {
|
||||
expect(birthDayEle.getText()).toEqual("The hero's birthday is Friday, April 15, 1988");
|
||||
|
@ -66,5 +66,52 @@ describe('Pipes', function () {
|
|||
});
|
||||
|
||||
|
||||
it('should support flying heroes (pure) ', function () {
|
||||
var nameEle = element(by.css('flying-heroes input[type="text"]'));
|
||||
var canFlyCheckEle = element(by.css('flying-heroes #can-fly'));
|
||||
var mutateCheckEle = element(by.css('flying-heroes #mutate'));
|
||||
var resetEle = element(by.css('flying-heroes button'));
|
||||
var flyingHeroesEle = element.all(by.css('flying-heroes #flyers div'));
|
||||
|
||||
expect(canFlyCheckEle.getAttribute('checked')).toEqual('true', 'should default to "can fly"');
|
||||
expect(mutateCheckEle.getAttribute('checked')).toEqual('true', 'should default to mutating array');
|
||||
expect(flyingHeroesEle.count()).toEqual(2, 'only two of the original heroes can fly');
|
||||
|
||||
return sendKeys(nameEle, "test1\n")
|
||||
.then(function(){
|
||||
expect(flyingHeroesEle.count()).toEqual(2, 'no change while mutating array');
|
||||
return mutateCheckEle.click();
|
||||
})
|
||||
.then(function() {
|
||||
return sendKeys(nameEle, "test2\n");
|
||||
})
|
||||
.then(function() {
|
||||
expect(flyingHeroesEle.count()).toEqual(4, 'not mutating; should see both adds');
|
||||
expect(flyingHeroesEle.get(2).getText()).toContain('test1');
|
||||
expect(flyingHeroesEle.get(3).getText()).toContain('test2');
|
||||
return resetEle.click();
|
||||
})
|
||||
.then(function() {
|
||||
expect(flyingHeroesEle.count()).toEqual(2, 'reset should restore orginal flying heroes');
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it('should support flying heroes (impure) ', function () {
|
||||
var nameEle = element(by.css('flying-heroes-impure input[type="text"]'));
|
||||
var canFlyCheckEle = element(by.css('flying-heroes-impure #can-fly'));
|
||||
var mutateCheckEle = element(by.css('flying-heroes-impure #mutate'));
|
||||
var resetEle = element(by.css('flying-heroes-impure button'));
|
||||
var flyingHeroesEle = element.all(by.css('flying-heroes-impure #flyers div'));
|
||||
|
||||
expect(canFlyCheckEle.getAttribute('checked')).toEqual('true', 'should default to "can fly"');
|
||||
expect(mutateCheckEle.getAttribute('checked')).toEqual('true', 'should default to mutating array');
|
||||
expect(flyingHeroesEle.count()).toEqual(2, 'only two of the original heroes can fly');
|
||||
|
||||
return sendKeys(nameEle, "test1\n")
|
||||
.then(function(){
|
||||
expect(flyingHeroesEle.count()).toEqual(3, 'new flying hero should show in mutating array');
|
||||
})
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -1,11 +1,26 @@
|
|||
<hr>
|
||||
<!-- async examples at the top so can see them in action -->
|
||||
<hero-message></hero-message>
|
||||
<a id="toc"></a>
|
||||
<h1>Pipes</h1>
|
||||
<a href="#happy-birthday1">Happy Birthday v.1</a><br>
|
||||
<a href="#birthday-date-pipe">Birthday DatePipe</a><br>
|
||||
<a href="#happy-birthday2">Happy Birthday v.2</a><br>
|
||||
<a href="#birthday-pipe-chaining">Birthday Pipe Chaining</a><br>
|
||||
<a href="#power-booster">Power Booster custom pipe</a><br>
|
||||
<a href="#power-boost-calc">Power Boost Calculator custom pipe with params</a><br>
|
||||
<a href="#flying-heroes">Flying Heroes filter pipe (pure)</a><br>
|
||||
<a href="#flying-heroes-impure">Flying Heroes filter pipe (impure)</a><br>
|
||||
<a href="#hero-message">Async Hero Message and AsyncPipe</a><br>
|
||||
<a href="#hero-list">Hero List with caching FetchJsonPipe</a><br>
|
||||
<a href="#random-pipe">Random Pipe (pure pipe / impure function)</a><br>
|
||||
|
||||
|
||||
<hr>
|
||||
<hero-list></hero-list>
|
||||
<a id="happy-birthday1"></a>
|
||||
<h2>Hero Birthday v.1</h2>
|
||||
<hero-birthday></hero-birthday>
|
||||
|
||||
<hr>
|
||||
<a id="birthday-date-pipe"></a>
|
||||
<h2>Birthday DatePipe</h2>
|
||||
<!-- #docregion hero-birthday-template -->
|
||||
<p>The hero's birthday is {{ birthday | date }}</p>
|
||||
<!-- #enddocregion hero-birthday-template-->
|
||||
|
@ -15,33 +30,59 @@
|
|||
<!-- #enddocregion format-birthday-->
|
||||
|
||||
<hr>
|
||||
<h4>Hero Birthday v.2</h4>
|
||||
<hero-birthday>loading...</hero-birthday>
|
||||
<a id="happy-birthday2"></a>
|
||||
<h2>Hero Birthday v.2</h2>
|
||||
<hero-birthday2></hero-birthday2>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
<!-- #docregion chained-birthday -->
|
||||
<a id="birthday-pipe-chaining"></a>
|
||||
<h2>Birthday Pipe Chaining</h2>
|
||||
<p>
|
||||
<!-- #docregion chained-birthday -->
|
||||
The chained hero's birthday is
|
||||
{{ birthday | date | uppercase}}
|
||||
</p>
|
||||
<!-- #enddocregion chained-birthday -->
|
||||
</p>
|
||||
|
||||
<!-- #docregion chained-parameter-birthday -->
|
||||
<p>
|
||||
<!-- #docregion chained-parameter-birthday -->
|
||||
The chained hero's birthday is
|
||||
{{ birthday | date:'fullDate' | uppercase}}
|
||||
</p>
|
||||
<!-- #enddocregion chained-parameter-birthday -->
|
||||
<!-- #docregion chained-parameter-birthday-parens -->
|
||||
</p>
|
||||
<p>
|
||||
<!-- #docregion chained-parameter-birthday-parens -->
|
||||
The chained hero's birthday is
|
||||
{{ ( birthday | date:'fullDate' ) | uppercase}}
|
||||
</p>
|
||||
<!-- #enddocregion chained-parameter-birthday-parens -->
|
||||
</p>
|
||||
<hr>
|
||||
<power-booster>loading...</power-booster>
|
||||
<a id="power-booster"></a>
|
||||
<power-booster></power-booster>
|
||||
|
||||
<hr>
|
||||
<power-boost-calculator>loading ..</power-boost-calculator>
|
||||
<a id="power-boost-calc"></a>
|
||||
<power-boost-calculator>loading</power-boost-calculator>
|
||||
|
||||
<hr>
|
||||
<a id="flying-heroes"></a>
|
||||
<flying-heroes></flying-heroes>
|
||||
|
||||
<hr>
|
||||
<a id="flying-heroes-impure"></a>
|
||||
<flying-heroes-impure></flying-heroes-impure>
|
||||
|
||||
<hr>
|
||||
<a id="hero-message"></a>
|
||||
<!-- async examples at the top so can see them in action -->
|
||||
<hero-message></hero-message>
|
||||
|
||||
<hr>
|
||||
<a id="hero-list"></a>
|
||||
<hero-list></hero-list>
|
||||
|
||||
<hr>
|
||||
<a id="random-pipe"></a>
|
||||
<random-pipe></random-pipe>
|
||||
|
||||
<div style="margin-top:12em;"></div>
|
||||
|
|
|
@ -1,20 +1,30 @@
|
|||
// #docregion
|
||||
import {Component} from 'angular2/core';
|
||||
import {HTTP_PROVIDERS} from 'angular2/http';
|
||||
|
||||
import {FlyingHeroesComponent,
|
||||
FlyingHeroesImpureComponent} from './flying-heroes.component';
|
||||
import {HeroAsyncMessageComponent} from './hero-async-message.component';
|
||||
import {HeroBirthday} from './hero-birthday2.component';
|
||||
import {HeroBirthday} from './hero-birthday1.component';
|
||||
import {HeroBirthday2} from './hero-birthday2.component';
|
||||
import {HeroListComponent} from './hero-list.component';
|
||||
import {PowerBooster} from './power-booster.component';
|
||||
import {PowerBoostCalculator} from './power-boost-calculator.component';
|
||||
import {RandomPipeComponent} from './random-pipe.component';
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
templateUrl: 'app/app.component.html',
|
||||
directives:[
|
||||
FlyingHeroesComponent, FlyingHeroesImpureComponent,
|
||||
HeroAsyncMessageComponent,
|
||||
HeroBirthday,
|
||||
HeroBirthday2,
|
||||
HeroListComponent,
|
||||
PowerBooster, PowerBoostCalculator
|
||||
]
|
||||
PowerBooster, PowerBoostCalculator,
|
||||
RandomPipeComponent
|
||||
],
|
||||
providers:[HTTP_PROVIDERS]
|
||||
})
|
||||
export class AppComponent {
|
||||
birthday = new Date(1988,3,15); // April 15, 1988
|
||||
|
|
|
@ -11,8 +11,8 @@ import {Pipe, PipeTransform} from 'angular2/core';
|
|||
*/
|
||||
@Pipe({name: 'exponentialStrength'})
|
||||
export class ExponentialStrengthPipe implements PipeTransform {
|
||||
|
||||
transform(value:number, args:string[]) : any {
|
||||
return Math.pow(value, parseInt(args[0] || '1', 10));
|
||||
transform(value:number, [exponent]) : number {
|
||||
var exp = parseFloat(exponent);
|
||||
return Math.pow(value, isNaN(exp) ? 1 : exp);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/// <reference path='./window.extension.d.ts'/>
|
||||
// #docregion
|
||||
import {Pipe, PipeTransform} from 'angular2/core';
|
||||
import {Http} from 'angular2/http';
|
||||
|
||||
// #docregion pipe-metadata
|
||||
@Pipe({
|
||||
|
@ -9,16 +9,20 @@ import {Pipe, PipeTransform} from 'angular2/core';
|
|||
})
|
||||
// #enddocregion pipe-metadata
|
||||
export class FetchJsonPipe implements PipeTransform{
|
||||
private fetchedValue:any;
|
||||
private fetchPromise:Promise<any>;
|
||||
private fetched:any = null;
|
||||
private prevUrl = '';
|
||||
|
||||
transform(value:string, args:string[]):any {
|
||||
if (!this.fetchPromise) {
|
||||
this.fetchPromise = window.fetch(value)
|
||||
.then((result:any) => result.json())
|
||||
.then((json:any) => this.fetchedValue = json);
|
||||
constructor(private _http: Http) { }
|
||||
|
||||
transform(url:string):any {
|
||||
if (url !== this.prevUrl) {
|
||||
this.prevUrl = url;
|
||||
this.fetched = null;
|
||||
this._http.get(url)
|
||||
.map( result => result.json() )
|
||||
.subscribe( result => this.fetched = result )
|
||||
}
|
||||
|
||||
return this.fetchedValue;
|
||||
return this.fetched;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<!-- #docplaster-->
|
||||
<!-- #docregion -->
|
||||
<h2>{{title}}</h2>
|
||||
<p>
|
||||
<!-- #docregion template-1 -->
|
||||
New hero:
|
||||
<input type="text" #box
|
||||
(keyup.enter)="addHero(box.value); box.value=''"
|
||||
placeholder="hero name">
|
||||
<!-- #enddocregion template-1 -->
|
||||
<input id="can-fly" type="checkbox" [(ngModel)]="canFly"> can fly
|
||||
</p>
|
||||
<p>
|
||||
<input id="mutate" type="checkbox" [(ngModel)]="mutate">Mutate array
|
||||
<!-- #docregion template-1 -->
|
||||
<button (click)="reset()">Reset</button>
|
||||
<!-- #enddocregion template-1 -->
|
||||
</p>
|
||||
|
||||
<h4>Heroes who fly (piped)</h4>
|
||||
<div id="flyers">
|
||||
<!-- #docregion template-flying-heroes -->
|
||||
<div *ngFor="#hero of (heroes | flyingHeroes)">
|
||||
{{hero.name}}
|
||||
</div>
|
||||
<!-- #enddocregion template-flying-heroes -->
|
||||
</div>
|
||||
|
||||
<h4>All Heroes (no pipe)</h4>
|
||||
<div id="all">
|
||||
<!-- #docregion template-1 -->
|
||||
<!-- #docregion template-all-heroes -->
|
||||
<div *ngFor="#hero of heroes">
|
||||
{{hero.name}}
|
||||
</div>
|
||||
<!-- #enddocregion template-all-heroes -->
|
||||
<!-- #enddocregion template-1 -->
|
||||
|
||||
</div>
|
|
@ -0,0 +1,64 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
import {Component} from 'angular2/core';
|
||||
import {FlyingHeroesPipe,
|
||||
FlyingHeroesImpurePipe} from './flying-heroes.pipe';
|
||||
import {HEROES} from './heroes';
|
||||
|
||||
@Component({
|
||||
selector: 'flying-heroes',
|
||||
templateUrl: 'app/flying-heroes.component.html',
|
||||
styles: ['#flyers, #all {font-style: italic}'],
|
||||
pipes: [FlyingHeroesPipe]
|
||||
})
|
||||
// #docregion v1
|
||||
export class FlyingHeroesComponent {
|
||||
heroes:any[] = [];
|
||||
canFly = true;
|
||||
// #enddocregion v1
|
||||
mutate = true;
|
||||
title = 'Flying Heroes (pure pipe)';
|
||||
|
||||
// #docregion v1
|
||||
constructor() { this.reset(); }
|
||||
|
||||
addHero(name:string) {
|
||||
name = name.trim();
|
||||
if (!name) { return; }
|
||||
let hero = {name, canFly: this.canFly};
|
||||
// #enddocregion v1
|
||||
if (this.mutate) {
|
||||
// Pure pipe won't update display because heroes array reference is unchanged
|
||||
// Impure pipe will display
|
||||
// #docregion v1
|
||||
// #docregion push
|
||||
this.heroes.push(hero)
|
||||
// #enddocregion push
|
||||
// #enddocregion v1
|
||||
} else {
|
||||
// Pipe updates display because heroes array is a new object
|
||||
// #docregion concat
|
||||
this.heroes = this.heroes.concat(hero);
|
||||
// #enddocregion concat
|
||||
}
|
||||
// #docregion v1
|
||||
}
|
||||
|
||||
reset() { this.heroes = HEROES.slice(); }
|
||||
}
|
||||
// #enddocregion v1
|
||||
|
||||
////// Identical except for impure pipe //////
|
||||
// #docregion impure-component
|
||||
@Component({
|
||||
selector: 'flying-heroes-impure',
|
||||
templateUrl: 'app/flying-heroes.component.html',
|
||||
// #enddocregion impure-component
|
||||
styles: ['.flyers, .all {font-style: italic}'],
|
||||
// #docregion impure-component
|
||||
pipes: [FlyingHeroesImpurePipe]
|
||||
})
|
||||
export class FlyingHeroesImpureComponent extends FlyingHeroesComponent {
|
||||
title = 'Flying Heroes (impure pipe)';
|
||||
}
|
||||
// #docregion impure-component
|
|
@ -0,0 +1,25 @@
|
|||
// #docregion
|
||||
// #docregion pure
|
||||
import {Flyer} from './heroes';
|
||||
import {Pipe, PipeTransform} from 'angular2/core';
|
||||
|
||||
@Pipe({ name: 'flyingHeroes' })
|
||||
export class FlyingHeroesPipe implements PipeTransform {
|
||||
transform(allHeroes:Flyer[]) {
|
||||
// #docregion filter
|
||||
return allHeroes.filter(hero => hero.canFly);
|
||||
// #enddocregion filter
|
||||
}
|
||||
}
|
||||
// #enddocregion pure
|
||||
|
||||
/////// Identical except for the pure flag
|
||||
// #docregion impure
|
||||
// #docregion pipe-decorator
|
||||
@Pipe({
|
||||
name: 'flyingHeroes',
|
||||
pure: false
|
||||
})
|
||||
// #enddocregion pipe-decorator
|
||||
export class FlyingHeroesImpurePipe extends FlyingHeroesPipe {}
|
||||
// #enddocregion impure
|
|
@ -1,15 +1,39 @@
|
|||
// #docregion
|
||||
import {Component} from 'angular2/core';
|
||||
import {Observable} from 'rxjs/Rx';
|
||||
|
||||
// Initial view: "Message: "
|
||||
// After 500ms: Message: You are my Hero!"
|
||||
|
||||
@Component({
|
||||
selector: 'hero-message',
|
||||
template: 'Message: {{delayedMessage | async}}',
|
||||
template: `
|
||||
<h2>Async Hero Message and AsyncPipe</h2>
|
||||
|
||||
<p>Message: {{ message$ | async }}</p>
|
||||
|
||||
<button (click)="resend()">Resend</button>`,
|
||||
})
|
||||
export class HeroAsyncMessageComponent {
|
||||
delayedMessage:Promise<string> = new Promise((resolve, reject) => {
|
||||
setTimeout(() => resolve('You are my Hero!'), 500);
|
||||
});
|
||||
message$:Observable<string>;
|
||||
|
||||
constructor() { this.resend(); }
|
||||
|
||||
resend() {
|
||||
this.message$ = Observable.interval(500)
|
||||
.map(i => this.messages[i])
|
||||
.take(this.messages.length);
|
||||
}
|
||||
|
||||
private messages = [
|
||||
'You are my hero!',
|
||||
'You are the best hero!',
|
||||
'Will you be my hero?'
|
||||
];
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
// Alternative message$ formula:
|
||||
// this.message$ = Observable.fromArray(this.messages)
|
||||
// .map(message => Observable.timer(500).map(() => message))
|
||||
// .concatAll();
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import {Component} from 'angular2/core'
|
||||
|
||||
@Component({
|
||||
selector: 'hero-birthday',
|
||||
selector: 'hero-birthday2',
|
||||
// #docregion template
|
||||
template: `
|
||||
<p>The hero's birthday is {{ birthday | date:format }}</p>
|
||||
|
@ -12,13 +12,11 @@ import {Component} from 'angular2/core'
|
|||
// #enddocregion template
|
||||
})
|
||||
// #docregion class
|
||||
export class HeroBirthday {
|
||||
export class HeroBirthday2 {
|
||||
birthday = new Date(1988,3,15); // April 15, 1988
|
||||
|
||||
toggle = true; // start with true == shortDate
|
||||
|
||||
get format() { return this.toggle ? 'shortDate' : 'fullDate'}
|
||||
|
||||
toggleFormat() { this.toggle = !this.toggle; }
|
||||
}
|
||||
// #enddocregion class
|
||||
|
|
|
@ -6,7 +6,7 @@ import {FetchJsonPipe} from './fetch-json.pipe';
|
|||
selector: 'hero-list',
|
||||
// #docregion template
|
||||
template: `
|
||||
<h4>Heroes from JSON File</h4>
|
||||
<h2>Heroes from JSON File</h2>
|
||||
|
||||
<div *ngFor="#hero of ('heroes.json' | fetch) ">
|
||||
{{hero.name}}
|
||||
|
@ -19,6 +19,4 @@ import {FetchJsonPipe} from './fetch-json.pipe';
|
|||
// #enddocregion template
|
||||
pipes: [FetchJsonPipe]
|
||||
})
|
||||
export class HeroListComponent {
|
||||
/* I've got nothing to do ;-) */
|
||||
}
|
||||
export class HeroListComponent { }
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export interface Flyer { canFly: boolean }
|
||||
export const HEROES = [
|
||||
{"name": "Windstorm", "canFly": true},
|
||||
{"name": "Bombasto", "canFly": false},
|
||||
{"name": "Magneto", "canFly": false},
|
||||
{"name": "Tornado", "canFly": true}
|
||||
];
|
|
@ -1,6 +1,6 @@
|
|||
import {bootstrap} from 'angular2/platform/browser';
|
||||
import 'rxjs/Rx';
|
||||
|
||||
import {AppComponent} from './app.component';
|
||||
import {HeroBirthday} from './hero-birthday1.component';
|
||||
|
||||
bootstrap(AppComponent);
|
||||
bootstrap(HeroBirthday); // v.1
|
|
@ -0,0 +1,24 @@
|
|||
import {Component} from 'angular2/core';
|
||||
import {Pipe, PipeTransform} from 'angular2/core';
|
||||
|
||||
// #docregion pipe
|
||||
// Pure pipe
|
||||
@Pipe({ name: 'randomizer' })
|
||||
export class RandomizerPipe implements PipeTransform {
|
||||
// Impure function
|
||||
transform() { return Math.random() * 10 ;}
|
||||
}
|
||||
// #enddocregion pipe
|
||||
|
||||
@Component({
|
||||
selector: 'random-pipe',
|
||||
template: `
|
||||
<h2>Random Pipe (pure pipe/impure function)</h2>
|
||||
<input #box (input)="0">
|
||||
<p>Input value: {{box.value}}</p>
|
||||
<p>Random pipe output: {{box.value | randomizer}}</p>
|
||||
`,
|
||||
pipes: [RandomizerPipe]
|
||||
})
|
||||
export class RandomPipeComponent {
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
interface Window {
|
||||
fetch(url: string, options? : {}) : Promise<any>
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
[
|
||||
{"name": "Windstorm"},
|
||||
{"name": "Bombasto"},
|
||||
{"name": "Magneto"},
|
||||
{"name": "Tornado"}
|
||||
{"name": "Windstorm", "canFly": true},
|
||||
{"name": "Bombasto", "canFly": false},
|
||||
{"name": "Magneto", "canFly": false},
|
||||
{"name": "Tornado", "canFly": true}
|
||||
]
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="node_modules/rxjs/bundles/Rx.js"></script>
|
||||
<script src="node_modules/angular2/bundles/angular2.dev.js"></script>
|
||||
<script src="node_modules/angular2/bundles/http.dev.js"></script>
|
||||
<script>
|
||||
System.config({
|
||||
packages: {
|
||||
|
@ -29,9 +30,6 @@
|
|||
</head>
|
||||
|
||||
<body>
|
||||
<h4>Hero Birthday v.1</h4>
|
||||
<hero-birthday>hero-birthday loading...</hero-birthday>
|
||||
|
||||
<my-app>my-app loading ...</my-app>
|
||||
</body>
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
"description": "Pipes",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js",
|
||||
"!**/*.d.ts"],
|
||||
"!**/*.js"],
|
||||
"tags": ["pipe"]
|
||||
}
|
|
@ -245,10 +245,8 @@ table(width="100%")
|
|||
td <a href="#docheck">DoCheck</a>
|
||||
td
|
||||
:marked
|
||||
Implements a `ngDoCheck` method with custom change detection
|
||||
that works because the data behave in a particularly simple way.
|
||||
See how often Angular calls this hook and
|
||||
watch it post changes to a log.
|
||||
Implements a `ngDoCheck` method with custom change detection.
|
||||
See how often Angular calls this hook and watch it post changes to a log.
|
||||
tr(style=top)
|
||||
td <a href="#afterview">AfterView</a>
|
||||
td
|
||||
|
@ -437,8 +435,12 @@ figure.image-display
|
|||
:marked
|
||||
## DoCheck
|
||||
|
||||
We can take over the change detection with the `DoCheck` hook when Angular doesn't
|
||||
catch an important change on its own.
|
||||
We can use the `DoCheck` hook to detect and act upon changes that Angular doesn't catch on its own.
|
||||
.l-sub-section
|
||||
:marked
|
||||
With this method we can detect a change that Angular overlooked.
|
||||
What we do with that information to refresh the display is a separate matter.
|
||||
:marked
|
||||
|
||||
The *DoCheck* sample extends the *OnChanges* sample with this implementation of `DoCheck`:
|
||||
+makeExample('lifecycle-hooks/ts/app/do-check.component.ts', 'ng-do-check', 'DoCheckComponent (ngDoCheck)')(format=".")
|
||||
|
|
|
@ -26,7 +26,7 @@ include ../_util-fns
|
|||
A pipe takes in data as input and transforms it to a desired output.
|
||||
We'll illustrate by transforming a component's birthday property into
|
||||
a human-friendly date:
|
||||
+makeExample('pipes/ts/app/hero-birthday1.component.ts', null, 'app/hero-birthday1.component.ts')
|
||||
+makeExample('pipes/ts/app/hero-birthday1.component.ts', null, 'app/hero-birthday1.component.ts')(format='.')
|
||||
|
||||
:marked
|
||||
Focus on the component's template.
|
||||
|
@ -44,9 +44,11 @@ include ../_util-fns
|
|||
They are all immediately available for use in any template.
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about these and many other built-in pipes in the the [API Reference](../api/);
|
||||
Learn more about these and many other built-in pipes in the the [API Reference](../api/#!?apiFilter=pipe);
|
||||
filter for entries that include the word "pipe".
|
||||
|
||||
Angular 2 doesn't have a `FilterPipe` or an `OrderByPipe` for reasons explained in an [appendix below](#no-filter-pipe)
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Parameterizing a Pipe
|
||||
|
@ -73,7 +75,7 @@ include ../_util-fns
|
|||
We also added a button to the template and bound its click event to the component's `toggleFormat` method.
|
||||
That method toggles the component's `format` property between a short form
|
||||
('shortDate') and a longer form ('fullDate').
|
||||
+makeExample('pipes/ts/app/hero-birthday2.component.ts', 'class', 'app/hero-birthday2.component.ts (class)')
|
||||
+makeExample('pipes/ts/app/hero-birthday2.component.ts', 'class', 'app/hero-birthday2.component.ts (class)')(format='.')
|
||||
:marked
|
||||
As we click the button, the displayed date alternates between
|
||||
"**<span style="font-family:courier">04/15/1988</span>**" and
|
||||
|
@ -103,12 +105,10 @@ figure.image-display
|
|||
|
||||
+makeExample('pipes/ts/app/app.component.html', 'chained-parameter-birthday')(format=".")
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
:marked
|
||||
We can add parentheses to alter the evaluation order or
|
||||
to provide extra clarity:
|
||||
+makeExample('pipes/ts/app/app.component.html', 'chained-parameter-birthday-parens')(format=".")
|
||||
:marked
|
||||
+makeExample('pipes/ts/app/app.component.html', 'chained-parameter-birthday-parens')(format=".")
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
|
@ -118,14 +118,14 @@ figure.image-display
|
|||
|
||||
Here's a custom pipe named `ExponentialStrengthPipe` that can boost a hero's powers:
|
||||
|
||||
+makeExample('pipes/ts/app/exponential-strength.pipe.ts', null, 'app/exponential-strength.pipe.ts')
|
||||
+makeExample('pipes/ts/app/exponential-strength.pipe.ts', null, 'app/exponential-strength.pipe.ts')(format=".")
|
||||
:marked
|
||||
This pipe definition reveals several key points
|
||||
|
||||
* A pipe is a class decorated with pipe metadata.
|
||||
|
||||
* The pipe class implements the `PipeTransform` interface's `transform` method that
|
||||
takes an input value and an optional array of parameter strings and returns the transformed value.
|
||||
accepts an input value and an optional array of parameters and returns the transformed value.
|
||||
|
||||
* There will be one item in the parameter array for each parameter passed to the pipe
|
||||
|
||||
|
@ -141,12 +141,12 @@ figure.image-display
|
|||
### The *PipeTransform* Interface
|
||||
|
||||
The `transform` method is essential to a pipe.
|
||||
The `PipeTransform` interface defines that method and guides both tooling and the compiler.
|
||||
It is optional; Angular looks for and executes the `transform` method regardless.
|
||||
The `PipeTransform` *interface* defines that method and guides both tooling and the compiler.
|
||||
It is technically optional; Angular looks for and executes the `transform` method regardless.
|
||||
|
||||
:marked
|
||||
Now we need a component to demonstrate our pipe.
|
||||
+makeExample('pipes/ts/app/power-booster.component.ts',null,'app/power-booster.component.ts')
|
||||
+makeExample('pipes/ts/app/power-booster.component.ts',null,'app/power-booster.component.ts')(format='.')
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/pipes/power-booster.png' alt="Power Booster")
|
||||
|
||||
|
@ -154,7 +154,7 @@ figure.image-display
|
|||
Two things to note:
|
||||
1. We use our custom pipe the same way we use the built-in pipes.
|
||||
|
||||
1. We must list our pipe in the @Component decorator's `pipes` array.
|
||||
1. We must list our pipe in the `pipes` array of the `@Component` decorator.
|
||||
|
||||
.callout.is-helpful
|
||||
header Remember the pipes array!
|
||||
|
@ -172,85 +172,248 @@ figure.image-display
|
|||
We could upgrade the example to a "Power Boost Calculator" that combines
|
||||
our pipe and two-way data binding with `ngModel`.
|
||||
|
||||
+makeExample('pipes/ts/app/power-boost-calculator.component.ts', null, '/app/power-boost-calculator.component.ts')
|
||||
+makeExample('pipes/ts/app/power-boost-calculator.component.ts', null, '/app/power-boost-calculator.component.ts')(format='.')
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/pipes/power-boost-calculator-anim.gif' alt="Power Boost Calculator")
|
||||
|
||||
.l-main-section
|
||||
a(id="change-detection")
|
||||
:marked
|
||||
## Pipes and Change Detection
|
||||
Angular looks for changes to data-bound values through a *change detection* process that runs after every JavaScript event:
|
||||
every keystroke, mouse move, timer tick, and server response. It could be expensive.
|
||||
Angular strives to lower the cost whenever possible and appropriate.
|
||||
|
||||
Angular picks a simpler, faster change detection algorithm when we use a pipe. Let's see how.
|
||||
|
||||
### No pipe
|
||||
The component in our next example uses the default, aggressive change detection strategy to monitor and update
|
||||
its display of every hero in the `heroes` array. Here's the template:
|
||||
+makeExample('pipes/ts/app/flying-heroes.component.html', 'template-1', 'app/flying-heroes.component.html (v1)')(format='.')
|
||||
:marked
|
||||
The companion component class provides heroes, pushes new heroes into the array, and can reset the array.
|
||||
+makeExample('pipes/ts/app/flying-heroes.component.ts', 'v1', 'app/flying-heroes.component.ts (v1)')(format='.')
|
||||
:marked
|
||||
We can add a new hero and Angular updates the display when we do.
|
||||
The `reset` button replaces `heroes` with a new array of the original heroes and Angular updates the display when we do.
|
||||
If we added the ability to remove or change a hero, Angular would detect those changes too and update the display again.
|
||||
add or remove heroes. It updates the display when we modify a hero.
|
||||
|
||||
### Flying Heroes pipe
|
||||
Let's add a `FlyingHeroesPipe` to the `*ngFor` repeater that filters the list of heroes to just those heroes who can fly.
|
||||
+makeExample('pipes/ts/app/flying-heroes.component.html', 'template-flying-heroes', 'app/flying-heroes.component.html (flyers)')(format='.')
|
||||
:marked
|
||||
Here's the `FlyingHeroesPipe` implementation which follows the pattern for custom pipes we saw earlier.
|
||||
+makeExample('pipes/ts/app/flying-heroes.pipe.ts', 'pure', 'app/flying-heroes.pipe.ts')(format='.')
|
||||
|
||||
:marked
|
||||
When we run the sample now we see odd behavior (try it in the [live example](/resources/live-examples/pipes/ts/plnkr.html)).
|
||||
Every hero we add is a flying hero but none of them are displayed.
|
||||
|
||||
Although we're not getting the behavior we want, Angular isn't broken.
|
||||
It's just using a different change detection algorithm — one that ignores changes to the list or any of its items.
|
||||
|
||||
Look at how we're adding a new hero:
|
||||
+makeExample('pipes/ts/app/flying-heroes.component.ts', 'push')(format='.')
|
||||
:marked
|
||||
We're pushing the new hero into the `heroes` array. The object reference to the array hasn't changed.
|
||||
It's the same array. That's all Angular cares about. From its perspective, *same array, no change, no display update*.
|
||||
|
||||
We can fix that. Let's use `concat` to create a new array with the new hero appended and assign that to `heroes`.
|
||||
This time Angular detects that the array object reference has changed.
|
||||
It executes the pipe and updates the display with the new array which includes the new flying hero.
|
||||
|
||||
*If we **mutate** the array, no pipe and no display update;
|
||||
if we **replace** the array, the pipe executes and the display updates*.
|
||||
|
||||
The *Flying Heroes* in the [live example](/resources/live-examples/pipes/ts/plnkr.html) extends the
|
||||
code with checkbox switches and additional displays to help us experience these effects.
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/pipes/flying-heroes-anim.gif' alt="Flying Heroes")
|
||||
|
||||
:marked
|
||||
Replacing the array is an efficient way to signal to Angular that it should update the display.
|
||||
When do we replace the array? When the data change.
|
||||
|
||||
That's an easy rule to follow in *this toy* example
|
||||
where the only way to change the data is by adding a new hero.
|
||||
|
||||
More often we don't know when the data have changed,
|
||||
especially in applications that mutate data in many ways,
|
||||
perhaps in application locations far away.
|
||||
A component is such an application usually can't know about those changes.
|
||||
|
||||
Moreover, it's unwise to distort our component design to accommodate a pipe.
|
||||
We strive as much as possible to keep the component class independent of the HTML.
|
||||
The component should be unaware of pipes.
|
||||
|
||||
Perhaps we should consider a different kind of pipe for filtering flying heroes, an *impure pipe*.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Stateful Pipes
|
||||
## Pure and Impure Pipes
|
||||
|
||||
There are two categories of pipes, stateless and stateful.
|
||||
There are two categories of pipes: **pure** and **impure**.
|
||||
|
||||
Stateless pipes are pure functions that flow input data
|
||||
through without remembering anything or causing detectable side-effects.
|
||||
Pipes are pure by default. Every pipe we've seen so far has been pure.
|
||||
|
||||
Most pipes are stateless. The `DatePipe` in our first example is a stateless pipe. So is our custom `ExponentialStrengthPipe`.
|
||||
We make a pipe impure by setting its pure flag to false. We could make the `FlyingHeroesPipe`
|
||||
impure with a flip of the switch:
|
||||
|
||||
Stateful pipes are conceptually similar to classes in object-oriented programming. They can manage the data they transform. A pipe that creates an HTTP request, stores the response and displays the output, is a stateful pipe.
|
||||
Pipes that retrieve or request data should be used cautiously, since working with network data tends to introduce error conditions that are better handled in JavaScript than in a template.
|
||||
We can mitigate this risk by creating a custom pipe for a particular backend and bake-in the essential error-handling.
|
||||
+makeExample('pipes/ts/app/flying-heroes.pipe.ts', 'pipe-decorator')(format='.')
|
||||
:marked
|
||||
Before we do that, let's understand the difference between *pure* and *impure*, starting with a *pure* pipe.
|
||||
|
||||
### Pure pipes
|
||||
|
||||
Angular executes a *pure pipe* only when it detects a *pure change* to the input value.
|
||||
|
||||
A *pure change* is *either* a change to a primitive input value (`String`, `Number`, `Boolean`, `Symbol`)
|
||||
*or* a changed object reference (`Date`, `Array`, `Function`, `Object`).
|
||||
|
||||
Angular ignores changes *within* the object itself.
|
||||
It won't call a pure pipe if we change the input month, add to the input array, or update an input object property.
|
||||
|
||||
This may seem restrictive but is is also fast.
|
||||
An object reference check is fast ... much faster than a deep check for differences.
|
||||
... so Angular can quickly determine if it can skip both the pipe execution and a screen update.
|
||||
|
||||
For this reason, we prefer a pure pipe if we can live with the change detection strategy.
|
||||
When we can't, we *may* turn to the impure pipe.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Or we might not use a pipe at all.
|
||||
It may be better to pursue the pipe's purpose with a property of the component,
|
||||
a point we take up later.
|
||||
:marked
|
||||
### Impure pipes
|
||||
Angular executes an *impure pipe* during *every* component change detection cycle.
|
||||
An impure pipe will be called a lot, as often as every keystroke or mouse-move.
|
||||
|
||||
If follows that we must implement an impure pipe with great care.
|
||||
An expensive, long-running pipe could destroy the user experience.
|
||||
|
||||
<a id="impure-flying-heroes"></a>
|
||||
### An impure *FlyingHeroesPipe*
|
||||
|
||||
A flip of the switch turns our `FlyingHeroesPipe` into a `FlyingHeroesImpurePipe`.
|
||||
Here's the complete implementation:
|
||||
+makeTabs(
|
||||
'pipes/ts/app/flying-heroes.pipe.ts, pipes/ts/app/flying-heroes.pipe.ts',
|
||||
'impure, pure',
|
||||
'FlyingHeroesImpurePipe, FlyingHeroesPipe')(format='.')
|
||||
:marked
|
||||
We inherit from `FlyingHeroesPipe` to prove the point that nothing changed internally.
|
||||
The only difference is the `pure` flag in the pipe metadata.
|
||||
|
||||
This is a good candidate for an impure pipe because the `transform` function is trivial and fast.
|
||||
+makeExample('pipes/ts/app/flying-heroes.pipe.ts','filter')(format='.')
|
||||
|
||||
We can derive a `FlyingHeroesImpureComponent` that we derive from the `FlyingHeroesComponent`.
|
||||
+makeExample('pipes/ts/app/flying-heroes.component.ts','impure-component',
|
||||
'app/flying-heroes.component.ts (FlyingHeroesImpureComponent)')(format='.')
|
||||
:marked
|
||||
The only substantive change is the pipe.
|
||||
We can confirm in the [live example](/resources/live-examples/pipes/ts/plnkr.html)
|
||||
that the *flying heroes* display updates as we enter new heroes even when we mutate the `heroes` array.
|
||||
|
||||
<a id="async-pipe"></a>
|
||||
## The stateful `AsyncPipe`
|
||||
The Angular Async pipe is a remarkable example of a stateful pipe.
|
||||
The Async pipe can receive a Promise or Observable as input
|
||||
and subscribe to the input automatically, eventually returning the emitted value(s).
|
||||
### The impure *AsyncPipe*
|
||||
The Angular `AsyncPipe` is an interesting example of an impure pipe.
|
||||
The `AsyncPipe` accepts a `Promise` or `Observable` as input
|
||||
and subscribes to the input automatically, eventually returning the emitted value(s).
|
||||
|
||||
It is stateful because the pipe maintains a subscription to the input and its returned values depend on that subscription.
|
||||
It is also stateful.
|
||||
The pipe maintains a subscription to the input `Observable` and
|
||||
keeps delivering values from that `Observable` as they arrive.
|
||||
|
||||
In the next example, we bind a simple promise to a view with the async pipe.
|
||||
In this next example, we bind an `Observable` of message strings (`messages$`) to a view with the `async` pipe.
|
||||
+makeExample('pipes/ts/app/hero-async-message.component.ts', null, 'app/hero-async-message.component.ts')
|
||||
|
||||
:marked
|
||||
The Async pipe saves boilerplate in the component code.
|
||||
The component doesn't have to subscribe to the async data source,
|
||||
it doesn't extract the resolved values and expose them for binding,
|
||||
and (in the case of Observable stream sources like `EventEmitter`)
|
||||
the component doesn't have to unsubscribe when it is destroyed
|
||||
and the component doesn't have to unsubscribe when it is destroyed
|
||||
(a potent source of memory leaks).
|
||||
|
||||
### Implementing a Stateful Pipe
|
||||
Pipes are stateless by default.
|
||||
We must declare a pipe to be stateful
|
||||
by setting the `pure` property of the `@Pipe` decorator to `false`.
|
||||
This setting tells Angular’s change detection system to
|
||||
check the output of this pipe each cycle, whether its input has changed or not.
|
||||
### An impure caching pipe
|
||||
|
||||
Let's write one more impure pipe, a pipe that makes an http request to the server.
|
||||
|
||||
Normally, that's a horrible idea.
|
||||
It's probably a horrible idea no matter what we do.
|
||||
We're forging ahead anyway to make a point.
|
||||
|
||||
Remember that impure pipes are called every few microseconds.
|
||||
If we're not careful, this pipe will punish the server with requests.
|
||||
|
||||
We are careful. Our pipe only makes a server call if the request URL has changed.
|
||||
It caches the request URL and waits for a result which it also caches when it arrives.
|
||||
The pipe returns the cached result (which is null while a request is in flight)
|
||||
after every Angular call and only contacts the server as necessary.
|
||||
|
||||
Here's the code, which uses the [Angular http](server-communication.html) facility
|
||||
to retrieve a `heroes.json` file:
|
||||
|
||||
Here's how we'll decorate our new stateful `FetchJsonPipe` that
|
||||
makes an HTTP `fetch` request and (eventually) displays the data in the server's response:
|
||||
+makeExample('pipes/ts/app/fetch-json.pipe.ts', 'pipe-metadata','app/fetch-json.pipe.ts (metadata)')
|
||||
:marked
|
||||
Immediately below we have the finished pipe. Its input value is an url to an endpoint that returns a JSON file.
|
||||
The pipe makes a one-time async request to the server and eventually receives the JSON response.
|
||||
+makeExample('pipes/ts/app/fetch-json.pipe.ts', null, 'app/fetch-json.pipe.ts')
|
||||
:marked
|
||||
Next we demonstrate this pipe in a test component whose template defines two bindings
|
||||
Then we demonstrate it in a harness component whose template defines two bindings to this pipe.
|
||||
+makeExample('pipes/ts/app/hero-list.component.ts', 'template', 'app/hero-list.component.ts (template)')
|
||||
:marked
|
||||
Despite the two bindings and what we know to be frequent pipe calls,
|
||||
the nework tab in the browser developer tools confirms that there is only one request for the file.
|
||||
|
||||
The component renders like this:
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/pipes/hero-list.png' alt="Hero List")
|
||||
:marked
|
||||
The first binding is straight forward. An `ngFor` repeater displays the hero names fetched from a json source file.
|
||||
We're piping the literal file name, "heroes.json", through to the custom `fetch` pipe.
|
||||
|
||||
### JsonPipe
|
||||
The second binding uses more pipe chaining.
|
||||
We take the same fetched results and display the raw hero data in JSON format
|
||||
by piping to the built-in `JsonPipe`.
|
||||
### *JsonPipe*
|
||||
The second binding involving the `FetchPipe` uses more pipe chaining.
|
||||
We take the same fetched results displayed in the first binding
|
||||
and display them again, this time in JSON format by chaining through to the built-in `JsonPipe`.
|
||||
|
||||
.callout.is-helpful
|
||||
header Debugging with the json pipe
|
||||
:marked
|
||||
The [JsonPipe](../api/common/JsonPipe-class.html)
|
||||
is an easy way to diagnosis a mysteriously failing data binding.
|
||||
provides an easy way to diagnosis a mysteriously failing data binding or
|
||||
inspect an object for future binding.
|
||||
:marked
|
||||
Here's the complete component implementation:
|
||||
+makeExample('pipes/ts/app/hero-list.component.ts', null, 'app/hero-list.component.ts')
|
||||
|
||||
a(id="pure-pipe-pure-fn")
|
||||
:marked
|
||||
### Pure pipes vs. pure functions
|
||||
|
||||
When developers first hear of *pure pipes* many of them think these pipes must be *pure functions*.
|
||||
|
||||
Pure functions process inputs and return values without detectable side-effects.
|
||||
Given the same input they should always return the same output.
|
||||
|
||||
Pure pipes _are_ typically implemented with pure function.
|
||||
The pipes we saw earlier in this chapter were implemented with pure functions.
|
||||
The built-in `DatePipe` is a pure pipe with a pure function implementation.
|
||||
So is our `ExponentialStrengthPipe`.
|
||||
So is our `FlyingHeroesPipe`.
|
||||
|
||||
But there is no necessary connection between a pure pipe and a pure function.
|
||||
A few steps back we reviewed the `FlyingHeroesImpurePipe` — *an impure pipe with a pure function*.
|
||||
|
||||
We can also write *a pure pipe with an impure function* such as the
|
||||
`RandomizerPipe` (also in the [live example](/resources/live-examples/pipes/ts/plnkr.html))
|
||||
that ignores its input value and outputs a random number. Clearly an impure function!
|
||||
Yet it's perfectly well behaved as a pure pipe.
|
||||
+makeExample('pipes/ts/app/random-pipe.component.ts', 'pipe','app/random-pipe.component.ts (pipe)')(format='.')
|
||||
:marked
|
||||
When the input changes, it outputs a new value albeit never the same value for the same input.
|
||||
|
||||
Remember *purity in a pipe has nothing to do with pure functions!*
|
||||
|
||||
*Pipe purity* is more about the purity of the input values than of the pipe itself.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
|
@ -261,5 +424,52 @@ figure.image-display
|
|||
into our templates expressions to enrich the appeal and usability
|
||||
of our views.
|
||||
|
||||
Explore Angular's inventory of built-in pipes in the [API Reference](../api/).
|
||||
Explore Angular's inventory of built-in pipes in the [API Reference](../api/#!?apiFilter=pipe).
|
||||
Try writing a custom pipe and perhaps contributing it to the community.
|
||||
|
||||
a(id="no-filter-pipe")
|
||||
.l-main-section
|
||||
:marked
|
||||
## No *FilterPipe* or *OrderByPipe*
|
||||
Angular does not ship with pipes for filtering or sorting lists.
|
||||
Developers familiar with Angular 1 know these as `filter` and `orderBy`.
|
||||
There are no equivalents in Angular 2.
|
||||
|
||||
This is not an oversight. Angular 2 is unlikely to offer such pipes because
|
||||
(a) they perform poorly and (b) they prevent aggressive minification.
|
||||
|
||||
Both *filter* and *orderBy* require parameters that reference object properties.
|
||||
We learned earlier that such pipes must be [*impure*](#pure-and-impure-pipes) and that
|
||||
Angular calls impure pipes in almost every change detection cycle.
|
||||
|
||||
Filtering and especially sorting are expensive operations.
|
||||
The user experience can degrade severely for even moderate sized lists when Angular calls these pipe methods many times per second.
|
||||
The `filter` and `orderBy` have often been abused in Angular 1 apps, leading to complaints that Angular itself is slow.
|
||||
That charge is fair in the indirect sense that Angular 1 prepared this performance trap
|
||||
by offering `filter` and `orderBy` in the first place.
|
||||
|
||||
The minification hazard is also compelling if less obvious. Imagine a sorting pipe applied to a list of heroes.
|
||||
We might sort the list by hero `name` and `planet` origin properties something like this:
|
||||
code-example(format="." language="html")
|
||||
<!-- NOT REAL CODE! -->
|
||||
<div *ngFor="#hero of heroes | orderBy:'name,planet'"></div>
|
||||
:marked
|
||||
We identify the sort fields by text strings, expecting the pipe to reference a property value by indexing
|
||||
(e.g., `hero['name']`).
|
||||
Unfortunately, aggressive minification *munges* the `Hero` property names so that `Hero.name` and `Hero.planet`
|
||||
becomes something like `Hero.a` and `Hero.b`. Clearly `hero['name']` is not going to work.
|
||||
|
||||
Some of us may not care to minify this aggressively. That's *our* choice.
|
||||
But the Angular product should not prevent someone else from minifying aggressively.
|
||||
Therefore, the Angular team decided that everything shipped in Angular will minify safely.
|
||||
|
||||
The Angular team and many experienced Angular developers strongly recommend that you move
|
||||
filtering and sorting logic into the component itself.
|
||||
The component can expose a `filteredHeroes` or `sortedHeroes` property and take control
|
||||
over when and how often to execute the supporting logic.
|
||||
Any capabilities that you would have put in a pipe and shared across the app can be
|
||||
written in a filtering/sorting service and injected into the component.
|
||||
|
||||
If these performance and minification considerations do not apply to you, you can always create your own such pipes
|
||||
(along the lines of the [FlyingHeroesPipe](#impure-flying-heroes)) or find them in the community.
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 53 KiB |
Loading…
Reference in New Issue