文章出處

對Angular 2.0的策略有疑問嗎?就在這里提吧。在接下來的這篇文章里,我會解釋Angular 2.0的主要特性區域,以及每個變化背后的動機。每個部分之后,我將提供自己在設計過程中的意見和見解,包括我認為仍然需要改進設計的重要部分。

注意:本文所反映是2014年11月6日的狀態記錄。如果你在較長時間之后讀到此文,請檢查一下我設計上是否有所變更。

AngularJS 1.3

在開始討論Angular的未來之前,我們先花點時間看看當前的版本。AngularJS 1.3是迄今為止最優的Angular版本,它是幾周之前發布的。它提供了大量的bug修復,功能增強和性能提升。如果你正在使用Angular,會有升級的愿望。如果將要開始用Angular做新的項目,這也會是你想要使用的版本。這是一個強大而成熟的框架,已經擺在我們面前了。

評注

可能你現在會對AngularJS的未來有很多疑問。什么時候2.0會出來?1.x怎么辦?會有從1.x到2.0的升級路徑嗎?AngularJS團隊在回答這些問題上,可以做得更好一些,你應當鼓勵他們這么做。我可以告訴你們的是,在Google內部,有1600個應用是基于Angular 1.2或者1.3構建的。所以,看起來Google對當前版本是會有很大興趣的,也會需要支持它們一段時間。在ngEurope的Q&A環節中,Brad Green說在Angular 2.0的RTM版本發布之后,對Angular 1.3的支持會持續至少1.5-2年。我們也剛針對Angular 1.3的支持作了一些團隊結構和領導的變更,即使是正在為Angular 2.0而努力,我們仍然保持了一個專有團隊全職處理Angular 1.3。這個團隊是由Pete Bacon Darwin領導的,我敢肯定你一定知道他在AngularJS上的豐富經驗。我想要鼓勵你們向Angular的引領者詢問這些變化,并且一起設法完善官方的支持。

當2.0可用時,如果有人想要把Angular1.x的應用遷移到2.0,目前也沒有什么可行的計劃。我認為我們可以在這一塊做些事。如果這對你來說也很重要,請大聲說出來,當然要友善一點,但要讓Angular團隊知道這對你而言很重要,他們應當對此有所考慮,并且也有所規劃。

Angular 2.0的動機

那么,你可能會想知道,為什么要做Angular 2.0呢?為什么一步跨到2.0,并且作了這么多不兼容變更?這一切都是很隨意的嗎?我能夠處理少量變更,但我所聽到的消息,在2.0中有很多較大的變更,它們真的合理嗎?值得嗎?

在深入特性細節之前,我很樂意花點時間來探討一些較高層次的動機,關于2.0所帶來的變化。我希望這能夠對后續細節建立一個基本的認識,在此基礎上可以作一些有意義的批評(其中有些我打算自己提供)。

性能

差不多五年前,當AngularJS剛創建出來的時候,它并不是給開發人員用的。它是一個工具,更傾向于給需要快速創建持久化HTML表單的設計人員用。隨著時間推移,它作了改變以適應各種場景,開發人員也用它建造更多、更復雜的應用程序。Angular 1.x團隊多年來一直努力增量化地改進設計,允許它適應現代Web應用程序需求的變更。然而,在所能做到的改進上,是有很大局限的,根源在于原始設計中的一些潛規則。很多這種限制,導致了當前的綁定與模板基礎架構的性能問題。為了解決這些問題,需要新的策略。

變化的Web

從最初設想Angular所開始的五年中,Web有了明顯的改變。比如說,5年前沒有jQuery之類框架的幫助,是基本不可能建立一個合適的跨瀏覽器網站的。但是,當今的瀏覽器DOM實現不僅更加一致,而且這些實現更快了,也提供了與應用程序框架相關的新特性。

而且web還在繼續變化……

雖然在過去幾年中,發生了巨大的變化,但與未來1-3年相比,這些變化還是顯得微不足道。在幾個月內,ES6規范將定稿。如果我們覺得在2015年就能看到完全實現此規范的瀏覽器,并非不可能。今天的瀏覽器已經支持其中一些特性了,并且正在實現其他剩余部分。這意味著瀏覽器支持像module、class、lambda、generator之類東西。這些特性從根本上改變JavaScript的編程體驗。但是,大的變化并不是只體現在JavaScript上,Web Components也噴薄欲出。術語Web Components通常是指四個相關的W3C規范:

  • 自定義元素,允許通過自定義標簽來擴展HTML
  • HTML Imports,允許對各種資源的打包(HTML,CSS,JS等等)
  • 模板元素,允許在一個文檔中包含inert HTML
  • Shadow DOM,允許對DOM和CSS的封裝

通過組合這四種能力,web開發人員可以創建聲明式的組件(自定義元素),并且是完全封裝的(Shadow DOM)。這些組件可以描述它們自己的視圖(模板元素),并且能很容易打包發布給其他開發人員(HTML Imports)。當這些規范在所有主流瀏覽器都可用的時候,我們就可能會看到開發人員的創造力爆發,作很多努力來創建可復用的組件,以解決常見問題,或者是彌補標準的HTML工具集所存在的不足(摘自Web Components與數據綁定)。今天,這已經變得可能,在Chrome和其他瀏覽器里,這些標準中有些已經實現,有些正在實現。未來顯得很美好,對不對?只剩下一個問題了:當今的多數數據綁定框架尚未準備好應對這些。多數框架,包括Angular 1.x,包含一個數據綁定系統,它構建在一小部分已知的HTML元素和常用事件、行為的基礎上。為了能讓Angular開發人員享有Web Components,很需要有一個全新的數據綁定實現。

移動端

想想5年前……噢,計算的情景已經有了多么大的改變!現在到處都是手機和平板了!雖然Angular可以被用于創建移動應用,但它的理念并非為它們設計的。這包括了所有的東西,從我剛提到過的基本的性能問題,到它的路由的能力缺失,以及不能緩存預編譯視圖,甚至是過于普通的觸摸支持。其中有些東西可以借助Angular 1.3來實現(比如說路由),但其余的需要根本的變更來修復。

易用性

老實說……AngularJS不是太容易學。是的,你選擇了它,內心想著“這太美好,很簡單,很魔幻!!!”然后開始建立自己的應用,發覺變成“TMD這什么啊!!??我不懂!!!”這種事我聽得多了,甚至還有個直觀的圖用來描述它。如此種種,還是回頭看看這個庫最初的設計意圖吧。比如,最開始是沒有自定義指令的,它們都是硬編碼的,然后,就有了一個用于添加指令的API。最開始是沒有控制器的,然后……你懂的。這種綁死的特性,很多成為了現在的核心理念,導致了API的不優雅。如果Angular真想變的易學易用,那么,從一開始,它就必須對自己的核心特性有清晰的認識。對一個框架而言,如果把指令和控制器當作初始設計的一部分,肯定要比后面逐步拼湊起來要好幾個數量級。

