docs(dev guide): pipes - new Dart prose, update Dart and Ts code (#1353)

+ guide/pipes/ts: update docs and example code

+ guide/pipes/dart: new prose, updated example code

+ fix platform_directives reference; html cleanup

+ enable pipes e2e testing
For `e2e-spec.js`: If the async test is executed too early it will fail
(simply because the async message hasn’t been received yet).

+ follow new constants naming convention
This commit is contained in:
Patrice Chalin 2016-05-13 13:44:14 -07:00 committed by Thibault Sottiaux
parent c1440e7eff
commit ad95b04e69
30 changed files with 444 additions and 215 deletions

View File

@ -15,6 +15,10 @@
- var _array = 'array'; - var _array = 'array';
- var _an_array = 'an array'; - var _an_array = 'an array';
//- Promise vs. Future, etc
- var _Promise = 'Promise';
- var _Observable = 'Observable';
//- Used to prefix identifiers that are private. In Dart this will be '_'. //- Used to prefix identifiers that are private. In Dart this will be '_'.
- var _priv = ''; - var _priv = '';

View File

@ -1,20 +1,26 @@
// #docregion
import 'package:angular2/angular2.dart'; import 'package:angular2/angular2.dart';
import 'flying_heroes_component.dart';
import 'hero_async_message_component.dart'; import 'hero_async_message_component.dart';
import 'hero_birthday1_component.dart';
import 'hero_birthday2_component.dart'; import 'hero_birthday2_component.dart';
import 'hero_list_component.dart'; import 'hero_list_component.dart';
import 'power_booster.dart'; import 'power_boost_calculator_component.dart';
import 'power_boost_calculator.dart'; import 'power_booster_component.dart';
@Component( @Component(
selector: 'my-app', selector: 'my-app',
templateUrl: 'app_component.html', templateUrl: 'app_component.html',
directives: const [ directives: const [
FlyingHeroesComponent,
FlyingHeroesImpureComponent,
HeroAsyncMessageComponent, HeroAsyncMessageComponent,
HeroBirthday, HeroBirthday,
HeroBirthday2,
HeroListComponent, HeroListComponent,
PowerBoostCalculator,
PowerBooster, PowerBooster,
PowerBoostCalculator
]) ])
class AppComponent { class AppComponent {
DateTime birthday = new DateTime(1988, 4, 15); // April 15, 1988 DateTime birthday = new DateTime(1988, 4, 15); // April 15, 1988

View File

@ -1,36 +1,83 @@
<a id="toc"></a>
<h1>Pipes</h1>
<a href="#happy-birthday1">Happy Birthday v1</a><br>
<a href="#birthday-date-pipe">Birthday DatePipe</a><br>
<a href="#happy-birthday2">Happy Birthday v2</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>
<hr> <hr>
<a id="happy-birthday1"></a>
<h2>Hero Birthday v1</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-->
<!-- #docregion format-birthday -->
<p>The hero's birthday is {{ birthday | date:"MM/dd/yy" }} </p>
<!-- #enddocregion format-birthday-->
<hr>
<a id="happy-birthday2"></a>
<h2>Hero Birthday v2</h2>
<hero-birthday2></hero-birthday2>
<hr>
<a id="birthday-pipe-chaining"></a>
<h2>Birthday Pipe Chaining</h2>
<p>
<!-- #docregion chained-birthday -->
The chained hero's birthday is
{{ birthday | date | uppercase}}
<!-- #enddocregion chained-birthday -->
</p>
<p>
<!-- #docregion chained-parameter-birthday -->
The chained hero's birthday is
{{ birthday | date:'fullDate' | uppercase}}
<!-- #enddocregion chained-parameter-birthday -->
</p>
<p>
<!-- #docregion chained-parameter-birthday-parens -->
The chained hero's birthday is
{{ ( birthday | date:'fullDate' ) | uppercase}}
<!-- #enddocregion chained-parameter-birthday-parens -->
</p>
<hr>
<a id="power-booster"></a>
<power-booster></power-booster>
<hr>
<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 --> <!-- async examples at the top so can see them in action -->
<hero-message></hero-message> <hero-message></hero-message>
<hr> <hr>
<a id="hero-list"></a>
<hero-list></hero-list> <hero-list></hero-list>
<hr> <div style="margin-top:12em;"></div>
<p>The hero's birthday is {{ birthday | date }}</p>
<p>The hero's birthday is {{ birthday | date:"MM/dd/yy" }} </p>
<hr>
<h4>Hero Birthday v.2</h4>
<hero-birthday>loading...</hero-birthday>
<hr>
<p>
The chained hero's birthday is
{{ birthday | date | uppercase}}
</p>
<p>
The chained hero's birthday is
{{ birthday | date:'fullDate' | uppercase}}
</p>
<p>
The chained hero's birthday is
{{ ( birthday | date:'fullDate' ) | uppercase}}
</p>
<hr>
<power-booster>loading...</power-booster>
<hr>
<power-boost-calculator>loading ..</power-boost-calculator>

View File

@ -1,5 +1,5 @@
// #docregion
import 'dart:math' as math; import 'dart:math' as math;
import 'package:angular2/angular2.dart'; import 'package:angular2/angular2.dart';
/* /*
@ -13,11 +13,13 @@ import 'package:angular2/angular2.dart';
*/ */
@Pipe(name: 'exponentialStrength') @Pipe(name: 'exponentialStrength')
class ExponentialStrengthPipe extends PipeTransform { class ExponentialStrengthPipe extends PipeTransform {
transform(dynamic value, [List<dynamic> args]) { num transform(dynamic _value, [List<dynamic> args]) {
var v = int.parse(value.toString(), onError: (source) => 0); var exponent = args.isEmpty
var p = args.isEmpty
? 1 ? 1
: int.parse(args.first.toString(), onError: (source) => 1); : args.first is num
return math.pow(v, p); ? args.first
: num.parse(args.first.toString(), (_) => 1);
var value = _value is num ? _value : num.parse(_value.toString(), (_) => 0);
return math.pow(value, exponent);
} }
} }

View File

@ -1,7 +1,6 @@
// #docregion // #docregion
import 'dart:html';
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:html';
import 'package:angular2/angular2.dart'; import 'package:angular2/angular2.dart';
@ -9,15 +8,17 @@ import 'package:angular2/angular2.dart';
@Pipe(name: 'fetch', pure: false) @Pipe(name: 'fetch', pure: false)
// #enddocregion pipe-metadata // #enddocregion pipe-metadata
class FetchJsonPipe extends PipeTransform { class FetchJsonPipe extends PipeTransform {
dynamic _fetchedValue; dynamic _fetchedJson;
Future<dynamic> _fetchPromise; String _prevUrl;
transform(dynamic url, [List<dynamic> args]) { dynamic transform(dynamic url, [List<dynamic> args]) {
if (_fetchPromise == null) { if (url != _prevUrl) {
_fetchPromise = new Future(() async { _prevUrl = url;
_fetchedValue = JSON.decode(await HttpRequest.getString(url)); _fetchedJson = null;
HttpRequest.getString(url).then((s) {
_fetchedJson = JSON.decode(s);
}); });
} }
return _fetchedValue; return _fetchedJson;
} }
} }

View File

@ -0,0 +1,66 @@
// #docplaster
// #docregion
import 'package:angular2/angular2.dart';
import 'flying_heroes_pipe.dart';
import 'heroes.dart';
@Component(
selector: 'flying-heroes',
templateUrl: 'flying_heroes_component.html',
styles: const ['#flyers, #all {font-style: italic}'],
pipes: const [FlyingHeroesPipe])
// #docregion v1
class FlyingHeroesComponent {
List<Hero> heroes;
bool canFly = true;
// #enddocregion v1
bool mutate = true;
String title = 'Flying Heroes (pure pipe)';
// #docregion v1
FlyingHeroesComponent() {
reset();
}
void addHero(String name) {
name = name.trim();
if (name.isEmpty) return;
var hero = new Hero(name, canFly);
// #enddocregion v1
if (mutate) {
// Pure pipe won't update display because heroes list
// reference is unchanged; Impure pipe will display.
// #docregion v1, push
heroes.add(hero);
// #enddocregion v1, push
} else {
// Pipe updates display because heroes list is a new object
// #docregion concat
heroes = new List<Hero>.from(heroes)..add(hero);
// #enddocregion concat
}
// #docregion v1
}
void reset() {
heroes = new List<Hero>.from(mockHeroes);
}
}
// #enddocregion v1
//\\\\ Identical except for impure pipe \\\\\\
// #docregion impure-component
@Component(
selector: 'flying-heroes-impure',
templateUrl: 'flying_heroes_component.html',
// #enddocregion impure-component
styles: const ['.flyers, .all {font-style: italic}'],
// #docregion impure-component
pipes: const [FlyingHeroesImpurePipe])
class FlyingHeroesImpureComponent extends FlyingHeroesComponent {
FlyingHeroesImpureComponent() {
title = 'Flying Heroes (impure pipe)';
}
}
// #docregion impure-component

View File

@ -0,0 +1,38 @@
<!-- #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>

View File

@ -0,0 +1,19 @@
// #docregion
// #docregion pure
import 'package:angular2/angular2.dart';
import 'heroes.dart';
@Pipe(name: 'flyingHeroes')
class FlyingHeroesPipe extends PipeTransform {
// #docregion filter
List<Hero> transform(dynamic value, [List<dynamic> args]) =>
value.where((hero) => hero.canFly).toList();
// #enddocregion filter
}
// #enddocregion pure
// Identical except for the pure flag
// #docregion impure, pipe-decorator
@Pipe(name: 'flyingHeroes', pure: false)
// #enddocregion pipe-decorator
class FlyingHeroesImpurePipe extends FlyingHeroesPipe {}

View File

@ -1,12 +1,32 @@
// #docregion
import 'dart:async'; import 'dart:async';
import 'package:angular2/angular2.dart'; import 'package:angular2/angular2.dart';
@Component( @Component(
selector: 'hero-message', template: 'Message: {{delayedMessage | async}}') selector: 'hero-message',
template: '''
<h2>Async Hero Message and AsyncPipe</h2>
<p>Message: {{ message | async }}</p>
<button (click)="resend()">Resend</button>
''')
class HeroAsyncMessageComponent { class HeroAsyncMessageComponent {
Future<String> delayedMessage = static const _msgEventDelay = const Duration(milliseconds: 500);
new Future.delayed(new Duration(milliseconds: 500), () {
return 'You are my Hero!'; Stream<String> message;
});
HeroAsyncMessageComponent() {
resend();
}
void resend() {
message =
new Stream.periodic(_msgEventDelay, (i) => _msgs[i]).take(_msgs.length);
}
List<String> _msgs = <String>[
'You are my hero!',
'You are the best hero!',
'Will you be my hero?'
];
} }

View File

@ -1,10 +1,12 @@
// #docregion
import 'package:angular2/angular2.dart'; import 'package:angular2/angular2.dart';
@Component( @Component(
selector: 'hero-birthday', selector: 'hero-birthday',
template: ''' // #docregion hero-birthday-template
<p>The hero's birthday is {{ birthday | date }}</p> template: "<p>The hero's birthday is {{ birthday | date }}</p>"
''') // #enddocregion hero-birthday-template
)
class HeroBirthday { class HeroBirthday {
DateTime birthday = new DateTime(1988, 4, 15); // April 15, 1988 DateTime birthday = new DateTime(1988, 4, 15); // April 15, 1988
} }

View File

@ -1,12 +1,17 @@
// #docregion
import 'package:angular2/angular2.dart'; import 'package:angular2/angular2.dart';
@Component( @Component(
selector: 'hero-birthday', selector: 'hero-birthday2',
// #docregion template
template: ''' template: '''
<p>The hero's birthday is {{ birthday | date:format }}</p> <p>The hero's birthday is {{ birthday | date:format }}</p>
<button (click)="toggleFormat()">Toggle Format</button> <button (click)="toggleFormat()">Toggle Format</button>
''') '''
class HeroBirthday { // #enddocregion template
)
// #docregion class
class HeroBirthday2 {
DateTime birthday = new DateTime(1988, 4, 15); // April 15, 1988 DateTime birthday = new DateTime(1988, 4, 15); // April 15, 1988
bool toggle = true; bool toggle = true;

View File

@ -1,9 +1,11 @@
// #docregion
import 'package:angular2/angular2.dart'; import 'package:angular2/angular2.dart';
import 'fetch_json_pipe.dart'; import 'fetch_json_pipe.dart';
@Component( @Component(
selector: 'hero-list', selector: 'hero-list',
// #docregion template
template: ''' template: '''
<h4>Heroes from JSON File</h4> <h4>Heroes from JSON File</h4>

View File

@ -0,0 +1,15 @@
class Hero {
final String name;
final bool canFly;
const Hero(this.name, this.canFly);
String toString() => "$name (${canFly ? 'can fly' : 'doesn\'t fly'})";
}
const List<Hero> mockHeroes = const <Hero>[
const Hero("Windstorm", true),
const Hero("Bombasto", false),
const Hero("Magneto", false),
const Hero("Tornado", true),
];

View File

@ -1,21 +0,0 @@
import 'package:angular2/angular2.dart';
import 'exponential_strength_pipe.dart';
@Component(
selector: 'power-boost-calculator',
template: '''
<h2>Power Boost Calculator</h2>
<div>Normal power: <input [(ngModel)]="power" /></div>
<div>Boost factor: <input [(ngModel)]="factor" /></div>
<p>
Super Hero Power: {{power | exponentialStrength: factor}}
</p>
''',
pipes: const [ExponentialStrengthPipe],
directives: const [COMMON_DIRECTIVES])
class PowerBoostCalculator {
// XXX: These should be ints, but that causes exceptions in checked mode.
String power = '5';
String factor = '1';
}

View File

@ -0,0 +1,19 @@
// #docregion
import 'package:angular2/angular2.dart';
import 'exponential_strength_pipe.dart';
@Component(
selector: 'power-boost-calculator',
template: '''
<h2>Power Boost Calculator</h2>
<div>Normal power: <input type="number" [(ngModel)]="power"/></div>
<div>Boost factor: <input type="number" [(ngModel)]="factor"/></div>
<p>
Super Hero Power: {{power | exponentialStrength: factor}}
</p>
''',
pipes: const [ExponentialStrengthPipe])
class PowerBoostCalculator {
num power = 5;
num factor = 1;
}

View File

@ -1,3 +1,4 @@
// #docregion
import 'package:angular2/angular2.dart'; import 'package:angular2/angular2.dart';
import 'exponential_strength_pipe.dart'; import 'exponential_strength_pipe.dart';
@ -5,9 +6,7 @@ import 'exponential_strength_pipe.dart';
selector: 'power-booster', selector: 'power-booster',
template: ''' template: '''
<h2>Power Booster</h2> <h2>Power Booster</h2>
<p> <p>Super power boost: {{2 | exponentialStrength: 10}}</p>
Super power boost: {{2 | exponentialStrength: 10}}
</p>
''', ''',
pipes: const [ExponentialStrengthPipe]) pipes: const [ExponentialStrengthPipe])
class PowerBooster {} class PowerBooster {}

View File

@ -10,7 +10,7 @@ dependencies:
dart_to_js_script_rewriter: ^1.0.1 dart_to_js_script_rewriter: ^1.0.1
transformers: transformers:
- angular2: - angular2:
platform_directives: 'package:angular2/src/common/directives.dart#CORE_DIRECTIVES' platform_directives: 'package:angular2/common.dart#COMMON_DIRECTIVES'
platform_pipes: 'package:angular2/common.dart#COMMON_PIPES' platform_pipes: 'package:angular2/common.dart#COMMON_PIPES'
entry_points: web/main.dart entry_points: web/main.dart
- dart_to_js_script_rewriter - dart_to_js_script_rewriter

View File

@ -8,9 +8,6 @@
</head> </head>
<body> <body>
<h4>Hero Birthday v.1</h4>
<hero-birthday>hero-birthday loading...</hero-birthday>
<my-app>my-app loading ...</my-app> <my-app>my-app loading ...</my-app>
</body> </body>

View File

@ -9,10 +9,6 @@ describe('Pipes', function () {
expect(element(by.css('hero-birthday p')).getText()).toEqual("The hero's birthday is Apr 15, 1988"); expect(element(by.css('hero-birthday p')).getText()).toEqual("The hero's birthday is Apr 15, 1988");
}); });
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 () { it('should show 4 heroes', function () {
expect(element.all(by.css('hero-list div')).count()).toEqual(4); expect(element.all(by.css('hero-list div')).count()).toEqual(4);
}); });
@ -114,4 +110,8 @@ describe('Pipes', function () {
}) })
}); });
it('should show an async hero message', function () {
expect(element.all(by.tagName('hero-message')).get(0).getText()).toContain('hero');
});
}); });

