Angular 2的核心概念

讓我們來構建一個程序

組件(Component)

Angular 2的應用是由一系列的組件構成的(ui element、route..),應用始終有一個包含其他組件的根組件,換句話每個angualr2應用都有一個組件樹,這個應用程序可能是這樣的:

Application是一個根組件,Filters組件具有speaker輸入框和過濾按鈕,下面有一系列的talks,及每一個talk-cmp

// TalkCmp.ts@Component({
  selector: 'talk-cmp',
  properties: ['talk'],
  events: ['rate']})@View({
  directives: [FormattedRating, WatchButton, RateButton],
  templateUrl: 'talk_cmp.html'})class TalkCmp {
  talk: Talk;
  rate: EventEmitter;
  //...}
<!-- talk_cmp.html -->`talk`.`title`
`talk`.`speaker`<formatted-rating [rating]="talk.rating"></formatted-rating><watch-button [talk]="talk"></watch-button><rate-button [talk]="talk"></rate-button>

屬性和事件綁定

一個組件具有的屬性和事件綁定,這些內容在組件的decorator定義。

@Component({
  selector: 'talk-cmp',
  properties: ['talk'],
  events: ['rate']})...

數據通過組件上的屬性綁定流入組件,數據通過組件上的事件綁定流出組件

當你在應用程序中實例化一個組件則可以通過公共API使用屬性和事件綁定

視圖(View)

一個組件有一個視圖,描述了在頁面上怎麼呈現組件

@View({
  directives: [FormattedRating, WatchButton, RateButton],
  templateUrl: 'talk_cmp.html'})
<!-- talk_cmp.html -->`talk`.`title`
`talk`.`speaker`<formatted-rating [rating]="talk.rating"></formatted-rating><watch-button [talk]="talk"></watch-button><rate-button [talk]="talk"></rate-button>

Angular 2遵循web平臺標準,因此組件視圖元素會在Shadow DOM內創建,如果你的瀏覽器不支持Shadow DOM,Angular會模擬Shadow DOM.

一個視圖需要知道兩件事情:模板本身及模板中可使用的指令。如在上面的例子中,你可以在外部定義模板,使用templateUrl,或內聯模板template。

...javascript
@View({
  directives: [FormattedRating, WatchButton, RateButton],
  template: `


    <formatted-rating [rating]="talk.rating"></formatted-rating>
    <watch-button [talk]="talk"></watch-button>
    <rate-button [talk]="talk"></rate-button>
  `
})
...

生命週期(LIFECYCLE)

組件有一個定義良好的生命週期可以利用(onChange、onInit、onCheck、onAllChangesDone)。該TalkCmp組件不訂閱任何生命週期事件,但一些其他組件可以。例如,該組件發生改變時會觸發起change事件。

@Component({
  selector: 'cares-about-changes',
  properties: ['field1', 'field2'],
  lifecycle: [onChange]})class CareAboutChanges {
  field1;
  field2;
  onChange(changes) {
    //..
  }}

注入(INJECTABLES)

一個組件可以包含一個注入對象列表,它的子組件可能也需要注入

@Component({
  selector: 'conf-app',
  viewInjector: [ConfAppBackend, Logger]})class TalksApp {
  //...}class TalksCmp {
  constructor(backend:ConfAppBackend) {
    //...
  }}

在這個例子中,我們在根組件中聲明ConfAppBackend,和Logger,這使得它們在整個應用程序可用,TalksCmp組件注入ConfAppBackend,我將在本文的第二部分詳細討論依賴注入

HOST元素(HOST ELEMENT)

要將Angular組件渲染成DOM中的某種東西,你需要在Angular組件中結合一個DOM元素,我們稱這些叫host元素。

一個組件可以用以下方式於其host DOM元素進行交互

  • 它可以監聽其事件。

  • 它可以更新它的屬性。

  • 它可以調用它的方法。

例如,組件可以使用host事件監聽輸入,對輸入值進行處理及將其存儲在一個字段中,angular會於DOM同步已存儲的值

@Component({
  selector: 'trimmed-input',
  host: {
    '(input)': 'onChange($event.target.value)',
    '[value]': 'value'
  } })class TrimmedInput {
  value: string;
  onChange(updatedValue: string) {
    this.value = updatedValue.trim();
  }}

請注意,我真得直接與DOM交互。Angular2旨在提供一個更高層次的API,所以在原生平臺(native platform),DOM,只會反映angular應用程序的狀態。

這裏幾個原因非常有用:

  • 它使你的應用更容易看懂

  • 它允許應用程序的大多數單元測試行爲不接觸DOM,這樣的測試更容易編寫和理解。此外,它們要快得多

  • 允許angular 應用運行在web worker上

  • 它允許運行angular應用在瀏覽器之外的其他平臺上,例如利用NativeScript

有時候,你只需要直接與DOM進行交互。Angular 2提供了這樣的API,但我們的希望是,你很少會需要使用它們。

組件是自描述性(COMPONENTS ARE SELF-DESCRIBING)

我所列出的組件構成.

  • 一個組件知道如何與它的host元素交互

  • 一個組件知道如果來渲染組件,所以它們知道自己的視圖

  • 一個組件可以配置依賴注入

  • 一個組件具有定義良好的屬性和事件綁定的公共API

在Angular2中所有的這些組件都具有自描述性,所以組件的註釋(annotations)包含它們的實例所需要的所有信息。這是非常重要的。

這意味着任何組件可以引導作爲應用程序,它並不需要任何特殊的方式,此外,任何組件可以被加載到一個router-outlet(ng-view)。因此,您可以編寫一個應用程序組件,可被引導(bootstrap),加載路由(route),或直接用於其他組件,這將導致更少的API來學習。同時也讓更多的組件可重用。

指令是什麼?(WHAT ABOUT DIRECTIVES?)

如果您熟悉angular 1,你一定想知道“指令發生了什麼變化”。

其實指令還在Angular 2中,組件只是指令中的最重要的一種,但不是唯一的指令類型,一個組件是一個指令,一個視圖,你仍然可以編寫一個沒有視圖的裝飾器風格(decorator-style)的指令

依賴注入(DEPENDENCY INJECTION)

讓我們來談論Angular 的另一個重要基石,依賴注入。

依賴注入背後的想法很簡單,如果有一個依賴於一個服務的組件。您無需自己創建服務並提供給組件,相反,你可以在構造函數中申請該服務,框架將會自動提供給你該服務,通過這樣做,你可以依賴接口,而不是具體類型,這將導致更多的代碼解耦,使可測試性和其他更好的事情。

Angular 2配備了依賴注入模塊,看它如何被使用,讓我們來看看下面的組件,該指令渲染一個talks的列表

@Component({selector: 'talk-list'})@View({templateUrl: 'talks.html', directives: [NgFor]})class TalkList {
  constructor() {
    //..get the data
  }}
<!-- talks.html --><h2>Talks:</h2><div *ng-for="var t of talks"></div>

我們模擬一個簡單的服務,會提供給我們數據

class TalksAppBackend {
  fetchTalks() {
    return [
      { name: 'Are we there yet?' },
      { name: 'The value of values' }
    ];
  }}

我們如何使用這項服務?一種方法是在我們的組件創建該服務的一個實例。

class TalkList {
  constructor() {
    var backend = new TalksAppBackend();
    this.talks = backend.fetchTalks();
  }}

這是一個不錯的演示應用程序,但對於實際應用並不好,在實際應用中TalksAppBackend將不只是返回對象的數組,它會發出HTTP請求來獲取數據,這意味着在單元測試中這個組件會創建真實的http-requrest(這不是一個好主意),這個問題是由已經耦合TalkList到TalksAppBackend這一事實引起的

我們可以通過注入一個實例TalksAppBackend到構造函數解決這個問題,所以我們可以把它在測試中輕易更換,就像這樣:

class TalkList {
  constructor(backend:TalksAppBackend) {
    this.talks = backend.fetchTalks();
  }}

這告訴了angular TalksList 依賴於TalksAppBackend,現在我們需要告訴Angular如果創建依賴,我們可以爲此組件通過添加viewInjector屬性

@Component({
  selector: 'talk-list',
  viewInjector: [TalksAppBackend]})class TalkList {
  constructor(backend:TalksAppBackend) {
   this.talks = backend.fetchTalks();
  }}

該TalksAppBackend服務必須在TalkList組件或其祖先指定,所以,如果你喜歡寫用Angular 1編寫應用方式,你可以在根組件配置你的所有注射服務

@Component({
  selector: 'talk-app',
  viewInjector: [TalksAppBackend] // registered in the root component, so it can be injected into any component in the app.})class Application {}@Component({
  selector: 'talk-list'})class TalkList {
  constructor(backend:TalksAppBackend) {
   this.talks = backend.fetchTalks();
  }}

(單一API)SINGLE API

Angular 1和Angular 2 都配備了依賴注入的模塊,但在Angular 1,我們有幾個API來注入依賴到指令:有的對象是按位置(例如,元素)注入,有的按名稱,這有點混亂。Angular 2提供了注入依賴的單一的API,他們全部在組件的constrocutor中注入。

例如,此組件注入TalksAppBackend(這很可能是一個單列),和一個ElementRef,這是唯一的每個組件的一個實例。

class TalksList {
  constructor(elRef:ElementRef, backend:TalksAppBackend) {
  }}

所以我們通過相同的API將全局和局部依賴注入到組件中。此外,組件可以使用相同API注入到其他組件中

class Component {
  constructor(sibling:SiblingCmp,
              @Parent parent:ParentCmp,
              @Ancestor ancestor:AncestorCmp) {
  }}

依賴注入是你可能馬上看不到的好處之一,但當你的應用程序增長更大時,它就越重要

(屬性綁定)PROPERTY BINDINGS

Angular 使用屬性綁定與組件樹上的MODEL和DOM自動同步,要理解爲什麼這是很重要的,讓我們來看看這個應用程序。

我們知道,這個應用程序將會有一個組件樹。除了這棵樹,它還將有一個模型。我們說這是簡單的JavaScript對象,如下所示:

{
  filters: {
    speaker: "Rich Hickey",
  }
  talks: [
    {
      title: "Are we there yet?",
      speaker: "Rich Hickey",
      yourRating: null,
      avgRating: 9.0
    }
  ]}

現在,想象一下一個事件改變model。我很喜歡它的talks,我給它9.9。

{
  filters: {
    speaker: "Rich Hickey",
  }
  talks: [
    {
      title: "Are we there yet?",
      speaker: "Rich Hickey",
      yourRating: null,
      avgRating: 9.9
    }
  ]}

如果我必須找到所有可能有改變的地方並手動更新他們,那將是很繁瑣且易出錯,我想要應用程序自動來反映這一變化,這就是屬性綁定。

在Angular虛擬機一輪結束,它會檢查每個組件的組件樹。更具體地說,它會檢查每一個屬性綁定(每一個方括號,每一對大括號),並將更新組件。它還將更新DOM來匹配組件樹的狀態。

ZONES

在Angular 1 你必須使用scope.$apply來告訴框架需要檢查更新的內容,在Angular 2中你不必擔心這個問題,Angular 2使用Zone.js知道當前檢查是必須得,這意味着在使用第3方庫集成時不需要在調用scope.$apply方法


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章