小結

了解了Angular的設計起源,以及Web和通用計算情景的逐步變化,很明顯需要作一些變更了。事實上,如果不開始解決這些問題,Angular很可能在一年內就有被淘汰的風險。一個框架,如果沒法跟Web Components協作,在移動端上一塌糊涂,還繼續推進自己的非標準的module和class API,離死也不遠了。Angular團隊對這些問題的回答是一個新版本:Angular 2.0。它本質上是為了現代Web而對AngularJS的重新想象,并且融合過去五年所得到的各種認識。

評注

盡管我尚未涉及詳細的部分,你已經可以發現AngularJS 2.0是與1.x大為不同的了。可能有人會問,這還是不是同一個框架了?我覺得這是個好問題。我之前提到,我認為Angular團隊需要提供對1.x支持的具體時間表,到2.0的遷移路徑,以及給企業一些指引,供當前決策或者是想要升級為2.0的計劃用。對于充滿技術思維的Angular團隊來說,這可能是很無趣的任務,但我認為它們對社區而言,是有必要的,有幫助的,也是一種尊重。

Angular 2.0的特性與設計

現在,你已經對創建Angular 2.0的動機有了一點相關背景了,我們來看看一些關鍵的特性區域。

AtScript

AtScript是一門語言,它是ES6的超集,被用來編寫Angular 2.0。它使用TypeScript的類型語法來表達可選類型,這可以用來做運行時的類型推斷,而不是編譯時的檢測。它也使用了元數據注解來擴展語言。這里有一個示例,有些AtScript代碼就長這樣:

import {Component} from 'angular';
import {Server} from './server';

@Component({selector: 'foo'})
export class MyComponent {
  constructor(server:Server) {
      this.server = server;
  }
}

在這里,我們在基線ES6代碼上添加了一些AtScript附屬物。示例頂部的import語句和class語法是直接從ES6里來的,沒什么特別的,但是,看一下構造函數,注意server參數指定了一個類型。在AtScript中,這個類型是用于生成運行時類型推斷的,引用也會存在于已知的位置,這樣,一個框架,比如說依賴注入框架,可以定位到類型信息,并且使用它。也注意一下class定義上面的@Component語法,這是一個元數據注解。組件是一個普通的類,跟其他的一樣。當你使用注解來裝飾一個東西的時候,編譯器會生成代碼,初始化注解,并且儲存在一個具體的位置,這樣它可以被像Angular這樣的框架訪問到。考慮到這一點,這就是上面的代碼轉譯成ES6語法之后的結果:

import * as rtts from 'rtts';
import {Component} from 'angular';
import {Server} from './server';

export class MyComponent {
  constructor(server) {
      rtts.types(server, Server);
      this.server = server;
  }
}

MyComponent.parameters = [{is:Server}];
MyComponent.annotate = [
  new Component({selector: 'foo'})
];

RTTS的意思是運行時類型系統(RunTime Type System),這是一個小型的關于運行時類型檢測的推斷庫。在此,編譯器插入一些代碼,以便把server變量推斷為類型Server。你也可以編寫自定義的類型推斷以使用結構化類型,或者使用臨時的類型規則。當部署到生產的時候,編譯器可以省略這些推斷以提高性能。

一個好的事情是,與類型推斷相獨立,類型注解和元數據注解是能夠被跟蹤到的。所有這些注解會被翻譯成非常簡單的ES5兼容數據結構,存儲在MyComponent函數自身上。這使得任意框架或者庫都能很容易發現這些元數據并且使用它們。多年來,這已經在像.NET和Java這樣的平臺上被證明是很方便的工具。它也和Ruby的元編程功能有一些相似之處。實際上,當跟一個庫組合的時候,注解可以被用于做元編程,Angular 2.0就是借此簡化指令的創建的。稍后將進行更詳細的討論。

評注

我個人是喜歡AtScript的,但我已經是一個TypeScript的愛好者了,所以你可能會說我是有前提條件的。我知道有些開發人員抵制在JavaScript中加入類型,我沒法責怪他們。我自己已經在類型系統上有過相當廣泛的經驗了,有些是好的,有些不好。AtScript有個有意思的地方,你可以只把類型語法當成向其他庫提供元數據的某種簡單方式,而完全不用它來作類型檢測。我覺得AtScript最強大的特性之一是,在運行時擁有類型和元數據信息,可供框架利用,或者是在自己的元編程中使用。如果其他的解釋型語言也加上這種特性的話,我是不會感到驚訝的。

也就是說,我持有保留意見。

我很樂于看到AtScript變得更正式些,我意思是說,我認為它應當從Angular團隊自身中釋放出來,它應當有自己的發言權,Angular作為它的一個重要客戶。應當至少有幾個開發人員全職圍繞AtScript工作,實現功能,修復bug,提升代碼生成,構建工具等等,同時也應當有一個長期支持計劃。當一個開發人員或者團隊選擇一種語言來編寫他們應用的時候,他們所作出的是一種重大投資。我樂于看到Google能夠為了未來,在AtScript上作出相當的投資。

關于AtScript,還有另外個問題,是跟Dart相關的。Dart是Google開發的另一種語言。它跟某種簡單的解釋性語言有所不同,是因為它有自己的運行時和基礎類庫。結果就是,Dart擁有自己的API,用于DOM處理,集合,事件,正則表達式等等。這些API在它們自己的領域中都很優秀,但跟已有的JavaScript代碼不兼容。由于這種阻抗不匹配,Dart和外界的任何通訊都必須通過一個特殊的編組API來完成。所以,雖然從技術上可以調用現有的JavaScript庫,一般來說不太實用。對AngularJS來說,性能上的損耗將是不可接受的。所以,Google創建了Angular Dart,一種用Dart重新思考過的AngularJS版本。

問題解決了……好吧,可能沒有。

現在,就有了兩個Angular的版本,要在里面修改bug,實現新特性,發布,等等,使用不同的語言編寫,由不同的團隊維護。所以,解決了一個問題,卻帶來了更多問題。

現在你可能有疑問了:這跟AtScript有什么關系呢?

Angular 2.0的想法是把Angular和Angular Dart統一起來。一個團隊在一個代碼庫上工作,要比兩個團隊在兩個代碼庫上工作好多了。AtScript能在這個事情上起作用,因為它是在Traceur上面實現的,這個東西可擴展性很好。所以,Angular團隊能夠用AtScript編譯出JavaScript和Dart兩個版本。

