1. 程式人生 > >Angular2學習筆記.2、繫結、ngFor和ngIf指令、MVVM分離、DisplayingData

Angular2學習筆記.2、繫結、ngFor和ngIf指令、MVVM分離、DisplayingData

本篇概述

本片為Anguar2學習筆記的第二篇文章,將會講述Angular2的資料繫結功能(僅涉及單向),ngFor、ngIf指令的使用以及如果分離MVVM。
本片內容參照官方文件DisplayingData
工程演示

工程結構

這次我們來建立一個新的工程DisplayData。
本篇筆記結束後,目錄結構應該是這個樣子,你不需要提前建立這個目錄結構,在需要的時候我會說明。
關於環境搭建和基礎目錄結構請參照第一篇筆記。

/  
|-package.json  
|-index.html  
|-node_modules/  
  |-libraries  
  |-...  
|-DisplayData/ |-index.html |-app/ |-DisplayComponent.js |-main.js |-template/ |-DisplayData.template

正文

資料繫結(僅涉及單向)

在Angular中,你可以為HTML模板繫結資料,這樣Angular就會在資料變化時自動更新UI所顯示的內容。
在這個工程中將會建立一個DisplayComponent的元件,這個元件的相關屬性會實時的顯示在模板上,例如:

DisplayComponent.myName="Alice";
<div
>
{{myName}} </div>

那麼在執行時,<div>中的內容會顯示為Alice,當Alice變為Jane時,<div>中的內容也會隨之改變。

Directive(指令)

Directive的概念在Angular 1.x就存在了,我對它的認識比較模糊,所以直接通過一些例子來說明。Angular提供的內建執行有很多,這次使用兩個:ngFor、ngIf。
先來說說Directive能實現什麼,現在我們想在一個頁面上顯示人名單,人名存在一個數組中。在PHP中我們可能要通過程式碼控制頁面的輸出,或者使用模板引擎。在Angular中同樣是很方便的,我們可以把這個遍歷輸出HTML的過程宣告式的寫入模板中。
例如:

  <ul>
    <li *ngFor="#name of names">
        {{ name }}
    </li>
  </ul>

可以看到<ul>中寫入了一個<li>元素,但是這個<li>元素中存在ngFor指令,其含義是將陣列names中的每個元素進行遍歷,根據name輸出多個<li>
其結果類似:

  <ul>
    <li>
        name1
    </li>
     <li>
        name2
    </li>
     <li>
        name3
    </li>
      <li>
        nameN
    </li>
  </ul>

其中{name}的作用如前面單向繫結中所說,顯示為變數name的內容,name依次迭代names中的元素。

ngFor

ngFOr指令為iterable(可迭代的)物件的每一個元素建立一個DOM元素。Angular會自動把你對names陣列所做的改動對映到DOM,例如新增、刪除、排序。

  <ul>
    <li *ngFor="#name of names">
        {{ name }}
    </li>
  </ul>

‘#name’:通過此變數引用可迭代物件的每一個值。
‘of names’:使用當前控制器(元件)的陣列names作為可迭代物件。

ngIf

ngIf指令通過判斷你該出的表示式從而新增或刪除DOM元素,它同樣是實時的。

  <p *ngIf="names.length > 3">
    You have many friends!
  </p>

當names的長度大於3時顯示<p>及其內容。

template、component和data的分離

MVVM(與MVC)

Angular採用MVVM架構,關於MVC、MVVM之類的探討在網路上有很多,但是大多數看完後仍然一頭霧水。我大概說一下我的看法供大家參考。
MVC架構將工程分為模型、檢視、控制器,他們彼此分離便於調整。C起到MV的粘合劑的作用,它將資料拿出來,整合,並且包含了業務邏輯。MVC架構是對稱的,C對MV的作用是同等的,它處在MV的中間。
MVVM就是Model View View-Model,顯而易見,C不見了,因為C的作用被改變(或削弱)了。在Angular中主要體現在資料的自動繫結,傳統的C不再需要操心如何將資料傳送到View中,它主要做好與Model的互動和業務邏輯就好了。如果說MVC是平衡的,現在MVVM重心向M傾斜(這是從開發做所做的工作量來看,實際上它們最後完成了同樣的工作,並沒有什麼區別)。從字面上開,我將VM解釋為“view的model”。
在一個部落格的場景中:
Model有分類、文章、評論這三個部分。
View有首頁、分類頁、文章頁(評論和文章在一起)。
View-Model根據View有首頁、分類頁、文章頁,但是他們主要由資料和業務邏輯組成,首頁VM從M拿過來所有的分類、熱門的文章(摘要而不是全文)以及熱門評論。分類頁VM從Model拿來所有分類,文章頁VM拿來某一篇文章的全文、推薦文章的摘要、文章的評論。他們還要負責諸如訪問統計、發表刪除文章及評論的業務邏輯。他們不用擔心和View的互動,因為這變得自動(透明)了。所以說我認為VM是“view的model”,外加業務邏輯。

