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.



2014年7月8日 星期二

Angular controller (續一, controller 還能做什麼?)

在上一篇 Angular controller 程式範例裡,  controller 只用來在 $scope 裡定義一個變數 (greeting), 並給了初始值 ('你好!'). 難道 controller 就只有 $scope 可以用嗎? 只能定義變數嗎?

No. 它還$scope 以外還可以 "Inject" (打針?) $http 等東西. (以後會專題討論 Dependency Injection.)

No. $scope 不只能定義變數, 它還能定義 function 且自動幫你呼叫它.

本文將說明如何用 Angular controller $scope 來定義一個 Javascript function 給 HTML 來用.







在 HTML 裡, 一個普普通通的 button (按鈕)加入一個參數型的內建 directive (ng-click), 將它等於一個 method 呼叫, 例如 ng-click="onClickSayHello()", 而這個 onClickSayHello function 要定義在 Javascript 程式裡的 "GreetingController" controller 的function之 $scope 裡, 例如




在前一篇 Angular controller 文章裡, 就有提到, $scope 就像一個在這 ng-controller 控制的範圍內類似一個所有變數的容器, 它在 Javascript 的定義的變數或方程式 (functions) 跟 HTML 裡都能互通. 因此, $scope.onClickSayHello 就等於 HTML 裡的 "onClickSayHello()", 所以當然 $scope.greeting 定義的變數, 在 HTML 也可以用, 例如將下面範例程式, ng-model="greeting", 或 HTML 隨便加一個 {{greeting}} 結果都會出現 '你好', 這就是它 data-binding 神奇好用的地方囖!



註一: 上圖的下方是 Google Chrome 瀏覽器, 打開 Tools->Javascript Console 的視窗. 你就可以看到 console.log 輸出 '你好!'

註二: 你有沒有發現, 其實你在學習使用 AngularJS 的過程中, 不知不覺的已經在使用 AngularJS 內建的 directive, 本文裡的 ng-click 就是其中之一, 詳細使用說明或其他內建directive, 請參考 https://docs.angularjs.org/api/ng/directive/ngClick 別忘了directive 的命名方式, 在 Javascript 裡他用 CamelCase (駱駝式) 像 ngClick, 在 HTML 裡呼叫要改名為 ng-click (dash 式). 多練習, 就不會忘記了.


你可以 fork 以下程式範例, 做練習, 例如將 console.log 改用 alert, 再按按 'Say Hello' button 看看效果如何?


http://codepen.io/mingderwang/pen/Hdrbe

Angular controller 那我的第一個 AngularJS controller 呢?

為何我跟別人 AngularJS 的教材特別不一樣, 反而先教 directives 後教 controllers 呢? 沒什麼原因, 只是我覺得 directives 是 Angular 比較容易上手, 而且常用的東西, 既使不太會寫 AngularJS code, 會用 Angular directives (別人提供的模組) 對寫 front-end 來說來的更重要.
但從模組的內容來看, 它們每一個各有其功能.

controller 其實本身很簡單, 只是幫這個控制器的範圍內, 提供一個屬於它專屬的 global 變數, 它叫做 $scope. 所以在這個 scope (範圍) 裡, 它肚子裡的所有變數或 functions. 大家都可以共享, access 的到. 它可以有兩種寫法.

myApp.controller('GreetingController', ['$scope', function($scope) {
  $scope.greeting = '你好!';
}]);



myApp.controller('GreetingController', function($scope) {
  $scope.greeting = '你好!';
});


雖然文法上, 第二種寫法比較看的懂, 但還是建議您用第一種寫法.

哪 Angular directive 的程式來改:

http://codepen.io/mingderwang/pen/tdKfB

1. 在 Javascript 的地方, 加入 controller 的 code 如下


原本 'myApp' module (模組) 只有 .directive 的定義, 現在在插入 .controller 的定義.

.controller('GreetingController', ['$scope', function($scope) {
  $scope.greeting = '你好!';
}])

也繼續沿用 cascading 的方法, 直接串在 module 物件後面即可.

別忘了, 這個 controller 名稱是 'GreetingController', 只定義在 Javascript 裡, HTML 並不知道它的範圍是哪裡道哪裡,

所以可以找個 <div> </div>來加 ng-controller = 'GreetingController' 如下


所以我們希望 <ng-taiwan/> 這個 element directive 在 Javascript code 裡改成

template: '<h1>哈囉 {{ greeting }}! using directive: ng-taiwan </h1>'

greeting 這個變數, 在這個範圍內是被 'GreetingController' 的 ng-controller 所控管. 所以 $scope.greeting 的值就會顯現出來, "哈囉 你好", 不再隨著 sometext 變數變動了.



Angular directive (續二: directive 不只能做成 HTML的 element)

Angular directive 在 HTML 裡呈現的方式可以是

1. 當成一個 element 例如: <ng-taiwan/>
2. 當成一個 element 的 attribute 例如 <div ng-app></div>
3. 視為一個 class, 例如: <input type="text" class="ng-hello"/>
4. 或當成一個註解, 例如 <!--directive:ng-ming-->

所以我們可以來試試看每一種的用法,

還是拿第一個 directive 的程式碼來改起, 它是屬於第一類做成一個 HTML 的 element.

http://codepen.io/mingder78/pen/HyfAb


所以 JS (Javascript) 程式碼裡 (如上圖), 至少要有一個 angular.module (模組), 它有兩個參數, 一個適模組名 (這裡是 'myApp') , 另一個是該模組還依賴哪些其他模組的陣列 (這裡沒有用到其他任何模組, 所以是空陣列 [ ] .)

