AngularDart 4.0 高級-結構指令

Angular擁有強大的模板引擎,可讓咱們輕鬆操縱元素的DOM結構。css

本指南介紹Angular如何用結構指令操縱DOM,以及如何編寫本身的結構指令來完成相同的操做。html

嘗試一下實例(查看源代碼)。java

什麼是結構指令?

結構指令負責HTML佈局。 它們一般經過添加,移除或操縱元素來塑造或重塑DOM的結構。git

與其餘指令同樣,您將結構指令應用於宿主元素。 而後該指令會執行它應該對該宿主元素及其後代所作的任何操做。github

結構指令很容易識別。 在此示例中,星號(*)在指令屬性名稱前面。web

<div *ngIf="hero != null" >{{hero.name}}</div>

沒有方括號。 沒有圓括號。 只要*ngIf設置爲一個字符串。express

您將在本指南中學習到星號(*)是一種便利的符號,字符串是一種微型語法,而不是一般的模板表達式。 Angular將這個符號解析成一個圍繞宿主元素及其後代的標記<template>。 每一個結構指令都與該模板有所不一樣。api

三種常見的內置結構指令 - NgIfNgForNgSwitch ... - 在模板語法指南中進行了描述,並在整個Angular文檔中的示例中進行了介紹。 如下是模板中的示例:瀏覽器

<div *ngIf="hero != null" >{{hero.name}}</div>

<ul>
  <li *ngFor="let hero of heroes">{{hero.name}}</li>
</ul>

<div [ngSwitch]="hero?.emotion">
  <happy-hero    *ngSwitchCase="'happy'"    [hero]="hero"></happy-hero>
  <sad-hero      *ngSwitchCase="'sad'"      [hero]="hero"></sad-hero>
  <confused-hero *ngSwitchCase="'confused'" [hero]="hero"></confused-hero>
  <unknown-hero  *ngSwitchDefault           [hero]="hero"></unknown-hero>
</div>

本指南不會重複如何使用它們。 但它確實解釋了它們是如何工做的以及如何編寫本身的結構指示。app

指令拼寫

在本指南中,您將看到UpperCamelCaselowerCamelCase拼寫的指令。 你已經看到了NgIfngIf。 有一個緣由。 NgIf指向指令類; ngIf引用指令的屬性(attribute)名稱。

指令類拼寫使用UpperCamelCase(NgIf)。 指令的屬性名稱拼寫使用lowerCamelCase(ngIf)。 該指南在談論其屬性以及指令的功能時引用了指令類。 指南在描述如何將指令應用於HTML模板中的元素時引用了屬性(attribute)名稱。

還有其餘兩種Angular指令,在其餘地方被普遍描述:(1)組件和(2)屬性指令。

組件以本地HTML元素的方式管理HTML區域。 從技術上講,這是一個模板指令。

屬性指令改變元素,組件或其餘指令的外觀或行爲。 例如,內置的NgStyle指令能夠同時更改多個元素樣式。

您能夠將許多屬性指令應用於一個宿主元素。 您只能將一個結構指令應用於宿主元素。

NgIf案例研究

NgIf是最簡單的結構指令,也是最容易理解的。 它須要一個布爾表達式並使DOM的整個塊出現或消失。

<p *ngIf="true">
  Expression is true and ngIf is true.
  This paragraph is in the DOM.
</p>
<p *ngIf="false">
  Expression is false and ngIf is false.
  This paragraph is not in the DOM.
</p>

ngIf指令不會隱藏CSS元素。 它從DOM中物理添加和刪除它們。 使用瀏覽器開發人員工具確認事實,以檢查DOM。

頂部段落在DOM中。 底部,廢棄的段落不是; 取而代之的是關於「模板綁定」的評論(稍後更多)。

當條件爲false時,NgIf從DOM中刪除它的宿主元素,將它從DOM事件(它所依附的)中分離出來,將組件從Angular變化檢測中分離出來並銷燬它。 組件和DOM節點能夠被垃圾收集並釋放內存。

爲何要移除而不是隱藏?

指令能夠經過將其顯示樣式設置爲無隱藏不須要的段落。

