AngularDart 4.0 高級-管道

每一個應用程序都以一個簡單的任務開始:獲取數據,轉換它們,並將它們展現給用戶。 獲取數據能夠像建立本地變量同樣簡單,也能夠像經過WebSocket傳輸流數據同樣複雜。javascript

一旦數據到達,您能夠將其原始的toString值直接推送到視圖中,但這不多能提供良好的用戶體驗。 例如,在大多數使用狀況下,用戶更喜歡以1988年4月15日這樣的簡單格式查看日期,而不是原始字符串格式Fri Apr 15 1988 00:00:00 GMT-0700(太平洋夏令時)。html

顯然,一些值能夠從一些編輯中受益。 您可能會注意到,您但願在許多應用程序內部和許多應用程序中重複執行許多相同的轉換。 你幾乎能夠把它們想象成風格。 事實上,您可能會喜歡將它們應用到HTML模板中,就像樣式同樣。java

介紹Angular管道,這是一種編寫顯示值轉換的方法,您能夠在HTML中聲明這些轉換。 嘗試一下實例(查看源代碼)。git

使用管道

管道將數據做爲輸入並將其轉換爲所需的輸出。 在此頁面中,您將使用管道將組件的生日屬性轉換爲人性化的日期。github

lib/src/hero_birthday1_component.dartweb

import 'package:angular/angular.dart';

@Component(
  selector: 'hero-birthday',
  template: "<p>The hero's birthday is {{ birthday | date }}</p>",
  pipes: const [COMMON_PIPES],
)
class HeroBirthdayComponent {
  DateTime birthday = new DateTime(1988, 4, 15); // April 15, 1988
}

關注組件的模板。算法

<p>The hero's birthday is {{ birthday | date }}</p>

在插值表達式中,經過管道運算符|)將組件的生日值傳遞給右側的日期管道函數。 全部管道都是這樣工做的。express

Date(日期)和Currency(貨幣)管道須要ECMAScript國際化API。 Safari和其餘舊版瀏覽器不支持它。 您可使用polyfill添加支持。json

<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=Intl.~locale.en"></script>

 

內置管道

Angular附帶一系列管道,如DatePipeUpperCasePipeLowerCasePipeCurrencyPipePercentPipe。它們均可用於任何模板。api

API參考管道主題中瞭解更多關於這些和許多其餘內置管道的信息; 過濾包含單詞「管道」的條目。

因爲本頁附錄中解釋了Angular沒有FilterPipeOrderByPipe的緣由。

參數化管道

