NG筆記6-動態新增下拉選單選項
2 | 12,880 |
復刻對象: KO範例3 - 動態新增下拉選單選項。
先示範一個失敗寫法。在KO範例裡,新增選項按鈕不包含在ViewModel範圍內,而是透過jQuery click事件在選項集合新增物件,而選項集合是ko.observabelArray(),KO能感測到新增動作,同步增加下拉選單選項;但同樣做法直接搬到NG行不通,option是尋常JavaScript陣列,NG感測不到Scope之外對ViewModel屬性的更動。如以下程式,按鈕後vm.options陣列雖已加入新元素,卻不會反應到下拉選單。Live Demo
<!DOCTYPE html>
<html ng-app="sampleApp">
<head>
<meta charset="utf-8">
<title>Lab 3 - 動態增加SELECT選項(無效)</title>
</head>
<body ng-controller="defaultCtrl">
<select id="selOptions" style="width: 120px"
ng-options="item.text for item in model.options" ng-model="model.result">
</select>
Result=<span ng-bind="model.result.value"></span>
<div style="margin-top: 10px">
Text: <input id='txtOptText' value="Firefox" />
Value: <input id='txtOptValue' value="ff" />
<input type="button" value="新增選項" id='btnAddOpt' />
<div id="dvDebug"></div>
</div>
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.js"></script>
<script>
var vm = null;
angular.module("sampleApp", [])
.controller("defaultCtrl", function($scope) {
function myViewModel() {
var self = this;
self.options = [
{ text: "IE", value: "ie" }
];
self.result = self.options[0];
}
vm = new myViewModel();
$scope.model = vm;
});
$("#btnAddOpt").click(function() {
vm.options.push({
"text": $("#txtOptText").val(),
"value": $("#txtOptValue").val()
});
$("#dvDebug").text(JSON.stringify(vm.options));
});
</script>
</body>
</html>
在下圖中,options陣列已新增Firefox選項,但下拉選單卻仍只有一個選項,由dvDebug顯示的JSON.stringify(vm.options)可以驗證這點:
以上範例突顯了NG與KO的一項重要差異: KO需要明確宣告ko.observable()、ko.observableArray(),但不管任何時候變更這些受觀察物件都會引發UI及相依變數連動;而在NG中,一般的JavaScript物件屬性就可做為繫結對象,但相對地,要"在Scope感應範圍內更動資料,才會引發UI改變及連動"。就這個案例而言,將新增選項動作移入ng-click(),Scope便會將資枓變化反應到<select>。Live Demo
<input type="button" value="新增選項" id='btnAddOpt' ng-click="model.addOption()" />
</div>
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.js"></script>
<script>
var vm = null;
angular.module("sampleApp", [])
.controller("defaultCtrl", function($scope) {
function myViewModel() {
var self = this;
self.options = [
{ text: "IE", value: "ie" }
];
self.result = self.options[0];
self.addOption = function() {
self.options.push({
"text": $("#txtOptText").val(),
"value": $("#txtOptValue").val()
});
};
}
vm = new myViewModel();
$scope.model = vm;
});
</script>
(說明: 在ViewModel中存取$("#txt…")有違SoC原則,為不良設計。此處強調僅移動function()位置,故函式內部保留原樣)
想從NG事件之外更動ViewModel,需要一些技巧。NG提供jQuery.scope()方法,可以取得UI元素所屬的Scope物件,接著我們就可以存取到model物件及model.options: Live Demo
<script>
var vm = null;
angular.module("sampleApp", [])
.controller("defaultCtrl", function($scope) {
function myViewModel() {
var self = this;
self.options = [{
text: "IE",
value: "ie"
}];
self.result = self.options[0];
}
vm = new myViewModel();
$scope.model = vm;
});
$("#btnAddOpt").click(function() {
var scope = $(this).scope();
scope.model.options.push({
"text": $("#txtOptText").val(),
"value": $("#txtOptValue").val()
});
$("#dvDebug").text(JSON.stringify(scope.model.options));
});
</script>
但是以上程式仍不管用,還差一個關鍵: 必須在Scope監視下更動資料,才會觸發繫結UI元素、連動變數或函數的更新。使用$scope.$apply()執行ViewModel更新,NG才能掌握資料異動。用法有三種: $scope.$apply("指令字串") 、 $scope.$apply(function() { 更新程式碼 }),或是依一般做法更新後再不帶參數呼叫$scopt.$apply()。Live Demo
$("#btnAddOpt").click(function() {
var scope = $(this).scope();
//方法1: 傳入指令字串給$apply()執行
scope.$apply("model.options.push({text:'Chrome',value:'chrome'})");
//方法2: 利用$apply()執行函式
scope.$apply(function() {
scope.model.options.push({
"text": $("#txtOptText").val(),
"value": $("#txtOptValue").val()
});
});
//方法3: 執行完畢呼叫$apply()[不帶參數]
scope.model.options.push({text:"Safari",value:"safari"});
scope.$apply();
$("#dvDebug").text(JSON.stringify(scope.model.options));
});
Comments
# by tim
感謝這麼清楚的筆記跟分析. 沒想到這個部分有點過於複雜,不及 knockout/backbone 清楚。 不知道 angular 2.0 有沒有更清楚的做法?
# by Jeffrey
to tim, NG 2規格仍未底定,但可以確定變動偵測機制已大幅革新,變得更有效率,如果你跟我一樣懷念KO的Observable運作模式,好消息是NG2也提供新選擇:http://victorsavkin.com/post/110170125256/change-detection-in-angular-2