<p [style.display]="'block'">
  Expression sets display to "block".
  This paragraph is visible.
</p>
<p [style.display]="'none'">
  Expression sets display to "none".
  This paragraph is hidden but still in the DOM.
</p>

雖然不可見,但元素仍保留在DOM中。

對於一個簡單的段落來講,隱藏和刪除之間的區別並不重要。 當宿主元素鏈接到資源密集型組件時,這很重要。 即便隱藏,這種組件的行爲也會繼續。 該組件保持鏈接到其DOM元素。 它一直在傾聽事件。 Angular不斷檢查可能會影響數據綁定的更改。 不管組件在作什麼,它都會繼續這樣作。

雖然看不見,但組件及其全部後代組件都會佔用資源。 性能和記憶負擔可能很大,響應性可能會下降,用戶什麼也看不到。

從積極的方面來講,再次顯示元素很快。 該組件的之前的狀態被保存並準備顯示。 該組件不會從新初始化 - 這種操做可能很昂貴。 因此隱藏和展現有時候是正確的。

可是若是沒有一個使人信服的理由讓他們保持身臨其境,你應該首先去除用戶看不到的DOM元素,並用像NgIf這樣的結構指令來恢復未使用的資源。

這些相同的考慮適用於每一個結構指令,不管是內置仍是定製。 在應用結構指令以前,您可能想暫停一下,以考慮添加和刪除元素以及建立和銷燬組件的後果。

星號(*)前綴

固然,你注意到了指令名稱的星號(*)前綴,並想知道爲何它是必要的以及它作了什麼。

這裏是*ngIf英雄存在,則顯示hero的名字。

<div *ngIf="hero != null" >{{hero.name}}</div>

星號是「語法糖」,由於它有點複雜。 在內部,Angular分兩個階段。 首先,它將*ngIf =「...」轉換爲模板屬性template =「ngIf ...」,就像這樣。

<div template="ngIf hero != null">{{hero.name}}</div>

而後它將模板屬性轉換爲一個模板元素,幷包裹在宿主元素上,就像這樣。

<template [ngIf]="hero != null">
  <div>{{hero.name}}</div>
</template>
  • *ngIf指令移至<template>元素,並在其中成爲屬性綁定[ngIf]。
  • 其他的<div>,包括它的class屬性,移動到<template>元素中。

這些形式都沒有實際呈現。 只有最終產品在DOM中結束。

Angular在實際渲染過程當中消耗了<template>內容,並用診斷註釋替換了<template>

NgForNgSwitch ...指令遵循相同的模式。

*ngFor內部詳解

Angular以相似的方式將*ngFor轉換爲從星號(*)語法經過模板屬性到模板元素。

這裏有一個全功能的NgFor應用程序,它有三種寫法:

<div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById"
     [class.odd]="odd">
  ({{i}}) {{hero.name}}
</div>

<div template="ngFor let hero of heroes; let i=index; let odd=odd; trackBy: trackById"
     [class.odd]="odd">
  ({{i}}) {{hero.name}}
</div>

<template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd"
          [ngForTrackBy]="trackById">
  <div [class.odd]="odd">({{i}}) {{hero.name}}</div>
</template>

這顯然比ngIf更復雜,正因如此。 NgFor指令具備比本指南中顯示的NgIf更多的功能,包括必需的和可選的。 至少NgFor須要一個循環變量(let hero)和一個列表(heroes)。

您能夠在分配給ngFor的字符串中啓用這些功能,這是您在Angular的microsyntax中編寫的。

ngFor字符串以外的全部內容仍在宿主元素(<div>)中且移動到<template>時保持不變。 在這個例子中,[ngClass] =「odd」保留在<div>上。

微語法