太棒了!那么,問題在哪里呢?

記得我提到過Dart在DOM之類的東西上有不同的對象模型,這些東西就不是簡單轉譯代碼所能解決的了。因此,Angular 2.0的構建過程實際上就會復雜一些了。當開發Angular的時候,必須創建不同的門面(facade)以屏蔽JavaScript和Dart之間的API差異。然后編譯器使用對應的門面來編譯成每種指定的語言。這個事情在技術上肯定是令人印象深刻了,但是,卻大大提高了想要轉向Angular 2.0的準入門檻。值得注意的是,這方面的發展還處于試驗階段,這個問題可能會有其他的解決方案。我知道你們中的很多人已經轉向了Angular,并且很珍惜這種經驗。Angular團隊也很珍惜它們,我們正在深度思考如何去改進這些,不過,到目前為止,還不是很理想。

注意:Angular 2.0是使用AtScript編寫的,但這并不意味著你就需要用AtScript編寫你的應用,或者為了使用Angular 2.0,要學AtScript的什么東西。你可以很輕松地使用TypeScript,ES6,ES5,CoffeeScript……隨便什么喜歡的東西來寫。目前來說,如果利用AtScript的話,能夠獲得最佳的Angular體驗,因為它能夠從語言原語自動生成元數據,不過,最終它還是會翻譯成簡單的ES5。最終的ES5在概念上某種程度類似于Angular 1.x里面的DDO對象,但在此情況下,它是被生成給任意JavaScript函數使用的,而不是某種指令相關的技術,需要用特殊的注冊API來編寫。

依賴注入

Angular 1.x的核心特性之一是依賴注入(DI,Dependency Injection)。通過DI,你可以很容易地在軟件開發過程中遵循“分而治之”的實踐。復雜的問題可以根據其角色和職責進行概念化,然后表示成對象,共同協作以完成最終目標。使用這種方式解構的大型(或者小型)系統可以通過使用DI框架在運行時進行組裝。這種系統通常是容易測試的,因為結果的設計更加模塊化,也允許了更容易的組件隔離。當然,這一切在Angular 1.x中都是可以的,不過有一些問題。

困擾1.x DI實現的第一個問題是由壓縮(minification)引起的。鑒于DI依賴于從函數解析參數名,本質上是把它們當作字符串令牌,而在壓縮過程中,這些名稱會被改變,就不再匹配于注冊的服務、控制器和其他組件了。結果就是應用掛了。為了使得DI對壓縮友好,添加了一個API,但它缺乏原始的優雅。在.NET和Java的世界中,先進的服務端DI框架里存在更多特性,1.x的實現主要就缺乏這些東西。欠缺的特性導致開發人員受到約束,兩個大的例子是:生命周期/作用域的控制,以及子注射器。

注解

通過AtScript,我們引入了一種廣義的將元數據附加到任意函數的機制。同時,AtScript元數據格式是不怕壓縮的,也容易使用ES5手工編寫。這使得它能夠出色地支撐一個DI庫,提供其所需要用于構造對象實例的信息。不必見怪,這就是新DI的運作方式。

當DI需要實例化一個類(或者調用一個函數)的時候,會檢測一下,看看它上面有沒有帶附屬的元數據。回顧一下上面從AtScript轉譯出來的代碼:

MyComponent.parameters = [{is:Server}];

如果新DI發現了parameters值,會用它來判斷將要嘗試調用的函數的依賴項。在本例中,它可以得知僅有一個類型為Server的參數。所以它會獲取一個Server的實例,并且在調用這個函數之前傳進去。你也可以顯式提供一個特定的Inject注解給DI用,這會覆蓋parameter數據。如果你在使用一種不能自動生成parameter元數據的語言,也很容易支持,下面就是用原生ES5代碼寫的樣子:

MyComponent.annotate = [new Inject(Server)];

這個的運行時效果跟parameter數據是一樣的。值得注意的是,你可以使用任意東西當作注入令牌,所以可以這樣:

MyComponent.annotate = [new Inject('my-string-token')];

只要你在DI上配置過能映射到'my-string-token'的東西,它就能運行。也就是說,推薦的使用方式是通過構造函數的實例,正如我之前所有的例子所示。

實例作用域

在Angular 1.x中,DI容器中的所有實例都是單例。在Angular 2.0中,默認也是這樣。為了獲得不同的行為,你需要使用Service,Provider,Constant等等。那都挺容易讓人迷惑的。幸運的是,新DI擁有一個新的,更通用,更強大的特性。它現在有實例作用域控制了。所以,如果你希望每次請求的時候,DI都創建一個類的新實例,可以這么做:

@TransientScope
export class MyClass { ... }

當你組合子注射器來創建自己的作用域標識符的時候,這會更加強大……

子注射器

子注射器是一個主要的新特性。子注射器從其父項那里繼承到所有父項的服務,但能夠在子級別上覆蓋它們。當它與作用域標識符組合使用的時候,你可以很輕松地在系統中調用到特定類型的對象,這些對象應當在不同作用域中被覆蓋,這非常強大。新的路由有一個“子路由”的功能,就是使用它的一個例子。在內部,每個子路由創建自己的子注射器,這使得路由的每個部分能夠從父路由繼承服務,或者在不同的導航場景中覆蓋這些服務。

注意:自定義作用域和子注射器會被認為是對注射器的中高級用法。我不希望太多的應用代碼用到它。但是,既然它在Angular的內部被使用到了,如果你需要類似功能,也可以用。

更多……

在新DI中,還有一些其他特性,比如provider(自定義函數,用于提供一個注入值),懶注入(指定你所期望注入的東西,但又不立即需要,稍后才要),還有基于promise的異步注入(注入一個promise,可以從中獲取異步的依賴項)。

評注

從個人角度,我非常喜歡新DI。我又有偏見了,因為我用DI好多年了,在我創建的其他UI框架中,它也是核心組件。新DI在Angular 2.0中扮演了很重要的角色,像子注射器等功能帶來了巨大的變更。現在這個功能有了,它能夠被模板引擎和路由利用,這兩者都有創建作用域和隔離不同服務的需求。

然后我們就來到了一個從Angular中移除的重要特性:$scope。不過,雖然$scope自身被移除了,它的有些特性還在。這些特性被作為此設計的一個部分,重新換了個位置,也有所提升。你可能會被$scope的缺失搞得措手不及,但新的設計既簡化了Angular內部的東西,也簡化了提供給你,開發人員的東西。我提到這些,是因為DI的有些新功能,比如說子注射器,與$scope中之前的一些功能重疊了。在這個情況下,我認為新DI系統拿出來的是一個更好的解決方案。它更加通用,所以不但能解決Angular的內部需求,還給你們開放了很多種可能性。