View File

@ -1,8 +1,8 @@
<a id="toc"></a> <a id="toc"></a>
<h1>Pipes</h1> <h1>Pipes</h1>
<a href="#happy-birthday1">Happy Birthday v.1</a><br> <a href="#happy-birthday1">Happy Birthday v1</a><br>
<a href="#birthday-date-pipe">Birthday DatePipe</a><br> <a href="#birthday-date-pipe">Birthday DatePipe</a><br>
<a href="#happy-birthday2">Happy Birthday v.2</a><br> <a href="#happy-birthday2">Happy Birthday v2</a><br>
<a href="#birthday-pipe-chaining">Birthday Pipe Chaining</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-booster">Power Booster custom pipe</a><br>
<a href="#power-boost-calc">Power Boost Calculator custom pipe with params</a><br> <a href="#power-boost-calc">Power Boost Calculator custom pipe with params</a><br>
@ -14,7 +14,7 @@
<hr> <hr>
<a id="happy-birthday1"></a> <a id="happy-birthday1"></a>
<h2>Hero Birthday v.1</h2> <h2>Hero Birthday v1</h2>
<hero-birthday></hero-birthday> <hero-birthday></hero-birthday>
<hr> <hr>
@ -30,7 +30,7 @@
<hr> <hr>
<a id="happy-birthday2"></a> <a id="happy-birthday2"></a>
<h2>Hero Birthday v.2</h2> <h2>Hero Birthday v2</h2>
<hero-birthday2></hero-birthday2> <hero-birthday2></hero-birthday2>
<hr> <hr>