Angular microsyntax容許您以緊湊友好的字符串配置指令。 microsyntax解析器將該字符串轉換爲<template>上的屬性:

  • let關鍵字聲明瞭模板中引用的模板輸入變量。這個例子中的輸入變量是heroiodd。解析器將 let hero, let i let odd 轉換成變量名let-herolet-ilet-odd
  • 微觀語法分析器提取oftrackby,將首字母大寫(of -> Of, trackBy -> TrackBy),並在它們前面加上指令的屬性名稱(ngFor),產生名稱ngForOfngForTrackBy。這些是兩個NgFor輸入屬性的名稱。 這就是指令如何得知列表是heroes,而且track-by功能是trackById
  • NgFor指令遍歷列表時,它會設置並重置其本身的上下文對象的屬性。 這些屬性包括indexodd以及一個名爲$implicit的特殊屬性。
  • let-ilet-odd變量被定義爲let i = indexlet odd = odd。 Angular將它們設置爲上下文的indexodd 屬性的當前值。
  • 沒有指定let-hero的上下文屬性。 它的原意是隱含的。Angular設置let-hero爲上下文的$implicit屬性的值,NgFor已經用當前迭代的hero初始化了它的值。
  • API指南描述了額外的NgFor指令屬性和上下文屬性。

當你編寫本身的結構指令時,可使用這些微觀語法機制。 研究NgIfNgFor的源代碼是瞭解更多信息的好方法。

模板輸入變量

模板輸入變量是一個變量,其值能夠在模板的單個實例中引用。 在這個例子中有幾個這樣的變量:heroiodd。 全部前面都有關鍵字let

模板輸入變量與模板引用變量不一樣,語義和語法都不一樣。

您使用let關鍵字(let hero)聲明模板輸入變量。 變量的做用域限於重複模板的單個實例。 您能夠在其餘結構指令的定義中再次使用相同的變量名稱。