不幸的是,玫瑰帶著刺。我們來討論下一些其他問題。在Angular 1.x中,還有個相關功能我沒有提到:module,你可能想知道它的位置在哪。Angular 2.0的方案是吸收ES6中關于module的標準。在Angular的之前版本中,處理模塊的方式是Angular特有的方式。五年前,當Angular剛開始構思時,并沒有用于完成此事的標準方式。今天,事情不同了,已經有了一個明確路徑。這當然是一種不兼容升級,如果有人要作遷移的話,需要對代碼重新作點調整。要作這么一種不兼容變更,是很惡心,但這就是Web的變化影響框架的一個實例,如果這事現在不解決,2.0將面臨被邊緣化的風險。

關于DI,還有另外一個坑,特別是如果你在用ES5寫代碼的話。Angular 2.0依托作為元數據的注解,支持了基于類的設計。類和注解的語法在ES5中并不太好,事實上,ES5壓根就沒這些語法。你可以使用原型之類來表示一切,但就沒有AtScript甚至ES6或者TypeScript那么清晰了,它們可是有類和靜態的類成員的。我想知道能不能為不準備遷移到ES6的開發人員做點什么,也許是一個簡單的可選降級庫,給出一種簡單的方式來創建帶元數據的類?可能會類似于Angular 1.x中的DDO對象,但是更通用,這樣能創建任意的類和元數據。我很想聽聽你對這個想法的意見,或者其他可能會解決ES5開發問題或對遷移能有所提升的主意。

模板與數據綁定

如果你已經看了這么多,一定屬于對Angular 2.0非常好奇的,感謝花了這么多時間。我們還有一條路要走,現在我們要進入真正有意思的地方了:模板和綁定。我打算把它們放在一起討論,雖然從技術上看,數據綁定系統是與模板系統分離的,你在編寫應用的時候卻會感覺它們是一個整體。所以,我覺得把它們拿到一起來說會比較好。

我們先從理解視圖到屏幕的顯示過程,然后一點一點地看。本質上,你是從一段HTML片段開始的,這會存在于一個<template>元素中。這個HTML片段被傳遞給模板編譯器,編譯器遍歷模板,辨識任意的指令,綁定表達式,事件處理函數等等。所有這些數據從DOM自身中提取,放到最終用于初始化模板的數據結構里。作為這個階段的一部分,在數據上作了一些處理工作,比如說解析綁定表達式。每個包含上面這種特殊指令節點會被打上一個特殊的class。這一過程的結果會被緩存,這樣才不至于需要重復這些工作。我們把這種結果稱為一個ProtoView。一旦我們有了ProtoView,就可以用它來創建View。當一個ProtoView生成了View,所有剛才辨識出的指令就會初始化,并且附加到它們的DOM節點上,綁定表達式上建立了監控,事件處理器也配置好了。明白了吧。在編譯階段,之前處理過的數據結構能夠讓我們很快地做這些事。一旦你得到一個View了,就可以把它添加到一個ViewPort中,并且顯示出來。一個ViewPort表達了屏幕的一個區域,可以在其中顯示View。作為一名開發人員,大部分東西你是看不到的,你寫模板就好了,它會運行的。可我還是希望在深入細節之前,在一個較高層次上把這些過程羅列出來。

動態加載

Angular 1.x所缺乏的重大功能之一是代碼的動態加載。如果你想在運行中添加新的指令或者控制器,非常困難,或者就做不到。它沒有被支持。在2.0中,我們從開始設計東西的時候,就把異步放在心里。所以,當你開始編譯一個模板的時候,實際上它是個異步過程。

現在我需要詳細討論上面一筆帶過的模板編譯了。當你編譯一個模板的時候,你并不僅僅為編譯器提供了一個模板,也同時提供了一個Component的定義。我們稍微深入一點。在模板中使用的時候,Component的定義就包含了什么指令啊,過濾器啊之類的元數據。這確保了在模板被編譯器處理之前,所有必要的依賴項都已加載。由于我們的代碼架設在ES6 module規范的基礎上,只需簡單地在Component定義中引用依賴項,如果他們尚未加載,module加載器就會加載它們。因此,通過這種結合ES6 module的方式,我們不費事就得到了各種東西的動態加載。

指令

在我們深入模板的語法之前,需要先看一看指令——Angular用于擴展HTML自身的方式。在Angular 1.x中,使用指令定義對象(DDO,Directive Definition Object)來創建指令。這好像是很多Angular開發人員巨大痛苦的來源之一。

如果我們能把指令弄簡單點,會怎樣呢?

我們已經討論過模塊、類和注解了,如果我們能用這些核心建筑來構建指令會怎樣呢?好吧,我們當然就是這么干的。

在Angular 2.0中,有三種指令類型。

  • 組件指令(Component Directive)創建一個組合了View和Controller的自定義組件,你可以把它當成一個自定義HTML元素。路由也可以映射到組件。
  • 裝飾指令(Decorator Directive)使用附加行為來裝飾一個已有的HTML元素,一個經典的例子是ng-show。
  • 模板指令(Template Directive)把HTML轉換成一個可復用的模板。指令的作者可以控制模板何時、怎樣初始化,并且插入DOM中。示例包括ng-if和ng-repeat。

你可能聽說過在Angular 2.0里面,Controller沒了。好吧,不完全正確。其實,Controller成為了我們稱之為Component的一部分。Component擁有一個View和一個Controller。View就是你的HTML模板,Controller就是你的JavaScript行為。不像在1.x中那樣,要用顯式的或者非標準的API來注冊控制器,在2.0中,只需創建一個普通的帶一些注解的類。這里是選項卡容器組件的控制器的一個部分(稍后會看到它的視圖):

@ComponentDirective({
    selector:'tab-container',
    directives:[NgRepeat]
})
export class TabContainer {
    constructor(panes:Query<Pane>) {
        this.panes = panes;
    }

    select(selectedPane:Pane) { ... }
}

這里有幾個特性值得注意。

首先,組件的控制器只是一個類。它的構造函數會被自動注入其依賴項。因為使用了子注射器,它可以獲得沿DOM樹向上所有服務的訪問,還包括從屬于自己元素的本地服務。比如說,這里它就被注入了一個Query,這是一個特殊的集合,會自動跟子Pane元素保持同步,讓你獲知何時出現新增或者移除。同時,你也被注入了Element自身,這能讓你處理與Angular 1.x中$link回調相同的邏輯,但卻是通過類構造函數,用一種更一致的方式來處理的。