你可以把每一個 module 當成 directives 或其他例如 controllers, filters, 或 services 等的容器, 所以我們也可以寫成


有沒有清楚一些, 其實 Javascript 可以把程式一直串下去 (cascading), 所以才會有第一種寫法, 當然如果自己也把程式細分成很多模組, 同一個程式也可以寫成這樣


結果是一樣的, 其實我最喜歡最後一種, 因為程式會越來越大, 能把不同的功能做成模組, 未來可以個別使用, 遠比所有不相干的東西都放在同一個 myApp 模組來的更合理些.

再來看看 directive 怎麼寫, 它也是有兩個參數, 一個是該 direcitve 的名稱, 另一個是回傳一個結構體的 function. 首先需知道的是這 directive 的名稱是有嚴格規定, 而相對的在 HTML 裡確用另一個寫法, 其實他們指的都是同一個 diretive. 我想這是 Angular 初學者最無法用直覺容易了解的東西. 但它就是如此. 在 Javascript 裡要用 CamelCase 的方式命名, 也就是好幾個字連在一起, 除了第一個字小寫, 其他都大寫. 例如 'I Love You", 在 Javascript 裡要寫成 'iLoveYou', 相對的同一個 directive 在 HTML 裡確要用 dash-delimited 的寫法, 也就是 'i-love-you' 都是小寫, 且用 '-' (dash) 符號把它們連起來.

所以在這個範例裡 HTML 用 ng-taiwan, 在 Javascript 裡用 'ngTaiwan'. 除了Angular element 會用這種命名方式, 如果 attributes (變數) 也有可能發生.

最後說明一下 function 回傳的值, 有很多變數需設定, 但這裡至少用到 restrict: 以及 template: 就先說明這兩個變數吧

restrict (限制) 的值有可能, 或同時用到 'E', 'A', 與 'C', 看你這個 directive 是要只能當成 'Element' 還是 'Attribute' 還是 'Class', (還有 'M' 代表這 directive 當成註解) 這就是為何這篇文章開宗明義, 它可以不只是 element 的作法.

template (樣板) 是最簡單的, 用一個 HTML 的文法來當取代 HTML 裡該 directive 在 DOM 裡的元素. 所以很簡單解釋這個範例程式, 就是下面 HTML 程式碼裡的 <ng-taiwan></ng-taiwan> 部分, 在顯示之前會塞入 <h1>哈囉 {{ sometext }}! using directive: ng-taiwan </h1> 這就是為什麼它的結果跟 Angular 101 裡的範例程式結果相同.

其實還有一個參數我沒用, 就是 replace (替代), 如果 replace: true, 樣板的部份, 就會整個替換掉 <ng-taiwan></ng-taiwan> 的部份.



2014年7月7日 星期一

Angular directive (續一: 在HTML 裡 directive 不同的表示法)

在我第一個 directive 程式裡,

HTML 裡顯示該 directive 的那一行是

<ng-taiwan></ng-taiwan>



它有很多寫法, 例如可改寫成

<ng-taiwan/>



<ng:taiwan/>



若當成 attribute 時還可以寫成

<div ng-taiwan></div>


<div ng:taiwan></div>

(在本範例程式不適用)


若當成 attribute 且在 HTML5 裡可以用

<div data-ng-taiwan></div>

<div x-ng-taiwan></div>

(在本範例程式不適用)


你可以 fork 以下程式在 Codepen裡試試看 directive 不同的表示法.
http://codepen.io/mingder78/pen/HyfAb

註: 但其實你只要挑一種使用就好, 只是怕看別人的 code, 會有不同的寫法, 稍微了解一下.



2014年7月6日 星期日

教學大綱 AngularJS Tutorial (in Chinese)

AngularJS 教學大綱:

* Angular 101

* 做你第一個 Angular directive

Angular directive (續一: 在HTML 裡 directive 不同的表示法)

Angular directive (續二: directive 不只能做成 HTML的 element)

那我的第一個 Angular controller 呢?

Angular controller (續一, controller 還能做什麼?)

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



Make your own AngularJS directive. 做一個 AngularJS directive.

我將上一個 Angular 101 裡的範例, 改寫成用 directive 的寫法再此做個教學, 教你如何做你的第一個 Angular directive.

Angular 101 裡的範例 (Codepen 版) :

http://codepen.io/mingder78/pen/sqdJA



改用 directive 的程式範例在 Codepen 如下:

http://codepen.io/mingder78/pen/HyfAb



在 HTML 裡看出差異的地方了嗎? 就是原本

<h1>哈囉 {{ sometext }}</h1>

的地方, 用

<ng-taiwan></ng-taiwan>

代替了.

好神奇, 你也可以自己利用 Angular directive 發明自己專用的 HTML tag, 甚至於這個 tag 背後代表的就是一個 DOM 的元件 (element). AngularJS 在背後幫你做很多是, 你自己或別人想要用你分享出來的模組 (module), 除來連將你的 javascript code 載入以外, 唯一要做的就是在 HTML 文章你加入你發明的 tag, 也就是所謂的 directive, 還有利用 ng-app 把該 directive 所隸屬的模組名稱告訴 web 瀏覽器. 該 tag 的地方就會呈現出你期望的效果.

之後會介紹如何用 directive 使用  d3.js 或 bootstrap. 敬請期待.