From ad95b04e69e6fa138999cdc058fe90df100b7a76 Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Fri, 13 May 2016 13:44:14 -0700 Subject: [PATCH] docs(dev guide): pipes - new Dart prose, update Dart and Ts code (#1353) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit + 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 --- public/_includes/_util-fns.jade | 4 + .../_examples/pipes/dart/example-config.json | 0 .../pipes/dart/lib/app_component.dart | 12 +- .../pipes/dart/lib/app_component.html | 105 +++++++--- .../dart/lib/exponential_strength_pipe.dart | 14 +- .../pipes/dart/lib/fetch_json_pipe.dart | 21 +- .../dart/lib/flying_heroes_component.dart | 66 +++++++ .../dart/lib/flying_heroes_component.html | 38 ++++ .../pipes/dart/lib/flying_heroes_pipe.dart | 19 ++ .../lib/hero_async_message_component.dart | 30 ++- .../dart/lib/hero_birthday1_component.dart | 8 +- .../dart/lib/hero_birthday2_component.dart | 11 +- .../pipes/dart/lib/hero_list_component.dart | 18 +- .../docs/_examples/pipes/dart/lib/heroes.dart | 15 ++ .../dart/lib/power_boost_calculator.dart | 21 -- .../lib/power_boost_calculator_component.dart | 19 ++ ...ster.dart => power_booster_component.dart} | 9 +- public/docs/_examples/pipes/dart/pubspec.yaml | 2 +- .../docs/_examples/pipes/dart/web/index.html | 3 - public/docs/_examples/pipes/e2e-spec.js | 8 +- .../_examples/pipes/ts/app/app.component.html | 8 +- .../_examples/pipes/ts/app/fetch-json.pipe.ts | 8 +- .../pipes/ts/app/flying-heroes.component.html | 1 - .../ts/app/hero-async-message.component.ts | 5 - .../pipes/ts/app/hero-birthday1.component.ts | 2 - .../pipes/ts/app/hero-birthday2.component.ts | 6 +- .../pipes/ts/app/power-booster.component.ts | 4 +- public/docs/dart/latest/_util-fns.jade | 2 + public/docs/dart/latest/guide/pipes.jade | 17 +- public/docs/ts/latest/guide/pipes.jade | 183 ++++++++++-------- 30 files changed, 444 insertions(+), 215 deletions(-) create mode 100644 public/docs/_examples/pipes/dart/example-config.json create mode 100644 public/docs/_examples/pipes/dart/lib/flying_heroes_component.dart create mode 100644 public/docs/_examples/pipes/dart/lib/flying_heroes_component.html create mode 100644 public/docs/_examples/pipes/dart/lib/flying_heroes_pipe.dart create mode 100644 public/docs/_examples/pipes/dart/lib/heroes.dart delete mode 100644 public/docs/_examples/pipes/dart/lib/power_boost_calculator.dart create mode 100644 public/docs/_examples/pipes/dart/lib/power_boost_calculator_component.dart rename public/docs/_examples/pipes/dart/lib/{power_booster.dart => power_booster_component.dart} (64%) diff --git a/public/_includes/_util-fns.jade b/public/_includes/_util-fns.jade index b3078fb838..66416bb8e1 100644 --- a/public/_includes/_util-fns.jade +++ b/public/_includes/_util-fns.jade @@ -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 = ''; diff --git a/public/docs/_examples/pipes/dart/example-config.json b/public/docs/_examples/pipes/dart/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/pipes/dart/lib/app_component.dart b/public/docs/_examples/pipes/dart/lib/app_component.dart index 1edd92c0f4..85b32279a2 100644 --- a/public/docs/_examples/pipes/dart/lib/app_component.dart +++ b/public/docs/_examples/pipes/dart/lib/app_component.dart @@ -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 diff --git a/public/docs/_examples/pipes/dart/lib/app_component.html b/public/docs/_examples/pipes/dart/lib/app_component.html index feed0f1908..a27d587fcd 100644 --- a/public/docs/_examples/pipes/dart/lib/app_component.html +++ b/public/docs/_examples/pipes/dart/lib/app_component.html @@ -1,36 +1,83 @@ + +

Pipes

+Happy Birthday v1
+Birthday DatePipe
+Happy Birthday v2
+Birthday Pipe Chaining
+Power Booster custom pipe
+Power Boost Calculator custom pipe with params
+Flying Heroes filter pipe (pure)
+Flying Heroes filter pipe (impure)
+Async Hero Message and AsyncPipe
+Hero List with caching FetchJsonPipe
+ +
+ +

Hero Birthday v1

+ + +
+ +

Birthday DatePipe

+ +

The hero's birthday is {{ birthday | date }}

+ + + +

The hero's birthday is {{ birthday | date:"MM/dd/yy" }}

+ + +
+ +

Hero Birthday v2

+ + +
+ +

Birthday Pipe Chaining

+

+ + The chained hero's birthday is + {{ birthday | date | uppercase}} + +

+ +

+ + The chained hero's birthday is + {{ birthday | date:'fullDate' | uppercase}} + +

+

+ + The chained hero's birthday is + {{ ( birthday | date:'fullDate' ) | uppercase}} + +

+
+ + + +
+ +loading + +
+ + + +
+ + + +
+
+ -
-

The hero's birthday is {{ birthday | date }}

- -

The hero's birthday is {{ birthday | date:"MM/dd/yy" }}

- -
-

Hero Birthday v.2

-loading... -
- - -

- The chained hero's birthday is - {{ birthday | date | uppercase}} -

- -

- The chained hero's birthday is - {{ birthday | date:'fullDate' | uppercase}} -

-

- The chained hero's birthday is - {{ ( birthday | date:'fullDate' ) | uppercase}} -

-
-loading... - -
-loading .. +
diff --git a/public/docs/_examples/pipes/dart/lib/exponential_strength_pipe.dart b/public/docs/_examples/pipes/dart/lib/exponential_strength_pipe.dart index 6e047a77b8..1e66b9c791 100644 --- a/public/docs/_examples/pipes/dart/lib/exponential_strength_pipe.dart +++ b/public/docs/_examples/pipes/dart/lib/exponential_strength_pipe.dart @@ -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 args]) { - var v = int.parse(value.toString(), onError: (source) => 0); - var p = args.isEmpty + num transform(dynamic _value, [List 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); } } diff --git a/public/docs/_examples/pipes/dart/lib/fetch_json_pipe.dart b/public/docs/_examples/pipes/dart/lib/fetch_json_pipe.dart index 748f515f2a..4ff5eab8d9 100644 --- a/public/docs/_examples/pipes/dart/lib/fetch_json_pipe.dart +++ b/public/docs/_examples/pipes/dart/lib/fetch_json_pipe.dart @@ -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 _fetchPromise; + dynamic _fetchedJson; + String _prevUrl; - transform(dynamic url, [List args]) { - if (_fetchPromise == null) { - _fetchPromise = new Future(() async { - _fetchedValue = JSON.decode(await HttpRequest.getString(url)); - }); + dynamic transform(dynamic url, [List args]) { + if (url != _prevUrl) { + _prevUrl = url; + _fetchedJson = null; + HttpRequest.getString(url).then((s) { + _fetchedJson = JSON.decode(s); + }); } - return _fetchedValue; + return _fetchedJson; } } diff --git a/public/docs/_examples/pipes/dart/lib/flying_heroes_component.dart b/public/docs/_examples/pipes/dart/lib/flying_heroes_component.dart new file mode 100644 index 0000000000..040bc6ae37 --- /dev/null +++ b/public/docs/_examples/pipes/dart/lib/flying_heroes_component.dart @@ -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 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.from(heroes)..add(hero); + // #enddocregion concat + } + // #docregion v1 + } + + void reset() { + heroes = new List.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 diff --git a/public/docs/_examples/pipes/dart/lib/flying_heroes_component.html b/public/docs/_examples/pipes/dart/lib/flying_heroes_component.html new file mode 100644 index 0000000000..5d2a5d8a40 --- /dev/null +++ b/public/docs/_examples/pipes/dart/lib/flying_heroes_component.html @@ -0,0 +1,38 @@ + + +

{{title}}

+

+ +New hero: + + + can fly +

+

+ Mutate array + + + +

+ +

Heroes who fly (piped)

+
+ +
+ {{hero.name}} +
+ +
+ +

All Heroes (no pipe)

+
+ + +
+ {{hero.name}} +
+ + +
diff --git a/public/docs/_examples/pipes/dart/lib/flying_heroes_pipe.dart b/public/docs/_examples/pipes/dart/lib/flying_heroes_pipe.dart new file mode 100644 index 0000000000..fe20fe1ea3 --- /dev/null +++ b/public/docs/_examples/pipes/dart/lib/flying_heroes_pipe.dart @@ -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 transform(dynamic value, [List 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 {} diff --git a/public/docs/_examples/pipes/dart/lib/hero_async_message_component.dart b/public/docs/_examples/pipes/dart/lib/hero_async_message_component.dart index 52d1851a86..c3ab0dee94 100644 --- a/public/docs/_examples/pipes/dart/lib/hero_async_message_component.dart +++ b/public/docs/_examples/pipes/dart/lib/hero_async_message_component.dart @@ -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: ''' +

Async Hero Message and AsyncPipe

+

Message: {{ message | async }}

+ + ''') class HeroAsyncMessageComponent { - Future delayedMessage = - new Future.delayed(new Duration(milliseconds: 500), () { - return 'You are my Hero!'; - }); + static const _msgEventDelay = const Duration(milliseconds: 500); + + Stream message; + + HeroAsyncMessageComponent() { + resend(); + } + + void resend() { + message = + new Stream.periodic(_msgEventDelay, (i) => _msgs[i]).take(_msgs.length); + } + + List _msgs = [ + 'You are my hero!', + 'You are the best hero!', + 'Will you be my hero?' + ]; } diff --git a/public/docs/_examples/pipes/dart/lib/hero_birthday1_component.dart b/public/docs/_examples/pipes/dart/lib/hero_birthday1_component.dart index 4b8f3a33f2..accb756c37 100644 --- a/public/docs/_examples/pipes/dart/lib/hero_birthday1_component.dart +++ b/public/docs/_examples/pipes/dart/lib/hero_birthday1_component.dart @@ -1,10 +1,12 @@ +// #docregion import 'package:angular2/angular2.dart'; @Component( selector: 'hero-birthday', - template: ''' -

The hero's birthday is {{ birthday | date }}

- ''') + // #docregion hero-birthday-template + template: "

The hero's birthday is {{ birthday | date }}

" + // #enddocregion hero-birthday-template + ) class HeroBirthday { DateTime birthday = new DateTime(1988, 4, 15); // April 15, 1988 } diff --git a/public/docs/_examples/pipes/dart/lib/hero_birthday2_component.dart b/public/docs/_examples/pipes/dart/lib/hero_birthday2_component.dart index 032888080e..eb76d84859 100644 --- a/public/docs/_examples/pipes/dart/lib/hero_birthday2_component.dart +++ b/public/docs/_examples/pipes/dart/lib/hero_birthday2_component.dart @@ -1,12 +1,17 @@ +// #docregion import 'package:angular2/angular2.dart'; @Component( - selector: 'hero-birthday', + selector: 'hero-birthday2', + // #docregion template template: '''

The hero's birthday is {{ birthday | date:format }}

- ''') -class HeroBirthday { + ''' + // #enddocregion template + ) +// #docregion class +class HeroBirthday2 { DateTime birthday = new DateTime(1988, 4, 15); // April 15, 1988 bool toggle = true; diff --git a/public/docs/_examples/pipes/dart/lib/hero_list_component.dart b/public/docs/_examples/pipes/dart/lib/hero_list_component.dart index 50ae1a5bf1..6088fa547f 100644 --- a/public/docs/_examples/pipes/dart/lib/hero_list_component.dart +++ b/public/docs/_examples/pipes/dart/lib/hero_list_component.dart @@ -1,19 +1,21 @@ +// #docregion import 'package:angular2/angular2.dart'; import 'fetch_json_pipe.dart'; @Component( selector: 'hero-list', + // #docregion template template: ''' -

Heroes from JSON File

+

Heroes from JSON File

-
- {{hero['name']}} -
+
+ {{hero['name']}} +
-

Heroes as JSON: - {{'heroes.json' | fetch | json}} -

-''', +

Heroes as JSON: + {{'heroes.json' | fetch | json}} +

+ ''', pipes: const [FetchJsonPipe]) class HeroListComponent {} diff --git a/public/docs/_examples/pipes/dart/lib/heroes.dart b/public/docs/_examples/pipes/dart/lib/heroes.dart new file mode 100644 index 0000000000..2c06ca83cc --- /dev/null +++ b/public/docs/_examples/pipes/dart/lib/heroes.dart @@ -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 mockHeroes = const [ + const Hero("Windstorm", true), + const Hero("Bombasto", false), + const Hero("Magneto", false), + const Hero("Tornado", true), +]; diff --git a/public/docs/_examples/pipes/dart/lib/power_boost_calculator.dart b/public/docs/_examples/pipes/dart/lib/power_boost_calculator.dart deleted file mode 100644 index 1d7f9558cd..0000000000 --- a/public/docs/_examples/pipes/dart/lib/power_boost_calculator.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:angular2/angular2.dart'; - -import 'exponential_strength_pipe.dart'; - -@Component( - selector: 'power-boost-calculator', - template: ''' -

Power Boost Calculator

-
Normal power:
-
Boost factor:
-

- Super Hero Power: {{power | exponentialStrength: factor}} -

-''', - 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'; -} diff --git a/public/docs/_examples/pipes/dart/lib/power_boost_calculator_component.dart b/public/docs/_examples/pipes/dart/lib/power_boost_calculator_component.dart new file mode 100644 index 0000000000..7c726ce511 --- /dev/null +++ b/public/docs/_examples/pipes/dart/lib/power_boost_calculator_component.dart @@ -0,0 +1,19 @@ +// #docregion +import 'package:angular2/angular2.dart'; +import 'exponential_strength_pipe.dart'; + +@Component( + selector: 'power-boost-calculator', + template: ''' +

Power Boost Calculator

+
Normal power:
+
Boost factor:
+

+ Super Hero Power: {{power | exponentialStrength: factor}} +

+ ''', + pipes: const [ExponentialStrengthPipe]) +class PowerBoostCalculator { + num power = 5; + num factor = 1; +} diff --git a/public/docs/_examples/pipes/dart/lib/power_booster.dart b/public/docs/_examples/pipes/dart/lib/power_booster_component.dart similarity index 64% rename from public/docs/_examples/pipes/dart/lib/power_booster.dart rename to public/docs/_examples/pipes/dart/lib/power_booster_component.dart index 194abe1b0c..9152a2cc52 100644 --- a/public/docs/_examples/pipes/dart/lib/power_booster.dart +++ b/public/docs/_examples/pipes/dart/lib/power_booster_component.dart @@ -1,13 +1,12 @@ +// #docregion import 'package:angular2/angular2.dart'; import 'exponential_strength_pipe.dart'; @Component( selector: 'power-booster', template: ''' -

Power Booster

-

- Super power boost: {{2 | exponentialStrength: 10}} -

-''', +

Power Booster

+

Super power boost: {{2 | exponentialStrength: 10}}

+ ''', pipes: const [ExponentialStrengthPipe]) class PowerBooster {} diff --git a/public/docs/_examples/pipes/dart/pubspec.yaml b/public/docs/_examples/pipes/dart/pubspec.yaml index 848c11cd1c..ff1fc42531 100644 --- a/public/docs/_examples/pipes/dart/pubspec.yaml +++ b/public/docs/_examples/pipes/dart/pubspec.yaml @@ -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 diff --git a/public/docs/_examples/pipes/dart/web/index.html b/public/docs/_examples/pipes/dart/web/index.html index 483b9537ba..01bdf05322 100644 --- a/public/docs/_examples/pipes/dart/web/index.html +++ b/public/docs/_examples/pipes/dart/web/index.html @@ -8,9 +8,6 @@ -