現在,看一看@ComponentDirective注解吧。它把類標識為一個Component,并且提供了編譯器所需用于掛接的元數據。比如說,selector:'tab-container'是一個CSS選擇器,會被用于匹配HTML。任何匹配這個選擇器的元素都會被轉換成一個TabContainer。同時,directives:[NgRepeat]表明了這個組件的模板的依賴項。到現在還沒給你們看過,馬上講語法的時候就會看到了。

一個重要的需要注意的細節是,模板將會直接綁定到這個類上,意味著類的任何屬性和方法都能直接在模板上訪問。這根Angular 1.2中的“controller as”語法很相似。在類和模板之間,不再有$scope了。結果就是Angular內部得到了簡化,開發人員也得到了更簡單的語法,那種在$scope對象上搞來搞去的事情變少了。

接下來,我們來看看Decorator Directive。一個簡單的NgShow是怎樣的呢?

@DecoratorDirective({
    selector:'[ng-show]',
    bind: { 'ngShow': 'ngShow' },
    observe: {'ngShow': 'ngShowChanged'}
})
export class NgShow {
    constructor(element:Element) {
        this.element = element;
    }

    ngShowChanged(newValue){
        if(newValue){
            this.element.style.display = 'block';
        }else{
            this.element.style.display = 'none';
        }
    }
}

這里,我們可以看到指令的更多方面。我們又寫了個帶注解的類,構造函數注入了裝飾器要附加到的HTML元素。因為有DecoratorDirective,編譯器知道這是一個裝飾器,也知道把它添加到任意匹配于selector:'[ng-show]' CSS選擇器的元素上。

在這個注解上,還有其他一些奇怪的屬性。

bind: { 'ngShow': 'ngShow' }用于把類屬性映射到HTML attribute。不是所有的類屬性都直接暴露成HTML的attribute的,如果你想要讓屬性在HTML中可綁定,需要在bind元數據中指定它。observe: {'ngShow': 'ngShowChanged'}告訴綁定系統,你想要在每次ngShow屬性變更的時候得到通知,并且使用ngShowChanged方法作為回調。注意ngShowChanged回調響應變更的方式是,改變附加到的HTML元素的display。(注意,這只是個非常幼稚的實現,僅作演示之用)。

好了,那Template Directive長什么樣呢?為什么不看看NgIf呢?

@TemplateDirective({
    selector: '[ng-if]',
    bind: {'ngIf': 'ngIf'},
    observe: {'ngIf': 'ngIfChanged'}
})
export class NgIf {
    constructor(viewFactory:BoundViewFactory, viewPort:ViewPort) {
        this.viewFactory = viewFactory;
        this.viewPort = viewPort;
        this.view = null;
    }

    ngIfChanged(value) {
        if (!value && this.view) {
            this.view.remove();
            this.view = null;
        }

        if (value) {
            this.view = this.viewFactory.createView();
            this.view.appendTo(this.viewPort);
        }
    }
}

希望你能理解TemplateDirective注解。它注冊了這個指令,并且提供了一些必要的元數據用于設置屬性和觀測,就像NgShow示例那樣。這就是一個TemplateDirective,它能夠訪問一些特殊的服務,這些服務可以被注入它的構造函數。第一個是ViewFactory,之前我提到,Template Directive把它附加到的HTML轉換為模板,模板被自動編譯,然后你就能在模板指令中訪問視圖工廠了。調用工廠的createView API會初始化模板自身。你也可以訪問ViewPort,這代表了模板從DOM中提取的位置,你可以用它在DOM上添加或者移除模板的實例。注意ngIfChanged回調是怎樣響應變更,初始化模板,添加到viewport,或者從viewport上移除的。如果你在實現類似于NgRepeat的東西,可以把模板實例化很多次,甚至給createView API提供一個指定的數據項,然后可以把多個實例添加到viewport上。基本就是這樣。

現在,你已經看到三種類型指令的一些典型例子了。我希望這能夠大致說明了如何使用新的行為來擴展HTML編譯器。

不過,還有一個重要的東西我尚未充分解釋:Controllers。

怎樣為應用創建一個控制器呢?設想你要建立一個路由,導航到一個控制器,然后顯示它的視圖。怎么做到這個呢?簡單的回答就是使用一個Component Directive來做。

在Angular 1.x中,Directive和Controller是兩種不同的東西,API不同,功能也不同。在Angular 2.0中,既然我們已經去掉了DDO,把Directive變成了基于類的,我們可以把Directive和Controller統一成Component模型。所以,現在可以一箭雙雕,當建立路由的時候,只要把路由映射到一個ComponentDirective(本質上由一個視圖和控制器構成,就像之前一樣)。

所以呢,如果你創建一個假想的客戶編輯控制器,可能會是這樣:

@ComponentDirective
export class CustomerEditController {
    constructor(server:Server) {
        this.server = server;
        this.customer = null;
    }

    activate(customerId) {
        return this.server.loadCustomer(customerId)
            .then(response => this.customer = response.customer);
    }
}

真沒什么新東西,我們就是在注入假想的服務端服務,當被路由激活的時候,使用它加載客戶。有意思的是,你不需要使用選擇器或者是其他任何元數據,因為這個組件不是被當作自定義元素來使用的。它是被路由動態創建,然后動態渲染到DOM中的。總之,不要太在意細節了。

那么,如果你明白了怎樣創建ComponentDirective,你就明白了怎樣創建等同于Angular 1.x中使用路由創建的控制器。在Angular 1.x中,很難把這些統一起來,但鑒于我們在Angular 2.0中有了這么帥的類和元數據驅動系統,指令就可以很顯著地簡化了,用這種方式創建你的“控制器”也變得很容易了。

注意:我想指出,上面的指令代碼示例基于早期的原型代碼和較新的設計文檔規范,它們應當被解讀為一種解釋的工具,而不是指令的準確語法,那東西還在不斷變化。模板編譯器和綁定語言現在是Angular 2.0中最不穩定的部分,設計的變更非常頻繁。

模板語法

至此,你已經對編譯過程有了一個概要的認識了:知道它可以異步加載代碼,如何編寫指令,它們是怎樣裝配的,控制器怎么適應這些東西的。但我們尚未看一下真正的模板。我們現在來看看剛才假想的TabContainer的模板吧。為了方便起見,把指令代碼再貼一遍:

@ComponentDirective({
    selector:'tab-container',
    directives:[NgRepeat]
})
export class TabContainer {
    constructor(panes:Query<Pane>) {
        this.panes = panes;
    }