管道能夠接受任意數量的可選參數來微調其輸出。 要向管道添加參數,請使用冒號(:)跟隨管道名稱,而後使用參數值(例如currency:"EUR")。 若是管道接受多個參數,請使用冒號分隔值(如slice:1:5

修改生日模板以給日期管道一個格式參數。 格式化英雄的生往後,它呈現爲04/15/88

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

參數值能夠是任何有效的模板表達式(請參閱模板語法頁面的模板表達式部分),例如字符串文字或組件屬性。 換句話說,您能夠經過綁定來控制格式,就像您經過綁定控制生日值同樣。

編寫第二個組件,將管道的格式參數綁定到組件的format屬性。 這是該組件的模板:

lib/src/hero_birthday2_component.dart (template)

template: '''
    <p>The hero's birthday is {{ birthday | date:format }}</p>
    <button (click)="toggleFormat()">Toggle Format</button>
  ''',

您還向模板添加了一個按鈕,並將其單擊事件綁定到組件的toggleFormat()方法。 該方法在短格式("shortDate")和較長格式("fullDate")之間切換組件的format屬性。

lib/src/hero_birthday2_component.dart (class)

class HeroBirthday2Component {
  DateTime birthday = new DateTime(1988, 4, 15); // April 15, 1988

  bool toggle = true;

  get format => toggle ? 'shortDate' : 'fullDate';

  void toggleFormat() {
    toggle = !toggle;
  }
}

當您點擊該按鈕時,顯示的日期在「04/15/1988」和「Friday, April 15, 1988」之間交替。

Date Pipe API Reference頁面閱讀有關DatePipe格式選項的更多信息。

連接管道

您能夠將管道鏈接成可能有用的組合。 在如下示例中,要以大寫形式顯示生日,生日將連接到DatePipe並鏈接到UpperCasePipe。 生日顯示爲APR 15, 1988

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

這個例子顯示了FRIDAY, APRIL 15, 1988,它連接上面的相同管道,可是還傳遞了一個參數。

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

自定義管道

您能夠編寫本身的自定義管道。 這是一個名爲ExponentialStrengthPipe的自定義管道,能夠提高英雄的力量:

lib/src/exponential_strength_pipe.dart

import 'dart:math' as math;
import 'package:angular/angular.dart';

/*
 * Raise the value exponentially
 * Takes an exponent argument that defaults to 1.
 * Usage:
 *   value | exponentialStrength:exponent
 * Example:
 *   {{ 2 |  exponentialStrength:10}}
 *   formats to: 1024
 */
@Pipe('exponentialStrength')
class ExponentialStrengthPipe extends PipeTransform {
  num transform(num value, num exponent) => math.pow(value ?? 0, exponent ?? 1);
}

這個管道定義揭示瞭如下關鍵點:

  • 管道是用@Pipe元數據註解的類。
  • 管道類實現了PipeTransform接口的transform方法,該方法接受一個輸入值,後跟一個可選參數並返回轉換後的值。
  • 對於傳遞給管道的每一個參數,transform方法都會有一個額外的參數。 你的管道有一個這樣的參數:exponent
  • 爲了告訴Angular這是一個管道,應用從主Angular庫導入的@Pipe註解。
  • @Pipe註解容許您定義將在模板表達式中使用的管道名稱。 它必須是有效的Dart標識符。 你的管道名稱是exponentialStrength

PipeTransform接口

transform方法對於管道是必不可少的。 PipeTransform接口定義該方法並指導工具和編譯器。 從技術上講,這是可選的; 不管角度如何,Angular都會查找並執行transform方法。

如今您須要一個組件來演示管道。

lib/src/power_booster_component.dart

import 'package:angular/angular.dart';
import 'exponential_strength_pipe.dart';

@Component(
    selector: 'power-booster',
    template: '''
      <h2>Power Booster</h2>
      <p>Super power boost: {{2 | exponentialStrength: 10}}</p>
    ''',
    pipes: const [ExponentialStrengthPipe])
class PowerBoosterComponent {}

請注意如下幾點:

  • 您能夠像使用內置管道同樣使用自定義管道。
  • 您必須將自定義管道包含在@Componentpipes列表中。

記住管道列表
您必須手動註冊自定義管道。 若是您不這樣作,Angular會報告錯誤。 在前面的例子中,你沒有列出DatePipe,由於全部的Angular內置管道都是預先註冊的。

要在實例中查看行爲(查看源代碼),請更改模板中的值和可選的指數。

功率提高計算器

更新模板以測試自定義管道並非頗有趣。 將示例升級到「Power Boost Calculator」,它使用ngModel將您的管道和雙向數據綁定相結合。

lib/src/power_boost_calculator_component.dart

import 'package:angular/angular.dart';
import 'package:angular_forms/angular_forms.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>
  ''',
  directives: const [CORE_DIRECTIVES, formDirectives],
  pipes: const [ExponentialStrengthPipe],
)
class PowerBoostCalculatorComponent {
  num power = 5;
  num factor = 1;
}

管道和變化檢測

Angular經過在每一個DOM事件以後運行的更改檢測過程查找數據綁定值的更改:每次擊鍵,鼠標移動,計時器滴答和服務器響應。 這多是昂貴的。 Angular努力盡量下降成本並適當。

當您使用管道時,Angular會選擇更簡單,更快速的變動檢測算法。 

不使用管道

在下一個示例中,組件使用默認的積極變化檢測策略來監控並更新其hero列表中每一個英雄的顯示。 這是模板:

lib/src/flying_heroes_component.html (v1)

New hero:
  <input type="text" #box
          (keyup.enter)="addHero(box.value); box.value=''"
          placeholder="hero name">
  <button (click)="reset()">Reset</button>
  <div *ngFor="let hero of heroes">
    {{hero.name}}
  </div>

伴隨組件類提供英雄,將英雄添加到列表中,並能夠重置列表。

lib/src/flying_heroes_component.dart (v1)

class FlyingHeroesComponent {
  List<Hero> heroes;
  bool canFly = true;
  FlyingHeroesComponent() {
    reset();
  }

  void addHero(String name) {
    name = name.trim();
    if (name.isEmpty) return;

    var hero = new Hero(name, canFly);
      heroes.add(hero);
  }

  void reset() {
    heroes = new List<Hero>.from(mockHeroes);
  }
}

你能夠添加英雄和Angular更新顯示。 若是你點擊reset按鈕,Angular用原有英雄的新列表替換heroes並更新顯示。 若是您添加了刪除或更改英雄的功能,Angular會檢測這些更改並更新顯示。

飛行英雄管道

將一個FlyingHeroesPipe添加到*ngFor迭代器,該迭代器將英雄列表過濾到只能飛行的英雄。

lib/src/flying_heroes_component.html (flyers)

<div *ngFor="let hero of (heroes | flyingHeroes)">
  {{hero.name}}
</div>

這是FlyingHeroesPipe實現,它遵循前面描述的自定義管道模式。

lib/src/flying_heroes_pipe.dart (pure)

import 'package:angular/angular.dart';
import 'heroes.dart';

@Pipe('flyingHeroes')
class FlyingHeroesPipe extends PipeTransform {
  List<Hero> transform(List<Hero> value) =>
      value.where((hero) => hero.canFly).toList();
}

請注意實例中的奇怪行爲(查看源代碼):添加飛行英雄時,它們都不會顯示在「飛翔的英雄」下。

雖然你沒有獲得你想要的行爲,但Angular並無被破壞。 它只是使用不一樣的變動檢測算法,忽略對列表或其任何項目的更改。

注意如何添加一個英雄:

heroes.add(hero);

您將英雄添加到英雄列表中。 對列表的引用沒有改變。 這是同一個列表。 這都是Angular關心的。 從它的角度來看,一樣的列表,沒有變化,沒有顯示更新。

爲了解決這個問題,建立一個新的英雄列表並將其分配給heroes。 此次Angular檢測到列表引用已經改變。 它執行管道並用新的列表更新顯示,其中包括新的飛行英雄。

若是您更改列表,則不會調用管道,而且不會更新顯示; 若是您替換列表,管道將執行並更新顯示。 Flying Heroes應用程序經過複選框開關和附加顯示擴展代碼,以幫助您體驗這些效果。

替換列表是發信號通知Angular更新顯示的有效方式。 你何時更換清單? 數據發生變化時。 在這個例子中,這是一個簡單的規則,其中更改數據的惟一方法是添加一個英雄。

更常見的狀況是,您不知道數據什麼時候發生變化,特別是在以多種方式變異數據的應用程序中,可能在遠離應用程序的位置。 這樣的應用程序中的組件一般沒法瞭解這些更改。 此外,篡改組件設計以適應管道是不明智的。 努力保持組件類獨立於HTML。 組件應該不知道管道。

爲了過濾飛行英雄,請考慮一個不純的管道。

純淨和不純的管道

有兩類管道:純淨和不純。 管道默認是純淨的。 到目前爲止,你看到的每一個管道都是純淨的。 經過將pure設置爲false,可使管道不純。 你可讓FlyingHeroesPipe不純像這樣:

@Pipe('flyingHeroes', pure: false)

在此以前,先了解純淨和不純的區別,從純淨的管道開始。

純淨的管道

僅當Angular檢測到對輸入值的純粹更改時才執行純管道。 在AngularDart中,純粹的改變僅僅來自對象引用的改變(假設全部東西都是Dart中的對象)。

Angular忽略(複合)對象內的更改。 若是您更改輸入月份,添加到輸入列表或更新輸入對象屬性,它將不會調用純管道。

這看起來頗有限制,但速度也很快。 對象引用檢查的速度比深刻檢查差別要快得多 - 因此Angular能夠快速肯定它是否能夠跳過管道執行和視圖更新。

出於這個緣由,若是您能夠接受變動檢測策略,則最好使用純淨的管道。 當你不能時,你可使用不純的管道。

或者你可能根本不使用管道。 用組件的屬性來追求管道的目的可能會更好,這點在本頁稍後會討論。

不純的管道

Angular在每一個組件更改檢測週期執行不純管道。 常常調用不純的管道,就像每次按鍵或鼠標移動同樣。

考慮到這一點,謹慎使用不純管道。 昂貴的,長期運行的管道可能會破壞用戶體驗。

不純的FlyingHeroesPipe

翻轉開關將FlyingHeroesPipe變成FlyingHeroesImpurePipe。 完整的實現以下:

lib/src/flying_heroes_pipe.dart (impure)

@Pipe('flyingHeroes', pure: false)
class FlyingHeroesImpurePipe extends FlyingHeroesPipe {}

lib/src/flying_heroes_pipe.dart (pure)

import 'package:angular/angular.dart';
import 'heroes.dart';
@Pipe('flyingHeroes')
class FlyingHeroesPipe extends PipeTransform {
  List<Hero> transform(List<Hero> value) =>
      value.where((hero) => hero.canFly).toList();
}

您從FlyingHeroesPipe繼承以證實內部沒有任何變化。 惟一的區別是管道元數據中的純標誌。

對於不純的管道來講,這是一個很好的選擇,由於轉換函數很簡單快捷。

List<Hero> transform(List<Hero> value) =>
    value.where((hero) => hero.canFly).toList();

您能夠從FlyingHeroesComponent派生FlyingHeroesImpureComponent

lib/src/flying_heroes_component.dart (impure component)

@Component(
  selector: 'flying-heroes-impure',
  templateUrl: 'flying_heroes_component.html',
  pipes: const [FlyingHeroesImpurePipe],
  directives: const [CORE_DIRECTIVES, formDirectives],
)
class FlyingHeroesImpureComponent extends FlyingHeroesComponent {
  FlyingHeroesImpureComponent() {
    title = 'Flying Heroes (impure pipe)';
  }
}

惟一的實質性變化是模板中的管道。 您能夠在實例(查看源代碼)中確認,當您添加英雄時,即便您變動heroes列表,飛行英雄也會顯示更新。

不純的AsyncPipe

Angular AsyncPipe是一個不純管道的有趣例子。 AsyncPipe接受FutureStream做爲輸入並自動訂閱輸入,最終返回發出的值。

AsyncPipe也是有狀態的。 管道保持對輸入Stream的訂閱,並在到達時保持該Stream的值。

下一個示例使用異步管道將消息字符串(messageStream綁定到視圖。

lib/src/hero_async_message_component.dart

import 'dart:async';

import 'package:angular/angular.dart';

@Component(
  selector: 'hero-message',
  template: '''
    <h2>Async Hero Message and AsyncPipe</h2>
    <p>Message: {{ message | async }}</p>
    <button (click)="resend()">Resend</button>
  ''',
  pipes: const [COMMON_PIPES],
)
class HeroAsyncMessageComponent {
  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?'
  ];
}

異步管道將樣板文件保存在組件代碼中。 該組件沒必要訂閱異步數據源,提取已解析的值並將其公開以進行綁定,而且必須在其銷燬時取消訂閱(內存泄漏的有效來源)。

不純的緩存管道

再寫一個不純的管道,一個發出HTTP請求的管道。

請記住,每隔幾毫秒就會調用不純的管道。 若是你不注意,這個管道將用請求折騰服務器。

在如下代碼中,管道只在請求URL發生更改和緩存服務器響應時調用服務器。

lib/src/fetch_json_pipe.dart

import 'dart:convert';
import 'dart:html';

import 'package:angular/angular.dart';

@Pipe('fetch', pure: false)
class FetchJsonPipe extends PipeTransform {
  dynamic _cachedData;
  String _cachedUrl;

  dynamic transform(String url) {
    if (url != _cachedUrl) {
      _cachedUrl = url;
      _cachedData = null;
      HttpRequest.getString(url).then((s) {
        _cachedData = JSON.decode(s);
      });
    }
    return _cachedData;
  }
}

如今在一個線束組件中演示它,該組件的模板定義了對這個管道的兩個綁定,都請求heroes.json文件中的heroes

lib/src/hero_list_component.dart

import 'package:angular/angular.dart';

import 'fetch_json_pipe.dart';

@Component(
    selector: 'hero-list',
    template: '''
      <h2>Heroes from JSON File</h2>

      <div *ngFor="let hero of ('heroes.json' | fetch) ">
        {{hero['name']}}
      </div>

      <p>Heroes as JSON: {{'heroes.json' | fetch | json}}</p>
    ''',
    directives: const [CORE_DIRECTIVES],
    pipes: const [COMMON_PIPES, FetchJsonPipe])
class HeroListComponent {}

該組件呈現以下:

管道的數據請求斷點顯示以下:

  • 每一個綁定都有本身的管道實例。
  • 每一個管道實例都緩存本身的URL和數據。
  • 每一個管道實例只調用一次服務器。

JsonPipe

在前面的代碼示例中,第二個提取管道綁定顯示了更多的管道連接。 它經過連接到內置的JsonPipe以JSON格式顯示相同的英雄數據。

使用JsonPipe進行調試:JsonPipe提供了一種簡單的方法來診斷離奇失敗的數據綁定,或者檢查將來綁定的對象。

純淨的管道和純粹的功能

純管道使用純功能。 純函數處理輸入並返回值,但沒有可檢測到的反作用。 給定相同的輸入,他們應該老是返回相同的輸出。

本頁前面討論的管道是用純函數實現的。 內置的DatePipe是一個純函數實現的純管道。 ExponentialStrengthPipeFlyingHeroesPipe也是如此。 回過頭來,你回顧了FlyingHeroesImpurePipe--一個純粹功能的不純管道。

老是要實現一個純函數的純管道。 不然,你會看到不少關於表達式被檢查後改變的控制檯錯誤。

下一步

管道是封裝和共享常見顯示值轉換的好方法。 像樣式同樣使用它們,將它們放入模板表達式中,以豐富視圖的吸引力和可用性。

API參考中探索Angular的內置管道庫。 嘗試編寫一個自定義管道,並可能將其貢獻給社區。

附錄:無FilterPipe或OrderByPipe

Angular不提供過濾或排序列表的管道。 熟悉Angular 1的開發人員將這些知識視爲filterorderBy。 Angular中沒有等價物。

這不是一個疏忽。 Angular不提供這樣的管道,由於它們表現不佳,而且避免操控性變弱。 filterorderBy都須要引用對象屬性的參數。 在本頁面的前面,您瞭解到這些管道必須是不純的,而且Angular在幾乎每一個變動檢測週期都會調用不純的管道。

過濾和特殊分類是昂貴的操做。 當Angular每秒鐘屢次調用這些管道方法時,即便是中等大小的列表,用戶體驗也會嚴重降級。 filterorderBy常常被濫用在Angular 1應用程序中,致使投訴Angular自己很慢。從間接的意義上說,Angular 1經過首先提供filterorderBy來準備這個性能陷阱是公平的。

若是不那麼明顯,縮小危險也是使人信服的。 想象一下,排序管道應用於英雄列表。 該列表可能按如下方式按英雄nameplanet屬性排序:

<!-- NOT REAL CODE! -->
<div *ngFor="let hero of heroes | orderBy:'name,planet'"></div>

您經過文本字符串來識別排序字段,指望管道經過索引引用屬性值(如hero ["name"])。不幸的是,主動減小操縱Hero屬性名稱讓Hero.nameHero.planet成爲Hero.aHero.b. 顯然 hero[」name「] 不起做用。

雖然有些人可能並不在乎這種積極的態度,但Angular的產品不該該阻止任何人積極貶低。 所以,Angular團隊決定Angular提供的全部內容都將安全地縮小。

Angular團隊和許多經驗豐富的Angular開發人員強烈建議將過濾和排序邏輯移植到組件自己中。 該組件能夠公開一個filteredHeroessortedHeroes屬性,並控制執行支持邏輯的時間和頻率。 您能夠在管道中放置並在應用程序中共享的任何功能均可以寫入過濾/排序服務並注入到組件中。

若是這些性能和縮小比例考慮不適用於您,您能夠隨時建立本身的這種管道(相似於FlyingHeroesPipe)或在社區中找到它們。

相關文章
相關標籤/搜索