如何分離

模板檔案

如果你不想將template直接寫在程式碼中,那麼可以將其單獨放入檔案:

(function(app){
    app.DisplayComponent = 
    ng.core.Component({
        selector:'display',
        templateUrl:'/DisplayData/app/template/DisplayData.template' 
    })
    .Class({
        constructor:function(friends){}
    });
})(window.app || (window.app = {}));

這類似於第一篇筆記中給介紹Component的內容,不同的是template變成了templateUrl。templateUrl的值為所引用模板檔案的URL。

data分離

將Model直接編碼在Controller裡是不恰當的,我們需要為Model建立一個新的類,並將它注入到控制器中:

function FriendsService() {
  this.names = ["Aarav", "Martín", "Shannon", "Ariana", "Kai"];
}
(function(app){
    app.DisplayComponent = 
    ng.core.Component({
        selector:'display',
        providers: [FriendsService],
        templateUrl:'/DisplayData/app/template/DisplayData.template' 
    })
    .Class({
        constructor:function(friends){
            this.names = friends.names;
        }
    });
    app.DisplayComponent.parameters = [[FriendsService]];
})(window.app || (window.app = {}));

這個Component(作為Controller)沒有將資料定義寫在Class中,而是放在了外面(或單獨的檔案中)。在需要建立Component例項的時候將data作為引數傳入。
此時(2016-2-19),官方文件中錯誤的將providers寫為appInjector,感謝M.Svrcek,Klas Mellbourn,Mark Rajcok

構建工程

說了這麼多終於可以用於實踐了,之前沒有建立檔案進行實際操作是因為程式碼要改來改去以說明不同的內容,這樣多個版本反而不利於理解,現在我把本文所有內容糅合在一起。
建立/DisplayData/index.html:

<html>
  <head>
    <title>Display Data</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- 1. Load libraries -->
    <!-- IE required polyfill -->
    <script src="/node_modules/es6-shim/es6-shim.min.js"></script>

    <script src="/node_modules/angular2/bundles/angular2-polyfills.js"></script>
    <script src="/node_modules/rxjs/bundles/Rx.umd.js"></script>
    <script src="/node_modules/angular2/bundles/angular2-all.umd.js"></script>

    <!-- 2. Load our 'modules' -->
    <script src='/DisplayData/app/DisplayComponent.js'></script>
    <script src='/DisplayData/app/main.js'></script>

  </head>

  <!-- 3. Display the application -->
  <body>
    <display>Loading...</display>
  </body>

</html>

建立/DisplayData/app/DisplayComponent.js:

function FriendsService() {
  this.names = ["Aarav", "Martín", "Shannon", "Ariana", "Kai"];
}
(function(app){

    app.DisplayComponent = 
    ng.core.Component({
        selector:'display',
        providers: [FriendsService],
        templateUrl:'/DisplayData/app/template/DisplayData.template' 
    })
    .Class({
        constructor:function(friends){
            this.myName="Alice";
            this.names = friends.names;
        }
    });
    app.DisplayComponent.parameters = [[FriendsService]];
})(window.app || (window.app = {}));

建立/DisplayData/app/main.js:

(function(app){
    document.addEventListener('DOMContentLoaded',function(){
        ng.platform.browser.bootstrap(app.DisplayComponent);
    });
})(window.app || (window.app = {}));

建立/DisplayData/app/template/DisplayData.template:

<p>My name: {{ myName }}</p>
<p>Friends:</p>
<ul>
    <li *ngFor="#name of names">
        {{ name }}
    </li>
</ul>
<p *ngIf="names.length > 3">You have many friends!</p>

在/下執行npm start
在這個工程中我沒有手動建立DisplayComponent的例項,Angular將在DOM和你定義的類結合的時候自動建立相關例項並將屬性和模板繫結。