    select(selectedPane:Pane) { ... }
}
<template>
    <div class="border">
        <div class="tabs">
            <div [ng-repeat|pane]="panes" class="tab" (^click)="select(pane)">
                <img [src]="pane.icon"><span>${pane.name}</span>
            </div>
        </div>
        <content></content>
    </div>
</template>

當你看到這個語法的時候,不要害怕。是啊,這是符合規范的HTML,但不是我們最后的綁定語法。不過,還是用它作例子吧,這樣我們能有個比較豐富的討論的起點。

理解數據綁定語法的關鍵是屬性定義的左側,考慮到這點,我們先來看一下image標簽。

<img [src]="pane.icon"><span>${pane.name}</span>

當你看到一個屬性名稱被[]包圍的時候,它意思是右側的屬性值有一個綁定表達式。

當你看到一個表達式被${}包圍的時候,它意思是這是一個表達式,應當被當作字符串插入到內容中(這跟ES6用來做字符串插值的語法相同)。

從模型/控制器到視圖的綁定都是單向的。

現在讓我們看看這可怕的div:

<div [ng-repeat|pane]="panes" class="tab" (^click)="select(pane)">

ng-repeat是一個TemplateDirective,你可以看出我們正在使用一個表達式來綁定它,因為它外面有[]。不過,它里面還有個 | ,還有一個單詞“pane”,這表明在模板中使用的局部變量名稱為“pane”。

現在看看(^click),使用括號表明我們把這個表達式作為一個事件處理函數。如果在括號里還有個 ^ ,就意味著不把處理函數直接附加到DOM節點上,而是讓它冒泡,在文檔的級別處理它。

在這個和模板部分的其他東西上,我暫不表達自己的意見,到下面的評注章節再說。現在先不管你我第一次看到這個的想法,先討論為什么會選擇這樣的語法。

Web Components改變了所有東西。這是另外一個Web的變化影響框架的例子。多數基于數據綁定的框架假定了HTML元素的一個固定集合,并且已經預知了一些特定元素的行為,比如說input等等。可是,在Web Components的世界里,沒有什么可以假設。一個開發人員,不針對Angular,可以編寫一個自定義的元素,帶有任意數量的屬性,高興加什么事件就加什么事件。不幸的是,沒有辦法檢測Web Component來收集有關這些元數據,驅動綁定系統需要這些數據。比如說,沒有辦法知道實際觸發了什么事件。看這個例子:

<x-foo bar="..." baz="..."></x-foo>

看看bar和baz,你能知道哪個是事件,哪個是屬性?不……不幸的是,Angular也不知道,因為Web Components規范沒有包含自描述組件的概念。這很不幸,因為它意味著一個數據綁定系統沒法知道:它是不是需要連接一個綁定表達式,或者是不是需要添加一個事件處理函數來調用表達式。為了解決這個問題,我們需要一個通用的數據綁定系統,語法能夠讓開發人員區分哪個是事件,哪個是屬性綁定。

這還不是唯一的困難。此外,所提供的信息還必須以這么一種不打破Web Component自身的方式。我這話的意思是,不能讓Web Component看到這些表達式,那會破壞這個組件,它只應當看到表達式執行后的結果。實際上這不僅僅影響到Web Components,也會影響原生元素。考慮一下這個:

<img src="{{some.expression}}">

這個代碼會產生一個錯誤的http請求,企圖尋找“some.expression”這個圖。這壓根就不是我們想要的,我們根本不想img看到這個表達式,只希望它看到值。AngularJS 1.x解決這問題的方式是使用ng-src,一個自定義指令。現在,我們回到Web Components……如果你要給任意Web Compoents的每個屬性都創建一個自定義指令,會是一場災難,是不是?我覺得不能這樣,所以需要在綁定系統中更普遍地解決這個問題。

要完成這事,你有兩個選擇。第一個是在模板編譯期間,從DOM上移除屬性。這能夠阻止Web Component碰到這個表達式的文本。可是,這么做就意味著檢測DOM的話,跟蹤不到屬性上綁定表達式的執行。這會讓調試更加困難。另外一個選擇是把屬性名編碼,這樣Web Component就“認不出”它。這樣可以讓Angular看到這些表達式,但是Web Components卻看不到。我們也可以在編譯后把屬性留在元素上,這樣檢測DOM的時候可以看得到。在調試的時候,當然就好得多了。

從以上可以看到,Angular團隊目前支持的是編碼屬性的方法,這種編碼也需要區分屬性和事件。上面所示的語法是完成此事的多種可選項之一。

Angular團隊已經在這一塊嚴重爭論了幾個月。上述語法并未獲得一致同意,但被多數人認同。當制訂綁定語法的時候,也有過大量的考慮。如果你對這些感興趣,我建議你讀一下關于此主題的相當廣泛【豐富】的文檔

好吧,現在我們終于介紹完了模板、綁定和指令是怎么混到一起的……

評注

關于剛才這些,我有太多話要說了……真不知道從何說起……

先從編譯器自身說起吧。

我們是從一個較高層次看待模板的編譯過程的。雖然在這一塊,還有大量的實現要做,我對編譯器的設計非常滿意了。在這里面有一些挺好的東西,保持小的內存占用,減少了垃圾,并且使得模板的實例化超快。這些都很偉大,當然還需要改進,但已經很穩定了。雖然我們尚未談及臟檢測(數據綁定表達式更新的機制),它的實現也有一些新的不錯的想法,可能會讓模板實例化和臟檢測自身的性能都有所提升。當然,能夠動態加載任意東西太可怕了,這是Angular 1.x非常缺乏的一個特性,對大型應用卻很關鍵。所以,它能作為核心需求來設計,我很高興。

現在我們來討論指令吧。

使用類、更好的依賴注入和注解來創建指令的新機制非常棒,它比Angular 1.x所需要的要簡單多了。不幸的是,對于1.x開發人員而言,這是一個相當大的不兼容變更,如果你限于使用ES5,而不能或者不想使用ES6,TypeScript或者AtScript的話,寫起來也會有些困難。本文的前面部分,我提到過提供小型庫用于在ES5中更方便地創建注解類,這樣的API可能會搞得像DDO對象那樣,也許這能讓從1.x到2.0的移植過程簡單點。或許我們現在就應當開始構建它了,這樣你可以在1.3里面使用,然后為2.0提供一個不同的實現……一種遷移抽象層。我也不確定,我想聽聽你們關于這塊的想法,我知道很多人很關心這個。