您經過在(#var)前綴加上變量名稱來聲明一個模板引用變量。 引用變量是指其附加的元素,組件或指令。 它能夠在整個模板中的任何地方訪問。

模板輸入和引用變量名稱都有其本身的名稱空間。 let hero中的hero變量永遠不會和#hero中的hero同樣。

每一個宿主元素一個結構指令

有一天你會想重複一段HTML,但只有當特定條件成立時纔會重複。 您將嘗試將*ngFor*ngIf放在同一宿主元素上。 Angular不會容許。 您僅能夠將一個結構指令應用於宿主元素。

緣由是簡單。 結構指令能夠用宿主元素及其後代完成複雜的事情。 當兩個指令聲明相同的宿主元素時,哪個優先? NgIfNgFor應該先走哪個? NgIf可否取消NgFor的效果? 若是是這樣(而且看起來應該如此),Angular應該如何歸納取消其餘結構指令的能力?

這些問題沒有簡單的答案。 禁止多項結構性指令使得它們沒有實際意義。 這個用例有一個簡單的解決方案:將*ngIf放在包裹*ngFor元素的容器元素上。 一個或兩個元素能夠是一個temple,因此你沒必要引入額外的HTML級別。

NgSwitch指令內部詳解

Angular NgSwitch其實是一組協做指令:NgSwitchNgSwitchCaseNgSwitchDefault

這是一個例子。

<div [ngSwitch]="hero?.emotion">
  <happy-hero    *ngSwitchCase="'happy'"    [hero]="hero"></happy-hero>
  <sad-hero      *ngSwitchCase="'sad'"      [hero]="hero"></sad-hero>
  <confused-hero *ngSwitchCase="'confused'" [hero]="hero"></confused-hero>
  <unknown-hero  *ngSwitchDefault           [hero]="hero"></unknown-hero>
</div>

您可能會遇到舊代碼中的NgSwitchWhen指令。 這是NgSwitchCase的棄用名稱。

分配給NgSwitch(hero.emotion)的閥值肯定顯示哪些閥(若是有)。

NgSwitch自己不是一個結構性指令。 它是一個屬性指令,用於控制其餘兩個switch指令的行爲。 這就是爲何你寫[ngSwitch],從不寫成*ngSwitch

NgSwitchCaseNgSwitchDefault是結構指令。使用星號(*)前綴表示法將它們附加到元素。當NgSwitchCase的值與switch的值匹配時,會顯示它的宿主元素。當沒有同級NgSwitchCase匹配switch的值時,NgSwitchDefault顯示它的宿主元素。

您應用指令的元素是其宿主元素.<happy-hero>*ngSwitchCase的宿主元素.<unknown-hero>*ngSwitchDefault的宿主元素。

與其餘結構指令同樣,NgSwitchCaseNgSwitchDefault能夠被解析爲模板屬性表單。

<div [ngSwitch]="hero?.emotion">
  <happy-hero    template="ngSwitchCase 'happy'"    [hero]="hero"></happy-hero>
  <sad-hero      template="ngSwitchCase 'sad'"      [hero]="hero"></sad-hero>
  <confused-hero template="ngSwitchCase 'confused'" [hero]="hero"></confused-hero>
  <unknown-hero  template="ngSwitchDefault"         [hero]="hero"></unknown-hero>
</div>

反過來,它能夠被解析到<template>元素窗體中。

<div [ngSwitch]="hero?.emotion">
  <template [ngSwitchCase]="'happy'">
    <happy-hero [hero]="hero"></happy-hero>
  </template>
  <template [ngSwitchCase]="'sad'">
    <sad-hero [hero]="hero"></sad-hero>
  </template>
  <template [ngSwitchCase]="'confused'">
    <confused-hero [hero]="hero"></confused-hero>
  </template >
  <template ngSwitchDefault>
    <unknown-hero [hero]="hero"></unknown-hero>
  </template>
</div>

首選星號(*)語法

星號(*)語法比其它脫糖形式更清晰。

雖然不多有理由在模板屬性或元素形式中應用結構指令,但瞭解Angular建立<template>並瞭解它的工做原理仍然很重要。

當你編寫本身的結構指令時,你會參考<template>

template元素

HTML 5 <template>是用於呈現HTML的方案。 它從不直接顯示。 事實上,在呈現視圖以前,Angular用註釋替換<template>及其內容。

若是沒有結構指令,而只是將一些元素包裝在<template>中,那些元素就會消失。好比短語」Hip! Hip! Hooray!」中間的「Hip」。

<p>Hip!</p>
<template>
  <p>Hip!</p>
</template>
<p>Hooray!</p>

Angular會擦掉中間的「Hip!」,讓歡呼聲不那麼熱烈。

結構指令使<template>起做用,就像您在編寫本身的結構指令時看到的同樣。

兄弟元素組

根元素一般能且應該成爲結構指令的宿主,列表元素(<li>)是NgFor迭代的典型宿主元素。

<li *ngFor="let hero of heroes">{{hero.name}}</li>

若是沒有宿主元素,一般能夠將內容包裝在本機HTML容器元素(如<div>)中,而後將該指令附加到該容器。

<div *ngIf="hero != null" >{{hero.name}}</div>

引入另外一個容器元素(一般是<span><div>)將元素組納入單個根元素一般是無害的。 一般...但不老是。

分組元素可能會破壞模板外觀,由於CSS樣式既不指望也不適應新佈局。例如,假設您有如下段落佈局。

<p>
  I turned the corner
  <span *ngIf="hero != null">
    and saw {{hero.name}}. I waved
  </span>
  and continued on my way.
</p>

您也有一個剛好適用於段落<p>內的<span>的CSS樣式規則。

p span { color: red; font-size: 70%; }

構建的段落呈現奇怪。

打算在其餘地方使用的p span樣式無心中應用於此處。

另外一個問題:一些HTML元素要求全部直系孩子屬於特定類型。 例如,<select>元素須要<option>子元素。 您不能將選項封裝在條件<div><span>中。

當你嘗試這個時,

<div>
  Pick your favorite hero
  (<label><input type="checkbox" checked (change)="showSad = !showSad">show sad</label>)
</div>
<select [(ngModel)]="hero">
  <span *ngFor="let h of heroes">
    <span *ngIf="showSad || h.emotion !== 'sad'">
      <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>
    </span>
  </span>
</select>

下拉列表是空的。

瀏覽器不會在<span>中顯示<option>

模板來解決

Angular <template>是一個分組元素,不會干擾樣式或佈局,由於Angular不會將其放入DOM中。

如下是條件段落,此次使用<template>

<p>
  I turned the corner
  <template [ngIf]="hero != null">
    and saw {{hero.name}}. I waved
  </template>
  and continued on my way. [template]
</p>

它正確渲染。 注意使用NgIf的脫糖形式。

如今有條件地用<template>排除一個選項<option>

<div>
  Pick your favorite hero 2
  (<label><input type="checkbox" checked (change)="showSad = !showSad">show sad</label>)
</div>
<select [(ngModel)]="hero">
  <template ngFor let-h [ngForOf]="heroes">
    <template [ngIf]="showSad || h.emotion !== 'sad'">
      <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>
    </template>
  </template>
</select>

下拉菜單正常工做。

<template>是Angular解析器識別的語法元素。 它不是指令,組件,類或接口。 它更像是Dart if塊中的花括號:

if (someCondition) {
  statement1;
  statement2;
  statement3;
}

若是沒有這些大括號,Dart只會在您打算有條件地將其所有做爲一個塊執行時執行第一條語句。 <template>知足Angular模板中的相似需求。

編寫一個結構指令

在本節中,您將編寫一個與NgIf相反的UnlessDirective結構指令。 NgIf在條件爲true時顯示模板內容。 UnlessDirective在條件爲false時顯示內容。

<p *myUnless="condition">Show this sentence unless the condition is true.</p>

建立指令與建立組件相似。 如下是您能夠開始的方式:

lib/src/unless_directive.dart (skeleton)

import 'package:angular/angular.dart';

@Directive(selector: '[myUnless]')
class UnlessDirective {
}

該指令的選擇器一般是指令的方括號中的屬性名稱[myUnless]。 括號定義了一個CSS屬性選擇器

指令屬性名稱應使用lowerCamelCase拼寫,並之前綴開頭。 不要使用ng。 該前綴屬於Angular。 選擇適合您或您公司的簡短內容。 在這個例子中,前綴是my

指令類名稱以Directive結尾。 Angular本身的指令不會。

TemplateRef和ViewContainerRef

像這樣一個簡單的結構指令從Angular生成的<template>中建立一個嵌入式視圖,並將該視圖插入與指令的原始<p>宿主元素相鄰的視圖容器中。

您將經過TemplateRef獲取<template>內容並經過ViewContainerRef訪問視圖容器。

你在指令構造函數中注入這兩個類做爲類的私有變量。

TemplateRef _templateRef;
ViewContainerRef _viewContainer;

UnlessDirective(this._templateRef, this._viewContainer);

myUnless屬性

指令消費者指望將真/假條件綁定到[myUnless]。 這意味着該指令須要使用@Input註解的myUnless屬性

模板語法指南中閱讀@Input

@Input()
set myUnless(bool condition) {
  if (!condition && !_hasView) {
    _viewContainer.createEmbeddedView(_templateRef);
    _hasView = true;
  } else if (condition && _hasView) {
    _viewContainer.clear();
    _hasView = false;
  }
}

只要條件的值發生變化,Angular就會設置myUnless屬性。 因爲myUnless屬性確實有效,它須要一個setter

  • 若是條件爲假而且視圖還沒有建立,請告訴視圖容器從模板建立嵌入的視圖。
  • 若是條件爲真而且當前顯示視圖,則清除且銷燬視圖的容器。

沒有人讀取myUnless屬性,所以它不須要getter

完成的指令代碼以下所示:

lib/src/unless_directive.dart (excerpt)

import 'package:angular/angular.dart';

@Directive(selector: '[myUnless]')
class UnlessDirective {
  bool _hasView = false;

  TemplateRef _templateRef;
  ViewContainerRef _viewContainer;

  UnlessDirective(this._templateRef, this._viewContainer);

  @Input()
  set myUnless(bool condition) {
    if (!condition && !_hasView) {
      _viewContainer.createEmbeddedView(_templateRef);
      _hasView = true;
    } else if (condition && _hasView) {
      _viewContainer.clear();
      _hasView = false;
    }
  }
}

將此指令添加到AppComponentdirectives列表中。

而後建立一些HTML來嘗試使用它。

<p *myUnless="condition" class="unless a">
  (A) This paragraph is displayed because the condition is false.
</p>

<p *myUnless="!condition" class="unless b">
  (B) Although the condition is true,
  this paragraph is displayed because myUnless is set to false.
</p>

當條件爲假時,出現頂部(A)段落而且底部(B)段落消失。 條件爲真時,頂部(A)段被刪除,底部(B)段出現。

概要

您能夠嘗試在實例中查看本指南的源代碼(查看源代碼)。

這是lib文件夾下的源代碼。

lib/app_component.dart

import 'package:angular/angular.dart';
import 'package:angular_forms/angular_forms.dart';
import 'package:angular_components/angular_components.dart';
import 'src/hero.dart';
import 'src/unless_directive.dart';
import 'src/hero_switch_components.dart';
@Component(
  selector: 'my-app',
  templateUrl: 'app_component.html',
  styleUrls: const ['app_component.css'],
  directives: const [
    CORE_DIRECTIVES, formDirectives,
    heroSwitchComponents,
    materialDirectives,
    UnlessDirective
  ],
  providers: const [materialProviders],
)
class AppComponent {
  final List<Hero> heroes = mockHeroes;
  Hero hero;
  bool condition = false;
  final List<String> logs = [];
  bool showSad = true;
  String status = 'ready';
  AppComponent() {
    hero = heroes[0];
  }
  num trackById(num index, Hero hero) => hero.id;
}

lib/app_component.html

<h1>Structural Directives</h1>
<p>Conditional display of hero</p>
<blockquote>
<div *ngIf="hero != null" >{{hero.name}}</div>
</blockquote>
<p>List of heroes</p>
<ul>
  <li *ngFor="let hero of heroes">{{hero.name}}</li>
</ul>
<hr>
<h2 id="ngIf">NgIf</h2>
<p *ngIf="true">
  Expression is true and ngIf is true.
  This paragraph is in the DOM.
</p>
<p *ngIf="false">
  Expression is false and ngIf is false.
  This paragraph is not in the DOM.
</p>
<p [style.display]="'block'">
  Expression sets display to "block".
  This paragraph is visible.
</p>
<p [style.display]="'none'">
  Expression sets display to "none".
  This paragraph is hidden but still in the DOM.
</p>
<h4>NgIf with template</h4>
<p>&lt;template&gt; element</p>
<template [ngIf]="hero != null">
  <div>{{hero.name}}</div>
</template>
<p>template attribute</p>
<div template="ngIf hero != null">{{hero.name}}</div>
<hr>
<a id="ng-container"></a>
<h2 id="template">&lt;template&gt;</h2>
<h4>*ngIf with a &lt;template&gt;</h4>
<button (click)="hero = hero != null ? null : heroes[0]">Toggle hero</button>
<p>
  I turned the corner
  <template [ngIf]="hero != null">
    and saw {{hero.name}}. I waved
  </template>
  and continued on my way. [template]
</p>
<!-- No ng-container yet:
<p>
  I turned the corner
  <ng-container *ngIf="hero != null">
    and saw {{hero.name}}. I waved
  </ng-container>
  and continued on my way.
</p>
-->
<p>
  I turned the corner
  <span *ngIf="hero != null">
    and saw {{hero.name}}. I waved
  </span>
  and continued on my way.
</p>
<p><i>&lt;select&gt; with &lt;span&gt;</i></p>
<div>
  Pick your favorite hero
  (<label><input type="checkbox" checked (change)="showSad = !showSad">show sad</label>)
</div>
<select [(ngModel)]="hero">
  <span *ngFor="let h of heroes">
    <span *ngIf="showSad || h.emotion !== 'sad'">
      <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>
    </span>
  </span>
</select>
<p><i>&lt;select&gt; with &lt;template&gt;</i></p>
<div>
  Pick your favorite hero 2
  (<label><input type="checkbox" checked (change)="showSad = !showSad">show sad</label>)
</div>
<select [(ngModel)]="hero">
  <template ngFor let-h [ngForOf]="heroes">
    <template [ngIf]="showSad || h.emotion !== 'sad'">
      <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>
    </template>
  </template>
</select>
<!--
<p><i>&lt;select&gt; with &lt;ng-container&gt;</i></p>
<div>
  Pick your favorite hero
  (<label><input type="checkbox" checked (change)="showSad = !showSad">show sad</label>)
</div>
<select [(ngModel)]="hero">
  <ng-container *ngFor="let h of heroes">
    <ng-container *ngIf="showSad || h.emotion !== 'sad'">
      <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>
    </ng-container>
  </ng-container>
</select>
-->
<br><br>
<hr>
<h2 id="ngFor">NgFor</h2>
<div class="box">
<p class="code">&lt;div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd"&gt;</p>
<div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById"
     [class.odd]="odd">
  ({{i}}) {{hero.name}}
</div>
<p class="code">&lt;div template="ngFor let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd"&gt;</p>
<div template="ngFor let hero of heroes; let i=index; let odd=odd; trackBy: trackById"
     [class.odd]="odd">
  ({{i}}) {{hero.name}}
</div>
<p class="code">&lt;template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById"&gt;</p>
<template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd"
          [ngForTrackBy]="trackById">
  <div [class.odd]="odd">({{i}}) {{hero.name}}</div>
</template>
</div>
<hr>
<h2 id="ngSwitch">NgSwitch</h2>
<div>Pick your favorite hero</div>
<material-radio-group [(ngModel)]="hero">
  <material-radio *ngFor="let h of heroes" [value]="h">
    {{h.name}}
  </material-radio>
  <material-radio>None of the above</material-radio>
</material-radio-group>
<h4>NgSwitch</h4>
<div [ngSwitch]="hero?.emotion">
  <happy-hero    *ngSwitchCase="'happy'"    [hero]="hero"></happy-hero>
  <sad-hero      *ngSwitchCase="'sad'"      [hero]="hero"></sad-hero>
  <confused-hero *ngSwitchCase="'confused'" [hero]="hero"></confused-hero>
  <unknown-hero  *ngSwitchDefault           [hero]="hero"></unknown-hero>
</div>
<h4>NgSwitch with <i>template</i> attribute</h4>
<div [ngSwitch]="hero?.emotion">
  <happy-hero    template="ngSwitchCase 'happy'"    [hero]="hero"></happy-hero>
  <sad-hero      template="ngSwitchCase 'sad'"      [hero]="hero"></sad-hero>
  <confused-hero template="ngSwitchCase 'confused'" [hero]="hero"></confused-hero>
  <unknown-hero  template="ngSwitchDefault"         [hero]="hero"></unknown-hero>
</div>
<h4>NgSwitch with &lt;template&gt;</h4>
<div [ngSwitch]="hero?.emotion">
  <template [ngSwitchCase]="'happy'">
    <happy-hero [hero]="hero"></happy-hero>
  </template>
  <template [ngSwitchCase]="'sad'">
    <sad-hero [hero]="hero"></sad-hero>
  </template>
  <template [ngSwitchCase]="'confused'">
    <confused-hero [hero]="hero"></confused-hero>
  </template >
  <template ngSwitchDefault>
    <unknown-hero [hero]="hero"></unknown-hero>
  </template>
</div>
<hr>
<h2>&lt;template&gt;</h2>
<p>Hip!</p>
<template>
  <p>Hip!</p>
</template>
<p>Hooray!</p>
<hr>
<h2 id="myUnless">UnlessDirective</h2>
<p>
  The condition is currently
  <span [ngClass]="{ a: !condition, b: condition, unless: true }">{{condition}}</span>.
  <button
    (click)="condition = !condition"
    [ngClass] = "{ a: condition, b: !condition }" >
    Toggle condition to {{condition ? 'false' : 'true'}}
  </button>
</p>
<p *myUnless="condition" class="unless a">
  (A) This paragraph is displayed because the condition is false.
</p>
<p *myUnless="!condition" class="unless b">
  (B) Although the condition is true,
  this paragraph is displayed because myUnless is set to false.
</p>
<h4>UnlessDirective with template</h4>
<p *myUnless="condition">Show this sentence unless the condition is true.</p>
<p template="myUnless condition" class="code unless">
  (A) &lt;p template="myUnless condition" class="code unless"&gt;
</p>
<template [myUnless]="condition">
  <p class="code unless">
    (A) &lt;template [myUnless]="condition"&gt;
  </p>
</template>

lib/app_component.css

button {
  min-width: 100px;
  font-size: 100%;
}
.box {
  border: 1px solid gray;
  max-width: 600px;
  padding: 4px;
}
.choices {
  font-style: italic;
}
code, .code {
  background-color: #eee;
  color: black;
  font-family: Courier, sans-serif;
  font-size: 85%;
}
div.code {
  width: 400px;
}
.heroic {
  font-size: 150%;
  font-weight: bold;
}
hr {
  margin: 40px 0
}
.odd {
  background-color:  palegoldenrod;
}
td, th {
  text-align: left;
  vertical-align: top;
}
p span { color: red; font-size: 70%; }
.unless {
  border: 2px solid;
  padding: 6px;
}
p.unless {
  width: 500px;
}
button.a, span.a, .unless.a {
  color: red;
  border-color: gold;
  background-color: yellow;
  font-size: 100%;
}
button.b, span.b, .unless.b {
  color: black;
  border-color: green;
  background-color: lightgreen;
  font-size: 100%;
}

lib/src/hero.dart

class Hero {
  final int id;
  String name;
  /*@nullable*/ String emotion;
  Hero(this.id, this.name, [this.emotion]);
  @override
  String toString() => '$name';
}
final List<Hero> mockHeroes = <Hero>[
  new Hero(1, 'Mr. Nice', 'happy'),
  new Hero(2, 'Narco', 'sad'),
  new Hero(3, 'Windstorm', 'confused'),
  new Hero(4, 'Magneta')
];

lib/src/hero_switch_components.dart

import 'package:angular/angular.dart';
import 'hero.dart';
@Component(
  selector: 'happy-hero',
  template: 'Wow. You like {{hero.name}}. What a happy hero ... just like you.',
)
class HappyHeroComponent {
  @Input()
  Hero hero;
}
@Component(
  selector: 'sad-hero',
  template: 'You like {{hero.name}}? Such a sad hero. Are you sad too?',
)
class SadHeroComponent {
  @Input()
  Hero hero;
}
@Component(
  selector: 'confused-hero',
  template: 'Are you as confused as {{hero.name}}?',
)
class ConfusedHeroComponent {
  @Input()
  Hero hero;
}
@Component(
  selector: 'unknown-hero',
  template: '{{message}}',
)
class UnknownHeroComponent {
  @Input()
  Hero hero;
  String get message => hero != null && hero.name.isNotEmpty
      ? '${hero.name} is strange and mysterious.'
      : 'Are you feeling indecisive?';
}
const List heroSwitchComponents = const [
  HappyHeroComponent,
  SadHeroComponent,
  ConfusedHeroComponent,
  UnknownHeroComponent
];

lib/src/unless_directive.dart

import 'package:angular/angular.dart';
/// Add the template content to the DOM unless the condition is true.
///
///  If the expression assigned to `myUnless` evaluates to a truthy value
///  then the templated elements are removed removed from the DOM,
///  the templated elements are (re)inserted into the DOM.
///
///  <div *ngUnless="errorCount" class="success">
///    Congrats! Everything is great!
///  </div>
///
///  ### Syntax
///
///  - `<div *myUnless="condition">...</div>`
///  - `<div template="myUnless condition">...</div>`
///  - `<template [myUnless]="condition"><div>...</div></template>`
@Directive(selector: '[myUnless]')
class UnlessDirective {
  bool _hasView = false;
  TemplateRef _templateRef;
  ViewContainerRef _viewContainer;
  UnlessDirective(this._templateRef, this._viewContainer);
  @Input()
  set myUnless(bool condition) {
    if (!condition && !_hasView) {
      _viewContainer.createEmbeddedView(_templateRef);
      _hasView = true;
    } else if (condition && _hasView) {
      _viewContainer.clear();
      _hasView = false;
    }
  }
}

你學到了

相關文章
相關標籤/搜索