View File

@ -9,7 +9,7 @@ import { Http } from '@angular/http';
}) })
// #enddocregion pipe-metadata // #enddocregion pipe-metadata
export class FetchJsonPipe implements PipeTransform{ export class FetchJsonPipe implements PipeTransform{
private fetched:any = null; private fetchedJson: any = null;
private prevUrl = ''; private prevUrl = '';
constructor(private _http: Http) { } constructor(private _http: Http) { }
@ -17,12 +17,12 @@ export class FetchJsonPipe implements PipeTransform{
transform(url: string): any { transform(url: string): any {
if (url !== this.prevUrl) { if (url !== this.prevUrl) {
this.prevUrl = url; this.prevUrl = url;
this.fetched = null; this.fetchedJson = null;
this._http.get(url) this._http.get(url)
.map( result => result.json() ) .map( result => result.json() )
.subscribe( result => this.fetched = result ); .subscribe( result => this.fetchedJson = result );
} }
return this.fetched; return this.fetchedJson;
} }
} }

View File

@ -35,5 +35,4 @@ New hero:
</div> </div>
<!-- #enddocregion template-all-heroes --> <!-- #enddocregion template-all-heroes -->
<!-- #enddocregion template-1 --> <!-- #enddocregion template-1 -->
</div> </div>

View File

@ -2,16 +2,11 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { Observable } from 'rxjs/Rx'; import { Observable } from 'rxjs/Rx';
// Initial view: "Message: "
// After 500ms: Message: You are my Hero!"
@Component({ @Component({
selector: 'hero-message', selector: 'hero-message',
template: ` template: `
<h2>Async Hero Message and AsyncPipe</h2> <h2>Async Hero Message and AsyncPipe</h2>
<p>Message: {{ message$ | async }}</p> <p>Message: {{ message$ | async }}</p>
<button (click)="resend()">Resend</button>`, <button (click)="resend()">Resend</button>`,
}) })
export class HeroAsyncMessageComponent { export class HeroAsyncMessageComponent {

View File

@ -1,4 +1,3 @@
// Version #1
// #docregion // #docregion
import { Component } from '@angular/core' import { Component } from '@angular/core'
@ -11,4 +10,3 @@ import { Component } from '@angular/core'
export class HeroBirthday { export class HeroBirthday {
birthday = new Date(1988,3,15); // April 15, 1988 birthday = new Date(1988,3,15); // April 15, 1988
} }
// #enddocregion

View File

@ -1,4 +1,3 @@
// Version #2
// #docregion // #docregion
import { Component } from '@angular/core' import { Component } from '@angular/core'
@ -19,4 +18,3 @@ export class HeroBirthday2 {
get format() { return this.toggle ? 'shortDate' : 'fullDate'} get format() { return this.toggle ? 'shortDate' : 'fullDate'}
toggleFormat() { this.toggle = !this.toggle; } toggleFormat() { this.toggle = !this.toggle; }
} }
// #enddocregion class

View File

@ -7,9 +7,7 @@ import { ExponentialStrengthPipe } from './exponential-strength.pipe';
selector: 'power-booster', selector: 'power-booster',
template: ` template: `
<h2>Power Booster</h2> <h2>Power Booster</h2>
<p> <p>Super power boost: {{2 | exponentialStrength: 10}}</p>
Super power boost: {{2 | exponentialStrength: 10}}
</p>
`, `,
pipes: [ExponentialStrengthPipe] pipes: [ExponentialStrengthPipe]
}) })