關于指令,還有另外一件讓我很困擾的事:注解有些冗長。回顧一下NgShow指令,你看到文本‘ngShow’或者它的某種變體重復了多少次?這對我來說顯得有些傻。再看看CustomerEditController,我們要ComponentDirective干什么啊?既然路由都知道它是什么了,我們只寫個普通類不行嗎?

在內部我說了很多約定優于配置的想法,這也是Rails流行并影響很多現代框架的原因,我認為這是一種積極的方式。我想看到一些用于創建指令的約定能把樣板消除。沒有它們的話,我會認為新的指令系統并未把指令簡化到應有的程度,感覺就像是把DDO的一些復雜性放到另外一個地方,也就是注解中去了。你會怎么想呢?喜歡約定嗎?覺得這能讓指令簡單嗎(假設你一直選擇明確 這個地方怎么翻譯啊)?

(assuming you always had the option to be explicit and override the conventions with annotations)?

不幸的是,這些都還不是真正的大問題。在Component Directive中有一個嚴重的問題,希望你已經看到了。注意到它們破壞了展現分離(Separated Presentation)原則嗎?再看看我的TabContainer示例:

@ComponentDirective({
    selector:'tab-container',
    directives:[NgRepeat]
})
export class TabContainer {
    constructor(panes:Query<Pane>) {
        this.panes = panes;
    }

    select(selectedPane:Pane) { ... }
}

有沒有看到TabContainer必須在其元數據中,列出其模板使用到的所有指令?這是TabContainer(控制器)到其視圖實現細節的直接耦合。之前我提到過,對編譯器而言,這是有必要的,因為它在編譯模板之前,需要知道要加載什么,但是,這抵消了使用MVC,MVVM或者其他展現分離模式所帶來的主要優勢。為了避免你覺得我在紙上談兵,我來指出一些后果吧:

  • 沒辦法實現ng-include了。為了編譯HTML,編譯器需要ComponentDirective,因此你就沒法編譯它自己上面的HTML,并且將其包含到一個View里。
  • 如果你想要為同一個組件創建多個潛在的視圖,會很痛苦。想象一下,你有一個組件,但是想要在手機和電腦上顯示不同的視圖,需要聚合所有的指令、過濾器之類,這些東西你在所有視圖都要用,還要確保在單個組件的元數據里把它們都列出來。這個維護起來太可怕了。如果不檢測所有的視圖,從依賴列表中刪任何東西都是不可靠的,也有可能會忘記加東西。
  • 對同一個組件而言,是不可能擁有多種運行時視圖的。想象一下,你配置了一些路由,某些路由使用了相同的“控制器”,但是你需要不同的視圖。不好意思,真做不到。
  • 完全不可能實現展現層的臨時組合,最起碼這會讓數據驅動的UI構建更加復雜。你沒法簡單地組合視圖和控制器(視圖模型)來渲染,這抑制了UI的組合方式,也限制了可復用性,它迫使你為了得到不同的視圖,把控制器拆分成子類。

幸好,設計還是會有不少變化的,我提了個建議來解決這個問題,非常簡單:通過讓模板指定自己的imports,使得它能夠完全自包含。把元數據移出指令,放到HTML模板中。可以這樣使用一個自定義元素:

<ng-import src="ngRepeat"></ng-import>

編譯器可以很容易找到他們,并且確保編譯模板內容的時候,所有東西都加載完成了。就這樣,剛才我提到的所有問題都解決了。

好了,現在我們說完編譯器和指令了……

我感覺接下來是不是該說模板語法了?

老實說,很多人看到這個模板語法的時候可能會吐了,不是所有人,但有不少。我個人是不太喜歡這種語法的,但這是多數人投票的結果(為了理解為什么它還是個草案,你需要看看這篇文檔)。別怕!已經有一些技術性的問題讓這種語法變不成事實,更不用說社區的反對之聲了。Angular團隊已經回頭繼續討論最佳語法了,很多社區成員也加入了,并且提出了自己的見解,很棒。我會把我自己的推薦放在這里,這樣每個人都可以評論。

這是我提議的基本語法:

property="{{expression}}" - 從模型到元素屬性的單向綁定,使用{{}}標識
on-event="{{expression}}" - 給事件添加處理函數,執行表達式,使用on-前綴標識
${expression} - HTML內容和屬性中的字符串插值(基于ES6語法)

就這樣。然后,在實現的時候,我們需要把表達式從DOM移除,以避免各種Web Component的問題之類。僅在調試模式,我們可以通過一個前綴,比如bind-,把它們加回來,這樣可以在不影響Web Components的情況下,通過檢測DOM的方式看到它們。這使得你所寫的和在DOM檢測器中看到的東西不太對稱,但我覺得為了清晰、更加標準的綁定語法起見,這是一種合理的權衡。不是所有人都同意我,你覺得呢?

這個提議解決了綁定的技術問題,也對向后的兼容性有所幫助。可能我們能夠對向后兼容性做很多的事情。我們能夠允許在HTML內容中使用{{expression}}來做字符串插值,但是你可能會對此有選擇余地。它可能會計劃在20xx年被淘汰。推薦的方式可能會是${expression},但這可以對模板提供一個更平緩的升級路徑。也有可能創建一套可選的指令用于支持ng-click之類,同樣將于20xx年廢棄。此外,我們可以提供文檔,對照顯示新舊的差異,幫助人們在“截斷日期”之前,逐步地轉換模板。

這就是我提案的基本內容,也有其他的提案,當然我是有傾向的,我也想知道你們的看法。向后兼容對你來說重要嗎?你是更傾向于使用{{}}這樣的語法,還是用某種方式把屬性名進行編碼?有太多選擇了。

嗯,現在我們的問題都解決了。沒,還有個超大的。

看看雙向綁定!

我不知道你注意到沒有,整篇文章連一個雙向綁定的例子都沒有。其實,我上面解釋過的所有語法都不能用于指定各種綁定選項,如:方向性,觸發器,防反跳等等。那,怎樣綁定一個input元素,把數據推送到模型中呢?怎樣綁定一個需要更新模型的自定義Web Component呢?

在Angular 2.0是否需要雙向的數據綁定,Angular團隊中產生了激烈的辯論。如果你讀過公開的設計文檔(包括這篇文檔),或者看過ngEurope關于Angular 2.0核心的演講Q&A,你可能會發現這一點。我強烈支持保留雙向數據綁定,在我看來,這是Angular靈魂的一部分。我尚未看到哪個建議能提供一個優雅的替代,在我能提出之前,還是會認同支持保留雙向綁定。

你可能想知道這到底為了什么。