Hero Birthday v.1

- hero-birthday loading... - my-app loading ... diff --git a/public/docs/_examples/pipes/e2e-spec.js b/public/docs/_examples/pipes/e2e-spec.js index 4f07ce7137..ae8327dd0d 100644 --- a/public/docs/_examples/pipes/e2e-spec.js +++ b/public/docs/_examples/pipes/e2e-spec.js @@ -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'); + }); + }); diff --git a/public/docs/_examples/pipes/ts/app/app.component.html b/public/docs/_examples/pipes/ts/app/app.component.html index 83856e4e91..a27d587fcd 100644 --- a/public/docs/_examples/pipes/ts/app/app.component.html +++ b/public/docs/_examples/pipes/ts/app/app.component.html @@ -1,8 +1,8 @@

Pipes

-Happy Birthday v.1
+Happy Birthday v1
Birthday DatePipe
-Happy Birthday v.2
+Happy Birthday v2
Birthday Pipe Chaining
Power Booster custom pipe
Power Boost Calculator custom pipe with params
@@ -14,7 +14,7 @@
-

Hero Birthday v.1

+

Hero Birthday v1


@@ -30,7 +30,7 @@
-

Hero Birthday v.2

+

Hero Birthday v2


diff --git a/public/docs/_examples/pipes/ts/app/fetch-json.pipe.ts b/public/docs/_examples/pipes/ts/app/fetch-json.pipe.ts index 5a5de3707e..72fd58e178 100644 --- a/public/docs/_examples/pipes/ts/app/fetch-json.pipe.ts +++ b/public/docs/_examples/pipes/ts/app/fetch-json.pipe.ts @@ -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; } } diff --git a/public/docs/_examples/pipes/ts/app/flying-heroes.component.html b/public/docs/_examples/pipes/ts/app/flying-heroes.component.html index c004987503..93e635b662 100644 --- a/public/docs/_examples/pipes/ts/app/flying-heroes.component.html +++ b/public/docs/_examples/pipes/ts/app/flying-heroes.component.html @@ -35,5 +35,4 @@ New hero: - diff --git a/public/docs/_examples/pipes/ts/app/hero-async-message.component.ts b/public/docs/_examples/pipes/ts/app/hero-async-message.component.ts index 6975d0d6d2..cced9f3e57 100644 --- a/public/docs/_examples/pipes/ts/app/hero-async-message.component.ts +++ b/public/docs/_examples/pipes/ts/app/hero-async-message.component.ts @@ -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: `

Async Hero Message and AsyncPipe

-

Message: {{ message$ | async }}

- `, }) export class HeroAsyncMessageComponent { diff --git a/public/docs/_examples/pipes/ts/app/hero-birthday1.component.ts b/public/docs/_examples/pipes/ts/app/hero-birthday1.component.ts index 7e998a33e6..52a462757b 100644 --- a/public/docs/_examples/pipes/ts/app/hero-birthday1.component.ts +++ b/public/docs/_examples/pipes/ts/app/hero-birthday1.component.ts @@ -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 diff --git a/public/docs/_examples/pipes/ts/app/hero-birthday2.component.ts b/public/docs/_examples/pipes/ts/app/hero-birthday2.component.ts index 04fad5e26a..683f082e27 100644 --- a/public/docs/_examples/pipes/ts/app/hero-birthday2.component.ts +++ b/public/docs/_examples/pipes/ts/app/hero-birthday2.component.ts @@ -1,15 +1,14 @@ -// Version #2 // #docregion import { Component } from '@angular/core' @Component({ selector: 'hero-birthday2', -// #docregion template + // #docregion template template: `

The hero's birthday is {{ birthday | date:format }}

` -// #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 diff --git a/public/docs/_examples/pipes/ts/app/power-booster.component.ts b/public/docs/_examples/pipes/ts/app/power-booster.component.ts index df753bb572..78c6f9c127 100644 --- a/public/docs/_examples/pipes/ts/app/power-booster.component.ts +++ b/public/docs/_examples/pipes/ts/app/power-booster.component.ts @@ -7,9 +7,7 @@ import { ExponentialStrengthPipe } from './exponential-strength.pipe'; selector: 'power-booster', template: `

Power Booster

-

- Super power boost: {{2 | exponentialStrength: 10}} -

+

Super power boost: {{2 | exponentialStrength: 10}}

`, pipes: [ExponentialStrengthPipe] }) diff --git a/public/docs/dart/latest/_util-fns.jade b/public/docs/dart/latest/_util-fns.jade index ffc0ae6fc0..04deed6425 100644 --- a/public/docs/dart/latest/_util-fns.jade +++ b/public/docs/dart/latest/_util-fns.jade @@ -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 diff --git a/public/docs/dart/latest/guide/pipes.jade b/public/docs/dart/latest/guide/pipes.jade index 7348505632..ae4e3aeead 100644 --- a/public/docs/dart/latest/guide/pipes.jade +++ b/public/docs/dart/latest/guide/pipes.jade @@ -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)). diff --git a/public/docs/ts/latest/guide/pipes.jade b/public/docs/ts/latest/guide/pipes.jade index 9ade0d8f51..4fa76296bc 100644 --- a/public/docs/ts/latest/guide/pipes.jade +++ b/public/docs/ts/latest/guide/pipes.jade @@ -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 - (April 15, 1988) to the original raw string format - ( Fri Apr 15 1988 00:00:00 GMT-0700 (Pacific Daylight Time) ). + E.g., almost everyone prefers a simple birthday date like + April 15, 1988 to the original raw string format + — Fri Apr 15 1988 00:00:00 GMT-0700 (Pacific Daylight Time). 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 **04/15/88**. + After formatting the hero's April 15th birthday, it should render as **04/15/88**. +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 - "**04/15/1988**" and - "**Friday, April 15, 1988**". + "**04/15/1988**" and + "**Friday, April 15, 1988**". 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 - **APR 15, 1988** + **APR 15, 1988** +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 - **FRIDAY, APRIL 15, 1988** + This example — which displays **FRIDAY, APRIL 15, 1988** — + 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 — much faster than a deep check for + differences — 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 ### 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` — *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") <!-- NOT REAL CODE! --> <div *ngFor="let hero of heroes | orderBy:'name,planet'"></div> :marked