View File

@ -6,6 +6,8 @@ include ../../../_includes/_util-fns
- var _array = 'list'; - var _array = 'list';
- var _an_array = 'a list'; - var _an_array = 'a list';
- var _priv = '_'; - var _priv = '_';
- var _Promise = 'Future';
- var _Observable = 'Stream';
mixin liveExampleLink(linkText, exampleUrlPartName) mixin liveExampleLink(linkText, exampleUrlPartName)
a(href='https://angular-examples.github.io/#{exampleUrlPartName}')= linkText a(href='https://angular-examples.github.io/#{exampleUrlPartName}')= linkText

View File

@ -1,12 +1,11 @@
extends ../../../ts/latest/guide/pipes.jade
block includes
include ../_util-fns include ../_util-fns
block pure-change
:marked :marked
We're working on the Dart version of this chapter. Angular executes a *pure pipe* only when it detects a *pure change* to the input value.
In the meantime, please see these resources: In Angular Dart, a *pure change* results only from a change in object reference
(given that [everything is an object in Dart](https://www.dartlang.org/docs/dart-up-and-running/ch02.html#important-concepts)).
* [Pipes](/docs/ts/latest/guide/pipes.html):
The TypeScript version of this chapter
* [Dart source code](https://github.com/angular/angular.io/tree/master/public/docs/_examples/pipes/dart):
A preliminary version of the example code that will appear in this chapter

View File

@ -1,23 +1,23 @@
block includes
include ../_util-fns include ../_util-fns
:marked :marked
Every application starts out with what seems like a simple task: get data, transform them, and show them to users. Every application starts out with what seems like a simple task: get data, transform them, and show them to users.
Getting data could be as simple as creating a local variable or as complex as streaming data over a Websocket. Getting data could be as simple as creating a local variable or as complex as streaming data over a Websocket.
Once data arrive, we could push their raw `toString` values directly to screen. Once data arrive, we could push their raw `toString` values directly to the view.
That rarely makes for a good user experience. That rarely makes for a good user experience.
Almost everyone prefers a simple birthday date E.g., almost everyone prefers a simple birthday date like
(<span style="font-family:courier">April 15, 1988</span>) to the original raw string format <samp>April 15, 1988</samp> to the original raw string format
( <span style="font-family:courier">Fri Apr 15 1988 00:00:00 GMT-0700 (Pacific Daylight Time)</span> ). &mdash; <samp>Fri Apr 15 1988 00:00:00 GMT-0700 (Pacific Daylight Time)</samp>.
Clearly some values benefit from a bit of massage. We soon discover that we Clearly some values benefit from a bit of massage. We soon discover that we
desire many of the same transformations repeatedly, both within and across many applications. desire many of the same transformations repeatedly, both within and across many applications.
We almost think of them as styles. We almost think of them as styles.
In fact, we'd like to apply them in our HTML templates as we do styles. In fact, we'd like to apply them in our HTML templates as we do styles.
p.
Welcome, Angular pipes, the simple display-value transformations that we can declare in our HTML! Welcome, Angular pipes, the simple display-value transformations that we can declare in our HTML!
Try the #[+liveExampleLink2('live example', 'pipes')].
[Live Example](/resources/live-examples/pipes/ts/plnkr.html).
.l-main-section .l-main-section
:marked :marked
@ -26,11 +26,14 @@ include ../_util-fns
A pipe takes in data as input and transforms it to a desired output. 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 We'll illustrate by transforming a component's birthday property into
a human-friendly date: a human-friendly date:
+makeExample('pipes/ts/app/hero-birthday1.component.ts', null, 'app/hero-birthday1.component.ts')(format='.') +makeExample('pipes/ts/app/hero-birthday1.component.ts', null, 'app/hero-birthday1.component.ts')(format='.')
:marked :marked
Focus on the component's template. Focus on the component's template.
+makeExample('pipes/ts/app/app.component.html', 'hero-birthday-template')(format=".") +makeExample('pipes/ts/app/app.component.html', 'hero-birthday-template')(format=".")
:marked :marked
Inside the interpolation expression we flow the component's `birthday` value through the Inside the interpolation expression we flow the component's `birthday` value through the
[pipe operator](./template-syntax.html#pipe) ( | ) to the [Date pipe](../api/common/DatePipe-class.html) [pipe operator](./template-syntax.html#pipe) ( | ) to the [Date pipe](../api/common/DatePipe-class.html)
@ -39,26 +42,27 @@ include ../_util-fns
.l-main-section .l-main-section
:marked :marked
## Built-in pipes ## Built-in pipes
Angular comes with a stock set of pipes such as Angular comes with a stock of pipes such as
`DatePipe`, `UpperCasePipe`, `LowerCasePipe`, `CurrencyPipe`, and `PercentPipe`. `DatePipe`, `UpperCasePipe`, `LowerCasePipe`, `CurrencyPipe`, and `PercentPipe`.
They are all immediately available for use in any template. They are all immediately available for use in any template.
.l-sub-section .l-sub-section
:marked :marked
Learn more about these and many other built-in pipes in the the [API Reference](../api/#!?apiFilter=pipe); 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". 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) Angular 2 doesn't have a `FilterPipe` or an `OrderByPipe` for reasons explained in an [appendix below](#no-filter-pipe).
.l-main-section .l-main-section
:marked :marked
## Parameterizing a Pipe ## Parameterizing a Pipe
A pipe may accept any number of optional parameters to fine-tune its output.
A pipe may accept any number of optional parameters to fine-tune its output.
We add parameters to a pipe by following the pipe name with a colon ( : ) and then the parameter value We add parameters to a pipe by following the pipe name with a colon ( : ) and then the parameter value
(e.g., `currency:'EUR'`). If our pipe accepts multiple parameters, we separate the values with colons (e.g. `slice:1:5`) (e.g., `currency:'EUR'`). If our pipe accepts multiple parameters, we separate the values with colons (e.g. `slice:1:5`)
We'll modify our birthday template to give the date pipe a format parameter. We'll modify our birthday template to give the date pipe a format parameter.
After formatting the hero's April 15th birthday should display as **<span style="font-family:courier">04/15/88</span>**. After formatting the hero's April 15th birthday, it should render as **<samp>04/15/88</samp>**.
+makeExample('pipes/ts/app/app.component.html', 'format-birthday')(format=".") +makeExample('pipes/ts/app/app.component.html', 'format-birthday')(format=".")
@ -70,16 +74,20 @@ include ../_util-fns
Let's write a second component that *binds* the pipe's format parameter Let's write a second component that *binds* the pipe's format parameter
to the component's `format` property. Here's the template for that component: to the component's `format` property. Here's the template for that component:
+makeExample('pipes/ts/app/hero-birthday2.component.ts', 'template', 'app/hero-birthday2.component.ts (template)')(format=".") +makeExample('pipes/ts/app/hero-birthday2.component.ts', 'template', 'app/hero-birthday2.component.ts (template)')(format=".")
:marked :marked
We also added a button to the template and bound its click event to the component's `toggleFormat` method. 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 That method toggles the component's `format` property between a short form
('shortDate') and a longer form ('fullDate'). (`'shortDate'`) and a longer form (`'fullDate'`).
+makeExample('pipes/ts/app/hero-birthday2.component.ts', 'class', 'app/hero-birthday2.component.ts (class)')(format='.') +makeExample('pipes/ts/app/hero-birthday2.component.ts', 'class', 'app/hero-birthday2.component.ts (class)')(format='.')
:marked :marked
As we click the button, the displayed date alternates between As we click the button, the displayed date alternates between
"**<span style="font-family:courier">04/15/1988</span>**" and "**<samp>04/15/1988</samp>**" and
"**<span style="font-family:courier">Friday, April 15, 1988</span>**". "**<samp>Friday, April 15, 1988</samp>**".
figure.image-display figure.image-display
img(src='/resources/images/devguide/pipes/date-format-toggle-anim.gif' alt="Date Format Toggle") img(src='/resources/images/devguide/pipes/date-format-toggle-anim.gif' alt="Date Format Toggle")
@ -88,37 +96,32 @@ figure.image-display
.l-sub-section .l-sub-section
:marked :marked
Learn more about the `DatePipes` format options in the [API Docs](../api/common/DatePipe-class.html). Learn more about the `DatePipes` format options in the [API Docs](../api/common/DatePipe-class.html).
:marked :marked
## Chaining pipes ## Chaining pipes
We can chain pipes together in potentially useful combinations. We can chain pipes together in potentially useful combinations.
In the following example, we chain the birthday to the `DatePipe` and on to the `UpperCasePipe` In the following example, we chain the birthday to the `DatePipe` and on to the `UpperCasePipe`
so we can display the birthday in uppercase. The following birthday displays as so we can display the birthday in uppercase. The following birthday displays as
**<span style="font-family:courier">APR 15, 1988</span>** **<samp>APR 15, 1988</samp>**
+makeExample('pipes/ts/app/app.component.html', 'chained-birthday')(format=".") +makeExample('pipes/ts/app/app.component.html', 'chained-birthday')(format=".")
:marked :marked
If we pass a parameter to a filter, we have to add parentheses This example &mdash; which displays **<samp>FRIDAY, APRIL 15, 1988</samp>** &mdash;
to help the template compiler with the evaluation order. chains the same pipes as above, but passes in a parameter to `date` as well.
The following example displays
**<span style="font-family:courier">FRIDAY, APRIL 15, 1988</span>**
+makeExample('pipes/ts/app/app.component.html', 'chained-parameter-birthday')(format=".") +makeExample('pipes/ts/app/app.component.html', 'chained-parameter-birthday')(format=".")
: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=".")
.l-main-section .l-main-section
:marked :marked
## Custom Pipes ## Custom Pipes
We can write our own custom pipes. We can write our own custom pipes.
Here's a custom pipe named `ExponentialStrengthPipe` that can boost a hero's powers: 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')(format=".") +makeExample('pipes/ts/app/exponential-strength.pipe.ts', null, 'app/exponential-strength.pipe.ts')(format=".")
:marked :marked
This pipe definition reveals several key points This pipe definition reveals several key points
@ -128,13 +131,13 @@ figure.image-display
accepts an input value followed by optional parameters and returns the transformed value. accepts an input value followed by optional parameters and returns the transformed value.
* There will be one additional argument to the `transform` method for each parameter passed to the pipe. * There will be one additional argument to the `transform` method for each parameter passed to the pipe.
Our pipe has one such parameter: The `exponent`. Our pipe has one such parameter: the `exponent`.
* We tell Angular that this is a pipe by applying the * We tell Angular that this is a pipe by applying the
`@Pipe` decorator which we import from the core Angular library. `@Pipe` #{_decorator} which we import from the core Angular library.
* The `@Pipe` decorator takes an object with a name property whose value is the * The `@Pipe` #{_decorator} allows us to define the
pipe name that we'll use within a template expression. It must be a valid JavaScript identifier. pipe name that we'll use within template expressions. It must be a valid JavaScript identifier.
Our pipe's name is `exponentialStrength`. Our pipe's name is `exponentialStrength`.
.l-sub-section .l-sub-section
@ -155,20 +158,23 @@ figure.image-display
Two things to note: Two things to note:
1. We use our custom pipe the same way we use the built-in pipes. 1. We use our custom pipe the same way we use the built-in pipes.
1. We must list our pipe in the `pipes` array of the `@Component` decorator. 1. We must include our pipe in the `pipes` #{_array} of the `@Component` #{_decorator}.
.callout.is-helpful .callout.is-helpful
header Remember the pipes array! header Remember the pipes #{_array}!
:marked :marked
Angular reports an error if we neglect to list our custom pipe. Angular reports an error if we neglect to list our custom pipe.
We didn't list the `DatePipe` in our previous example because all We didn't list the `DatePipe` in our previous example because all
Angular built-in pipes are pre-registered. Angular built-in pipes are pre-registered.
Custom pipes must be registered manually. Custom pipes must be registered manually.
:marked
If we try the [live code](/resources/live-examples/pipes/ts/plnkr.html) example, p.
If we try the #[+liveExampleLink('live code', 'pipes')] example,
we can probe its behavior by changing the value and the optional exponent in the template. we can probe its behavior by changing the value and the optional exponent in the template.
:marked
## Power Boost Calculator (extra-credit) ## Power Boost Calculator (extra-credit)
It's not much fun updating the template to test our custom pipe. It's not much fun updating the template to test our custom pipe.
We could upgrade the example to a "Power Boost Calculator" that combines We could upgrade the example to a "Power Boost Calculator" that combines
our pipe and two-way data binding with `ngModel`. our pipe and two-way data binding with `ngModel`.
@ -179,29 +185,34 @@ figure.image-display
img(src='/resources/images/devguide/pipes/power-boost-calculator-anim.gif' alt="Power Boost Calculator") img(src='/resources/images/devguide/pipes/power-boost-calculator-anim.gif' alt="Power Boost Calculator")
.l-main-section .l-main-section
a(id="change-detection") a#change-detection
:marked :marked
## Pipes and Change Detection ## Pipes and Change Detection
Angular looks for changes to data-bound values through a *change detection* process that runs after every JavaScript event: 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. every keystroke, mouse move, timer tick, and server response. This could be expensive.
Angular strives to lower the cost whenever possible and appropriate. 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. Angular picks a simpler, faster change detection algorithm when we use a pipe. Let's see how.
### No pipe ### No pipe
The component in our next example uses the default, aggressive change detection strategy to monitor and update 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: 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='.') +makeExample('pipes/ts/app/flying-heroes.component.html', 'template-1', 'app/flying-heroes.component.html (v1)')(format='.')
:marked :marked
The companion component class provides heroes, pushes new heroes into the array, and can reset the array. The companion component class provides heroes, adds 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='.') +makeExample('pipes/ts/app/flying-heroes.component.ts', 'v1', 'app/flying-heroes.component.ts (v1)')(format='.')
:marked :marked
We can add a new hero and Angular updates the display when we do. 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. 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. If we added the ability to remove or change a hero, Angular would detect those changes too and update the display as well.
add or remove heroes. It updates the display when we modify a hero.
### Flying Heroes pipe ### 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. 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='.') +makeExample('pipes/ts/app/flying-heroes.component.html', 'template-flying-heroes', 'app/flying-heroes.component.html (flyers)')(format='.')
:marked :marked
@ -218,25 +229,24 @@ a(id="change-detection")
Look at how we're adding a new hero: Look at how we're adding a new hero:
+makeExample('pipes/ts/app/flying-heroes.component.ts', 'push')(format='.') +makeExample('pipes/ts/app/flying-heroes.component.ts', 'push')(format='.')
:marked :marked
We're pushing the new hero into the `heroes` array. The object reference to the array hasn't changed. We're adding the new hero into the `heroes` #{_array}. The 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*. 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`. We can fix that. Let's 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. This time Angular detects that the #{_array} reference has changed.
It executes the pipe and updates the display with the new array which includes the new flying hero. 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*.
*If we **mutate** the #{_array}, no pipe is invoked and no display updated;
if we **replace** the #{_array}, then the pipe executes and the display is updated*.
The *Flying Heroes* in the [live example](/resources/live-examples/pipes/ts/plnkr.html) extends the 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. code with checkbox switches and additional displays to help us experience these effects.
figure.image-display figure.image-display
img(src='/resources/images/devguide/pipes/flying-heroes-anim.gif' alt="Flying Heroes") img(src='/resources/images/devguide/pipes/flying-heroes-anim.gif' alt="Flying Heroes")
:marked :marked
Replacing the array is an efficient way to signal to Angular that it should update the display. 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. When do we replace the #{_array}? When the data change.
That's an easy rule to follow in *this toy* example 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. where the only way to change the data is by adding a new hero.
@ -244,7 +254,6 @@ figure.image-display
especially in applications that mutate data in many ways, especially in applications that mutate data in many ways,
perhaps in application locations far away. perhaps in application locations far away.
A component is such an application usually can't know about those changes. 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. 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. We strive as much as possible to keep the component class independent of the HTML.
The component should be unaware of pipes. The component should be unaware of pipes.
@ -256,29 +265,31 @@ figure.image-display
## Pure and Impure Pipes ## Pure and Impure Pipes
There are two categories of pipes: **pure** and **impure**. There are two categories of pipes: **pure** and **impure**.
Pipes are pure by default. Every pipe we've seen so far has been pure. Pipes are pure by default. Every pipe we've seen so far has been pure.
We make a pipe impure by setting its pure flag to false. We could make the `FlyingHeroesPipe` We make a pipe impure by setting its pure flag to false. We could make the `FlyingHeroesPipe`
impure with a flip of the switch: impure like this:
+makeExample('pipes/ts/app/flying-heroes.pipe.ts', 'pipe-decorator')(format='.') +makeExample('pipes/ts/app/flying-heroes.pipe.ts', 'pipe-decorator')(format='.')
:marked :marked
Before we do that, let's understand the difference between *pure* and *impure*, starting with a *pure* pipe. Before we do that, let's understand the difference between *pure* and *impure*, starting with a *pure* pipe.
### Pure pipes ### Pure pipes
block pure-change
:marked
Angular executes a *pure pipe* only when it detects a *pure change* to the input value. 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`)
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`). *or* a changed object reference (`Date`, `Array`, `Function`, `Object`).
Angular ignores changes *within* the object itself. :marked
It won't call a pure pipe if we change the input month, add to the input array, or update an input object property. Angular ignores changes *within* (composite) objects.
It won't call a pure pipe if we change an input month, add to an input #{_array}, or update an input object property.
This may seem restrictive but is is also fast. This may seem restrictive but is is also fast.
An object reference check is fast ... much faster than a deep check for differences. An object reference check is fast &mdash; much faster than a deep check for
... so Angular can quickly determine if it can skip both the pipe execution and a screen update. differences &mdash; so Angular can quickly determine if it can skip both the
pipe execution and a view update.
For this reason, we prefer a pure pipe if we can live with the change detection strategy. 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. When we can't, we *may* turn to the impure pipe.
@ -288,8 +299,10 @@ figure.image-display
Or we might not use a pipe at all. 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, It may be better to pursue the pipe's purpose with a property of the component,
a point we take up later. a point we take up later.
:marked :marked
### Impure pipes ### Impure pipes
Angular executes an *impure pipe* during *every* component change detection cycle. 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. An impure pipe will be called a lot, as often as every keystroke or mouse-move.
@ -305,6 +318,7 @@ figure.image-display
'pipes/ts/app/flying-heroes.pipe.ts, pipes/ts/app/flying-heroes.pipe.ts', 'pipes/ts/app/flying-heroes.pipe.ts, pipes/ts/app/flying-heroes.pipe.ts',
'impure, pure', 'impure, pure',
'FlyingHeroesImpurePipe, FlyingHeroesPipe')(format='.') 'FlyingHeroesImpurePipe, FlyingHeroesPipe')(format='.')
:marked :marked
We inherit from `FlyingHeroesPipe` to prove the point that nothing changed internally. We inherit from `FlyingHeroesPipe` to prove the point that nothing changed internally.
The only difference is the `pure` flag in the pipe metadata. The only difference is the `pure` flag in the pipe metadata.
@ -317,19 +331,23 @@ figure.image-display
:marked :marked
The only substantive change is the pipe. The only substantive change is the pipe.
We can confirm in the [live example](/resources/live-examples/pipes/ts/plnkr.html) 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. that the *flying heroes* display updates as we enter new heroes even when we mutate the `heroes` #{_array}.
- var _dollar = _docsFor === 'ts' ? '$' : '';
:marked
<a id="async-pipe"></a> <a id="async-pipe"></a>
### The impure *AsyncPipe* ### The impure *AsyncPipe*
The Angular `AsyncPipe` is an interesting example of an impure pipe. The Angular `AsyncPipe` is an interesting example of an impure pipe.
The `AsyncPipe` accepts a `Promise` or `Observable` as input The `AsyncPipe` accepts a `#{_Promise}` or `#{_Observable}` as input
and subscribes to the input automatically, eventually returning the emitted value(s). and subscribes to the input automatically, eventually returning the emitted value(s).
It is also stateful. It is also stateful.
The pipe maintains a subscription to the input `Observable` and The pipe maintains a subscription to the input `#{_Observable}` and
keeps delivering values from that `Observable` as they arrive. keeps delivering values from that `#{_Observable}` as they arrive.
In this next example, we bind an `#{_Observable}` of message strings
(`message#{_dollar}`) 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') +makeExample('pipes/ts/app/hero-async-message.component.ts', null, 'app/hero-async-message.component.ts')
:marked :marked
@ -341,12 +359,10 @@ figure.image-display
### An impure caching pipe ### An impure caching pipe
Let's write one more impure pipe, a pipe that makes an http request to the server. Let's write one more impure pipe, a pipe that makes an HTTP request to the server.
Normally, that's a horrible idea. Normally, that's a horrible idea.
It's probably a horrible idea no matter what we do. It's probably a horrible idea no matter what we do.
We're forging ahead anyway to make a point. We're forging ahead anyway to make a point.
Remember that impure pipes are called every few microseconds. Remember that impure pipes are called every few microseconds.
If we're not careful, this pipe will punish the server with requests. If we're not careful, this pipe will punish the server with requests.
@ -367,10 +383,13 @@ figure.image-display
the nework tab in the browser developer tools confirms that there is only one request for the file. the nework tab in the browser developer tools confirms that there is only one request for the file.
The component renders like this: The component renders like this:
figure.image-display figure.image-display
img(src='/resources/images/devguide/pipes/hero-list.png' alt="Hero List") img(src='/resources/images/devguide/pipes/hero-list.png' alt="Hero List")
:marked :marked
### *JsonPipe* ### *JsonPipe*
The second binding involving the `FetchPipe` uses more pipe chaining. The second binding involving the `FetchPipe` uses more pipe chaining.
We take the same fetched results displayed in the first binding 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`. and display them again, this time in JSON format by chaining through to the built-in `JsonPipe`.
@ -381,8 +400,10 @@ figure.image-display
The [JsonPipe](../api/common/JsonPipe-class.html) The [JsonPipe](../api/common/JsonPipe-class.html)
provides an easy way to diagnosis a mysteriously failing data binding or provides an easy way to diagnosis a mysteriously failing data binding or
inspect an object for future binding. inspect an object for future binding.
:marked :marked
Here's the complete component implementation: Here's the complete component implementation:
+makeExample('pipes/ts/app/hero-list.component.ts', null, 'app/hero-list.component.ts') +makeExample('pipes/ts/app/hero-list.component.ts', null, 'app/hero-list.component.ts')
a(id="pure-pipe-pure-fn") a(id="pure-pipe-pure-fn")
@ -390,7 +411,6 @@ a(id="pure-pipe-pure-fn")
### Pure pipes and pure functions ### Pure pipes and pure functions
A pure pipe uses pure functions. A pure pipe uses pure functions.
Pure functions process inputs and return values without detectable side-effects. Pure functions process inputs and return values without detectable side-effects.
Given the same input they should always return the same output. Given the same input they should always return the same output.
@ -398,7 +418,6 @@ a(id="pure-pipe-pure-fn")
The built-in `DatePipe` is a pure pipe with a pure function implementation. The built-in `DatePipe` is a pure pipe with a pure function implementation.
So is our `ExponentialStrengthPipe`. So is our `ExponentialStrengthPipe`.
So is our `FlyingHeroesPipe`. So is our `FlyingHeroesPipe`.
A few steps back we reviewed the `FlyingHeroesImpurePipe` &mdash; *an impure pipe with a pure function*. A few steps back we reviewed the `FlyingHeroesImpurePipe` &mdash; *an impure pipe with a pure function*.
But a *pure pipe* must always be implemented with a *pure function*. Failure to heed this warning will bring about many a console errors regarding expressions that have changed after they were checked. But a *pure pipe* must always be implemented with a *pure function*. Failure to heed this warning will bring about many a console errors regarding expressions that have changed after they were checked.
@ -419,14 +438,14 @@ a(id="no-filter-pipe")
.l-main-section .l-main-section
:marked :marked
## No *FilterPipe* or *OrderByPipe* ## No *FilterPipe* or *OrderByPipe*
Angular does not ship with pipes for filtering or sorting lists. Angular does not ship with pipes for filtering or sorting lists.
Developers familiar with Angular 1 know these as `filter` and `orderBy`. Developers familiar with Angular 1 know these as `filter` and `orderBy`.
There are no equivalents in Angular 2. There are no equivalents in Angular 2.
This is not an oversight. Angular 2 is unlikely to offer such pipes because This is not an oversight. Angular 2 is unlikely to offer such pipes because
(a) they perform poorly and (b) they prevent aggressive minification. (a) they perform poorly and (b) they prevent aggressive minification.
Both `filter` and `orderBy` require parameters that reference object properties.
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 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. Angular calls impure pipes in almost every change detection cycle.
@ -437,8 +456,8 @@ a(id="no-filter-pipe")
by offering `filter` and `orderBy` in the first place. 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. 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: We might sort the list by hero `name` and `planet` of origin properties something like this:
code-example(format="." language="html") code-example(language="html")
&lt;!-- NOT REAL CODE! --> &lt;!-- NOT REAL CODE! -->
&lt;div *ngFor="let hero of heroes | orderBy:'name,planet'">&lt;/div> &lt;div *ngFor="let hero of heroes | orderBy:'name,planet'">&lt;/div>
:marked :marked