我聽到過一些有關為數據流執行DAG的解釋。這個思路是最近被ReactJS搞得火起來的。但是坦率地說,你不能完全執行它。我只要用一個事件聚合器就足以把它搞掛,這是一個在復合應用中非常常見的模式。我覺得你應當教給人們有關DAG的事情,幫助他們在合適的情況下使用,但不能強求。這會使他們的工作變得困難。

我聽說過另外一個論點,主要圍繞校驗能力的不足,但這不是一個移除雙向綁定的理由。你可以很容易在底層放雙向綁定功能,把校驗系統放在它的上層。

我認為最大問題來自Angular用于實現綁定的臟檢測。因為臟檢測,你每做一次檢測,其實是做了兩次。原因在于,如果第一次檢測導致了變化,作為一種副作用,它可能導致其他變化。所以,為了確認,你一定還要再檢測一次。然后,如果第二次檢測之后,又變化了,還得檢測第三次……等等。這個事情就稱為模型的穩定化。是啊,這是臟檢測系統的痛苦,但是移除雙向綁定并不能解決這個問題。你還需要移除所有的監控器,這樣一個表達式的變化不會導致它們中的任意一個產生變更。很明顯,這也就是也需要考慮移除監控器的原因。可是這樣也還是不能解決問題,因為一個事件聚合器就能繞過它……坦白地說,有時候你是需要這樣的。數據綁定是一個很強大的工具,人也是會犯錯的,但我認為我們能解決它。我知道你們中的很多人都可以的。

可能你不同意我的觀點,你覺得“good riddance to two-way binding.”,持這種觀點的人肯定很多。不過,我懷疑多數Angular,Durandal,Knockout,Ember等框架的用戶會認同我。所幸的是,Angular團隊在此事上尚未下定決心,他們在嘗試考慮所有的可能性。所以,沒必要擔心。不過,如果你愛雙向綁定的話,要來幫我,我覺得,如果Angular團隊的其他成員能聽到你們有多愛雙向綁定的話,就太好了。

另一方面,如果你認為雙向綁定是個壞主意,請你幫我們調查替代品。到目前為止,我尚未見到一個差不多好的替代方式,但也許你有比較好的想法呢。如果是這樣的話,我請你來跟我們分享一下。如果我們能一起想出一些更好的東西……那就太棒了。

路由

啊!你對Angular 2.0真夠感興趣的。我真不相信你一直看到這里了。多謝!現在我們來討論路由……

注意:如果你看累了,想要看一段有關路由的視頻的話,可以找到我在ngEurope上關于這個主題的25分鐘演講

基礎

為了讓Angular 2.0成為一個能干的框架,它需要有一個強大的路由解決方案。今年早些時候,Brian Ford開始圍繞Angular社區內外已有的路由解決方案,進行了大量信息的整合。我們看了已有方案的很大一部分,并且把這些案例的研究與我們從社區收到的請求整合起來。把這些放在一起之后,社區就此文檔提供了反饋
,然后我嘗試實現一些東西。幾個短的迭代之后,我們覺得我們做了個挺酷的東西。

自然,所有你期望路由處理的基本場景,新的路由處理了……

  • 簡單的基于JSON的路由配置
  • 可選的約定優于配置
  • 靜態的,參數化的,splat(這個什么意思?)的路由模式
  • 查詢字符串的支持
  • 使用Push State或者Hashchange
  • 導航模型(用于生成導航UI)
  • 文檔標題更新
  • 404路由的處理
  • 歷史的操作
  • 更多

子路由

你可能習慣了有一個路由,并且不得不提前為整個應用配置所有的路由。但是,基于我們的新路由,你擁有更多的靈活性。事實上,每個你導航到的組件都可以有一個路由。我們稱之為子路由,它允許你把應用的整個功能區域進行封裝。如果你有一個擁有多個團隊的大型項目,或者你是個“個體戶”,這能夠把你的代碼庫良好地分割,你會喜歡這個特性的。現在你可以把應用的每個部分當做一個小型應用來構建,它們有自己的路由。然后,你只要把他們掛接到主應用上,給組件映射一個相對路徑,它就能運行了。如果你想看點有意思的,看看我演講里面的遞歸子路由示例。

屏幕激活

有時候,在導航中你需要對過程有所控制。也許你是從一個帶有未保存數據的數據入口屏幕離開,然后需要跟用戶確認一下這樣行不行。也許你在實現一個向導,在顯示到第三步之前,需要確保數據存在于前兩步,然后做相應的重定向。為了處理這類場景,我們實現了一個顯式的導航生命周期,你的控制器可以選擇對導航過程作控制。這里是一個生命周期的鉤子列表:

  • canActivate,允許/阻止導航到新控制器
  • activate,對成功導航到新控制器的響應
  • canDeactivate,允許/阻止從舊控制器離開
  • deactivate,對成功離開舊控制器的響應

can*回調通過返回布爾值的方式,讓你控制導航。你也可以為這個值返回一個Promise,這可以讓你進行異步的操作,作為過程的一部分。此外,你可以返回一個特定的NavigationCommand(比如Redirect),它能讓你對過程作底層控制。

如你所愿,所有這些都可以無縫與子路由協作。

設計

我們努力讓設計盡可能可插拔。所有處理導航請求的邏輯都基于管道架構來建立,這意味著你可以向管道中加入自己的步驟,甚至移除一些我們默認的步驟。例如,如果你不喜歡屏幕激活行為,你可以把它干掉。在管道中,建模了四個步驟,每個代表一個生命周期階段。管道的另外一個重要特性是,每一步都是異步的。所以,如果你需要發起一個服務端請求來對一個用戶進行身份驗證,或者為一個控制器加載數據,你可以在管道中做這個,并且把這個代碼從控制器中移除。

評注

我很難對路由表示中立,因為是我做的實現。我認為這對于一個新路由而言,是良好的起步。肯定還有缺失的功能,但我認為高層次的設計是非常強大的。

作為獎勵,我們也將把它移植回Angular 1.3上。

總結

感謝花這么多時間閱讀本文。在本文發表的時間(2014年11月6日),這是有關Angular 2.0最廣泛在、最新的知識來源了。所以,(now you are up to speed,怎么翻譯?)

我試圖列舉主要的功能和設計的考慮,也包含了一定程度我自己的觀點。設計仍然在發展,我們還處于開發的早期。所以,我希望在最終定稿之前,還能有些變化。也有一些“未知”,比如雙向綁定,團隊還不確定將來要如何處理。我們在嘗試考慮所有的選擇,也許我們會想出一些新的,令人驚訝的東西!?請耐心等待,記住,作為網絡社區的成員,你們被邀請來評判這些問題。當做這些事情的時候,我懇請你們友好、禮貌,但請跟我們分享你的主意,思想和觀點。

謝謝!

 


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()