2014年7月10日 星期四

Directive Definition Object 裡, 我還是搞不懂 scope: 要用 '=', '&' 還是 '@'

這篇官方-非官方AngularJS 小抄
http://www.cheatography.com/proloser/cheat-sheets/angularjs/

我想利用這小抄, 提醒我還有哪些重要的功能還沒教到. 今天還是來談 directive 裡回傳值 scope 傳什麼? 很多初學者, 其中包括我一直搞不懂什麼時候該用 '=', 什麼時候該用其他兩種. 所有的英文教材, 舉了很多範例, 包括官方文件, 都說明了很清楚, 但還是看不懂.

(難道一定要挖掘 source code 來看嗎? 不, 先不要...)

其實有很多文章講的很多, 有的也還蠻清楚的, stack overfolow 也一大堆相關問題, github (有一篇寫的很仔細 https://github.com/angular/angular.js/wiki/Understanding-Scopes , 但我喜歡它有一句話 It doesn't work the way most people expect it should work.我想這是很難搞懂它的主要原因吧!) 但不想看長篇大論, 又想搞清楚的人. 可以利用以下規則來使用, 八九不離十會對, 但非100% work.

*先看看我在 Codepen 寫了一個範例, http://codepen.io/mingderwang/pen/bpBiD

HTML 程式如下:


Javascript 程式如下:


仔細看 directive return 值裡的 scope:

  scope: {
            /* 通常用來指向 controller 裡 $scope 某個 var 變數. 透過HTML 裡 language 變數來指定. */
            howAreYou: '=language',

            /* 通常用來指向 controller 裡 $scope 某個 function 變數. 透過HTML 裡 function 變數來指定. */
            onClick: '&function',

            /* 若要傳字串給 directive, 用 @ */
            name: '@myNameIs'
        },

1. 先解釋一下這個 scope, 簡單講就是"範圍", 每個 controller 都帶有一個 $scope (有帶錢號的 scope (修正,  它不等於 $rootScope, 謝謝謝昌熹 糾正)), 當成這範圍內所有共用的變數, 可以定義在它下面. 不管是 var 變數, 還是 function 函式都可以在 HTML 裡直接使用 (使用時不需加 $scope.) (註: 這裡所謂的"範圍", 如果還不太清楚它意思的人, 簡單講它在 HTML 裡 ng-controller 一定會放在<ANY ng-controller="">...</ANY>, 這裡的 ANY 常常是 div, 也就是這個 ANY 在 DOM 裡的範圍.)

但 directive 裡面若定義它自己的 scope, 也就是所謂的 "isolate" (隔離) scope. 一樣可以在 HTML 裡直接使用, 但它的範圍, 就只有在該 directive 呼叫的範圍裡, 就是範例裡 <ng-taiwan> 到 </ng-taiwa> 這個元素與 directive 程式的執行的範圍裡.

2. 但 directive 裡有可能要用到一些變數或函式, 以本文範例來看, template 裡有用到 onClick() 一個函式, 以及 name 與 hawAreYou 兩個變數, 所以需要用 scope: 這個物件來告訴 Angular,  HTML 用的名稱是對應到 Angular directive 用的哪個名稱.

這裡, directive 裡用到的 name 在 HTML 裡卻用 my-name-is, 也就是上面註解裡的 /* 若要傳字串給 directive, 用 @ */, 我希望使用這個 directive 的人, 可以在 HTML 就宣告 name 的預設值為, 傳不同值給 directive, (這是我在 directive 裡最想用的功能之一), 但它只能傳字串 (string). 其他就看你怎麼運用或 parsing 這個字傳了.

3. 但這裡還有一個一定要提醒的地方就是所謂的 isolate scope, 就是, 只要你回傳 scope: {...} 這個物件, 就表示你 directive 裡不想用 (繼承) $scope 裡的所有變數或函式, 都使用你新定義的, 透由 scope: {...} 裡定義的變數, 只有 directive 裡可以使用, directive 元素以外的別人看不到也改不到, 所以才叫 "isolated".

4. 如果你想直接用 $scope 裡的變數, 回傳 scope: true, 就好, directive 就可以像在 HTML 裡直接使用所謂 controller "範圍" 裡的 $scope 裡的所有功能 (所以我稱$scope為該範圍裡的 global 變數 container). (名稱使用時也不加  $scope.) [註: 4 不建議使用]

<謝謝 Pymaster 提醒, scope: true 雖然你可以直接用到 $scope 裡變數的名稱, 但那是複製 (說它是繼承更恰當) 一份$scope 的變數到 childScope 裡, 感覺上你可以用該變數或函式, 但你卻改變不了 $scope (原本 $scope 是 parentScope).>

5. 建議 directive 盡量用 isolate scope. (才不會無緣無故把 global ($scope) 裡的值被改掉, 而造成問題, 也比較模組化)

6. scope 沒有回傳時, 預設 (default) 值為 scope: false, 該 directive 跟 4. 很像, 可以使用 $scope 裡的變數或函式, 但會影響 $scope 的值. 它既沒繼承也沒複製, 就是直接使用 $scope 的變數或函式. (更正).

你可以試試 fork 這個 codepen 更改 scope: true 變成 scope: false, 你會發現 chinese 跟 alert() 這些變數都還是可以用, 但按 Say Hello 按鈕, 你會發現 alert() 顯示的值 $scope.chinese 會因為更改 <input /> 裡的值而改變.

註: 相反的, 如果不要跟 controller裡的 $scope 變數或函數名稱有瓜葛, 反而要用 scope: {} 來表示. 


7. '=' 原理上跟 '@' 類似, 唯一不同是 '@' 是單項 binding, '=' 是雙向 (two-way binding). 這樣解釋大多數的初學者絕對還是霧煞煞. 還是用我範例的註解來說明吧: /* 通常用來指向 controller 裡 $scope 某個 var 變數. 透過HTML 裡 language 變數來指定. */ 當 scope: 裡定義了 howAreYou: '=language',  在 HTML 裡 <ng-taiwan language='english' function='alert()' my-name-is='Ming'></ng-taiwan> Angular 會透由 controller (GreetingController) 的 $scope 裡找到 $scope.english 這個變數或函式. (這裡是變數, 定義了 $scope.english = 'How Are You!';) 所以 你可以 fork 我的範例, 將 HTML 裡的 language='english' 改成 language='chinese', 結果 howAreYou 的位置 binding 到的值, 變成"你好". (Cool)

8. '&' 乾脆直接用我的註解來解釋, /* 通常用來指向 controller 裡 $scope 某個 function 變數. 透過HTML 裡 function 變數來指定. */

onClick: '&function' ,

在 directive 的 template 裡<button ng-click="onClick()">Say Hello 中文</button>, 用到 onClick() 這個函式, 但在 HTML 裡有可能要讓使用 directive 的人更有彈性的使用該 directive, 所以我提供一個 function 變數, 讓user 可以決定用 alert() 還是 console.log() 來顯示 '你好', 所以在 HTML <ng-taiwan language='english' function='alert()' my-name-is='Ming'></ng-taiwan> 裡的 function='alert()' 改成 function='console()', 打開你web 瀏覽器的 javascript console, 就可以看到 '你好'.

9. 但值得一提的是 '&' 的原意不是只針對 $scope 裡的函式. 其實它原本功能是,  trigger evaluation of an expression (啟動一個 expression 的值(evaluated), (我們以後會再談 expressions), 當然一個 function 函式的值, 就是執行該函式, 或回傳它的結果.

10. 最後, 如果你用的是同一個名稱, (即使 HTML 裡用dash 方式, Javascript 裡用駱駝式命名, 我們還是認定它是同一個變數名稱). 可以後面一個不再寫名稱,  只要用 '=', '@', 或 '&' 即可.

希望本文能讓你快速且正確的使用 directive 裡的 isolate scope 定義.

Happy Coding.



1 則留言:

  1. Hello,

    建議關於 `scope: true` 的部分最好要特別說明一下是「會建立新的 child scope」,避免造成日後使用上的誤會以及 bug

    另外最近看了幾篇這系列的文章,覺得 Blogger 好像不太適合這樣寫技術文章,是否有考慮再自訂一下 theme 或是搬到比較適合技術文章的 blog 平台(Logdown 之類的)呢?

    回覆刪除