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 _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 '_'.
- var _priv = '';

View File

@ -1,20 +1,26 @@
// #docregion
import 'package:angular2/angular2.dart';
import 'flying_heroes_component.dart';
import 'hero_async_message_component.dart';
import 'hero_birthday1_component.dart';
import 'hero_birthday2_component.dart';
import 'hero_list_component.dart';
import 'power_booster.dart';
import 'power_boost_calculator.dart';
import 'power_boost_calculator_component.dart';
import 'power_booster_component.dart';
@Component(
selector: 'my-app',
templateUrl: 'app_component.html',
directives: const [
FlyingHeroesComponent,
FlyingHeroesImpureComponent,
HeroAsyncMessageComponent,
HeroBirthday,
HeroBirthday2,
HeroListComponent,
PowerBoostCalculator,
PowerBooster,
PowerBoostCalculator
])
class AppComponent {
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>
<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 -->
<hero-message></hero-message>
<hr>
<a id="hero-list"></a>
<hero-list></hero-list>
<hr>
<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>
<div style="margin-top:12em;"></div>

View File

@ -1,5 +1,5 @@
// #docregion
import 'dart:math' as math;
import 'package:angular2/angular2.dart';
/*
@ -13,11 +13,13 @@ import 'package:angular2/angular2.dart';
*/
@Pipe(name: 'exponentialStrength')
class ExponentialStrengthPipe extends PipeTransform {
transform(dynamic value, [List<dynamic> args]) {
var v = int.parse(value.toString(), onError: (source) => 0);
var p = args.isEmpty
num transform(dynamic _value, [List<dynamic> args]) {
var exponent = args.isEmpty
? 1
: int.parse(args.first.toString(), onError: (source) => 1);
return math.pow(v, p);
: args.first is num
? 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
import 'dart:html';
import 'dart:async';
import 'dart:convert';
import 'dart:html';
import 'package:angular2/angular2.dart';
@ -9,15 +8,17 @@ import 'package:angular2/angular2.dart';
@Pipe(name: 'fetch', pure: false)
// #enddocregion pipe-metadata
class FetchJsonPipe extends PipeTransform {
dynamic _fetchedValue;
Future<dynamic> _fetchPromise;
dynamic _fetchedJson;
String _prevUrl;
transform(dynamic url, [List<dynamic> args]) {
if (_fetchPromise == null) {
_fetchPromise = new Future(() async {
_fetchedValue = JSON.decode(await HttpRequest.getString(url));
});
dynamic transform(dynamic url, [List<dynamic> args]) {
if (url != _prevUrl) {
_prevUrl = 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 'package:angular2/angular2.dart';
@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 {
Future<String> delayedMessage =
new Future.delayed(new Duration(milliseconds: 500), () {
return 'You are my Hero!';
});
static const _msgEventDelay = const Duration(milliseconds: 500);
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';
@Component(
selector: 'hero-birthday',
template: '''
<p>The hero's birthday is {{ birthday | date }}</p>
''')
// #docregion hero-birthday-template
template: "<p>The hero's birthday is {{ birthday | date }}</p>"
// #enddocregion hero-birthday-template
)
class HeroBirthday {
DateTime birthday = new DateTime(1988, 4, 15); // April 15, 1988
}

View File

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

View File

@ -1,19 +1,21 @@
// #docregion
import 'package:angular2/angular2.dart';
import 'fetch_json_pipe.dart';
@Component(
selector: 'hero-list',
// #docregion template
template: '''
<h4>Heroes from JSON File</h4>
<h4>Heroes from JSON File</h4>
<div *ngFor="#hero of ('heroes.json' | fetch) ">
{{hero['name']}}
</div>
<div *ngFor="#hero of ('heroes.json' | fetch) ">
{{hero['name']}}
</div>
<p>Heroes as JSON:
{{'heroes.json' | fetch | json}}
</p>
''',
<p>Heroes as JSON:
{{'heroes.json' | fetch | json}}
</p>
''',
pipes: const [FetchJsonPipe])
class HeroListComponent {}

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,13 +1,12 @@
// #docregion
import 'package:angular2/angular2.dart';
import 'exponential_strength_pipe.dart';
@Component(
selector: 'power-booster',
template: '''
<h2>Power Booster</h2>
<p>
Super power boost: {{2 | exponentialStrength: 10}}
</p>
''',
<h2>Power Booster</h2>
<p>Super power boost: {{2 | exponentialStrength: 10}}</p>
''',
pipes: const [ExponentialStrengthPipe])
class PowerBooster {}

View File

@ -10,7 +10,7 @@ dependencies:
dart_to_js_script_rewriter: ^1.0.1
transformers:
- 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'
entry_points: web/main.dart
- dart_to_js_script_rewriter

View File

@ -8,9 +8,6 @@
</head>
<body>
<h4>Hero Birthday v.1</h4>
<hero-birthday>hero-birthday loading...</hero-birthday>
<my-app>my-app loading ...</my-app>
</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");
});
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);
});
@ -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>
<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="#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="#power-booster">Power Booster custom pipe</a><br>
<a href="#power-boost-calc">Power Boost Calculator custom pipe with params</a><br>
@ -14,7 +14,7 @@
<hr>
<a id="happy-birthday1"></a>
<h2>Hero Birthday v.1</h2>
<h2>Hero Birthday v1</h2>
<hero-birthday></hero-birthday>
<hr>
@ -30,7 +30,7 @@
<hr>
<a id="happy-birthday2"></a>
<h2>Hero Birthday v.2</h2>
<h2>Hero Birthday v2</h2>
<hero-birthday2></hero-birthday2>
<hr>

View File

@ -9,7 +9,7 @@ import { Http } from '@angular/http';
})
// #enddocregion pipe-metadata
export class FetchJsonPipe implements PipeTransform{
private fetched:any = null;
private fetchedJson: any = null;
private prevUrl = '';
constructor(private _http: Http) { }
@ -17,12 +17,12 @@ export class FetchJsonPipe implements PipeTransform{
transform(url: string): any {
if (url !== this.prevUrl) {
this.prevUrl = url;
this.fetched = null;
this.fetchedJson = null;
this._http.get(url)
.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>
<!-- #enddocregion template-all-heroes -->
<!-- #enddocregion template-1 -->
</div>

View File

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

View File

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

View File

@ -1,15 +1,14 @@
// Version #2
// #docregion
import { Component } from '@angular/core'
@Component({
selector: 'hero-birthday2',
// #docregion template
// #docregion template
template: `
<p>The hero's birthday is {{ birthday | date:format }}</p>
<button (click)="toggleFormat()">Toggle Format</button>
`
// #enddocregion template
// #enddocregion template
})
// #docregion class
export class HeroBirthday2 {
@ -19,4 +18,3 @@ export class HeroBirthday2 {
get format() { return this.toggle ? 'shortDate' : 'fullDate'}
toggleFormat() { this.toggle = !this.toggle; }
}
// #enddocregion class

View File

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

View File

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

View File

@ -1,12 +1,11 @@
include ../_util-fns
extends ../../../ts/latest/guide/pipes.jade
:marked
We're working on the Dart version of this chapter.
In the meantime, please see these resources:
block includes
include ../_util-fns
* [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
block pure-change
:marked
Angular executes a *pure pipe* only when it detects a *pure change* to the input value.
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)).

View File

@ -1,23 +1,23 @@
include ../_util-fns
block includes
include ../_util-fns
:marked
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.
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.
Almost everyone prefers a simple birthday date
(<span style="font-family:courier">April 15, 1988</span>) to the original raw string format
( <span style="font-family:courier">Fri Apr 15 1988 00:00:00 GMT-0700 (Pacific Daylight Time)</span> ).
E.g., almost everyone prefers a simple birthday date like
<samp>April 15, 1988</samp> to the original raw string format
&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
desire many of the same transformations repeatedly, both within and across many applications.
We almost think of them as 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!
[Live Example](/resources/live-examples/pipes/ts/plnkr.html).
Try the #[+liveExampleLink2('live example', 'pipes')].
.l-main-section
:marked
@ -26,11 +26,14 @@ 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')(format='.')
:marked
Focus on the component's template.
+makeExample('pipes/ts/app/app.component.html', 'hero-birthday-template')(format=".")
:marked
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)
@ -39,26 +42,27 @@ include ../_util-fns
.l-main-section
:marked
## 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`.
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/#!?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)
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
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
(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.
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=".")
@ -70,16 +74,20 @@ include ../_util-fns
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:
+makeExample('pipes/ts/app/hero-birthday2.component.ts', 'template', 'app/hero-birthday2.component.ts (template)')(format=".")
: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
('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='.')
:marked
As we click the button, the displayed date alternates between
"**<span style="font-family:courier">04/15/1988</span>**" and
"**<span style="font-family:courier">Friday, April 15, 1988</span>**".
"**<samp>04/15/1988</samp>**" and
"**<samp>Friday, April 15, 1988</samp>**".
figure.image-display
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
:marked
Learn more about the `DatePipes` format options in the [API Docs](../api/common/DatePipe-class.html).
:marked
## Chaining pipes
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`
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=".")
:marked
If we pass a parameter to a filter, we have to add parentheses
to help the template compiler with the evaluation order.
The following example displays
**<span style="font-family:courier">FRIDAY, APRIL 15, 1988</span>**
This example &mdash; which displays **<samp>FRIDAY, APRIL 15, 1988</samp>** &mdash;
chains the same pipes as above, but passes in a parameter to `date` as well.
+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
:marked
## Custom Pipes
We can write our own custom pipes.
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=".")
:marked
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.
* 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
`@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
pipe name that we'll use within a template expression. It must be a valid JavaScript identifier.
* The `@Pipe` #{_decorator} allows us to define the
pipe name that we'll use within template expressions. It must be a valid JavaScript identifier.
Our pipe's name is `exponentialStrength`.
.l-sub-section
@ -155,20 +158,23 @@ 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 `pipes` array of the `@Component` decorator.
1. We must include our pipe in the `pipes` #{_array} of the `@Component` #{_decorator}.
.callout.is-helpful
header Remember the pipes array!
header Remember the pipes #{_array}!
:marked
Angular reports an error if we neglect to list our custom pipe.
We didn't list the `DatePipe` in our previous example because all
Angular built-in pipes are pre-registered.
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.
:marked
## Power Boost Calculator (extra-credit)
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
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")
.l-main-section
a(id="change-detection")
a#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.
every keystroke, mouse move, timer tick, and server response. This 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:
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.
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='.')
: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.
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 as well.
### 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
@ -218,25 +229,24 @@ a(id="change-detection")
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'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*.
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*.
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} 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 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
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.
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.
@ -244,7 +254,6 @@ figure.image-display
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.
@ -256,29 +265,31 @@ figure.image-display
## Pure and Impure Pipes
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`
impure with a flip of the switch:
impure like this:
+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.
block pure-change
:marked
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`).
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.
:marked
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.
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.
An object reference check is fast &mdash; much faster than a deep check for
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.
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.
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.
@ -305,6 +318,7 @@ figure.image-display
'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.
@ -317,19 +331,23 @@ figure.image-display
: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.
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>
### The impure *AsyncPipe*
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).
It is also stateful.
The pipe maintains a subscription to the input `Observable` and
keeps delivering values from that `Observable` as they arrive.
The pipe maintains a subscription to the input `#{_Observable}` and
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')
:marked
@ -341,12 +359,10 @@ figure.image-display
### 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.
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.
@ -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 component renders like this:
figure.image-display
img(src='/resources/images/devguide/pipes/hero-list.png' alt="Hero List")
:marked
### *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`.
@ -381,8 +400,10 @@ figure.image-display
The [JsonPipe](../api/common/JsonPipe-class.html)
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")
@ -390,7 +411,6 @@ a(id="pure-pipe-pure-fn")
### Pure pipes and pure functions
A pure pipe uses pure functions.
Pure functions process inputs and return values without detectable side-effects.
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.
So is our `ExponentialStrengthPipe`.
So is our `FlyingHeroesPipe`.
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.
@ -419,14 +438,14 @@ 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.
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.
@ -437,8 +456,8 @@ a(id="no-filter-pipe")
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")
We might sort the list by hero `name` and `planet` of origin properties something like this:
code-example(language="html")
&lt;!-- NOT REAL CODE! -->
&lt;div *ngFor="let hero of heroes | orderBy:'name,planet'">&lt;/div>
:marked