Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AngularJS实例教程(一)——数据绑定与监控 #14

Open
xufei opened this issue Jan 12, 2015 · 50 comments

Comments

Projects
None yet
@xufei
Copy link
Owner

commented Jan 12, 2015

数据绑定与监控

在业务开发的过程中,我们可能会大量使用DOM操作,这个过程很繁琐,但是有了AngularJS,基本上就可以解脱了,做到这一点的关键是数据绑定。那什么是数据绑定,怎样绑定呢?本节将从多种角度,选取业务开发过程中的各种场景来举例说明。

基于单一模型的界面同步

有时候,我们会有这样的需求,界面上有个输入框,然后有另外一个地方,要把这个文本原样显示出来,如果没有数据绑定,这个代码可能很麻烦了,比如说,我们要监听输入框的各种事件,键盘按键、复制粘贴等等,然后再把取得的值写入对应位置。但是如果有数据绑定,这个事情就非常简单。

<input type="text" ng-model="a"/>
<div>{{a}}</div>

这么小小一段代码,就实现了我们想要的功能,是不是很可爱?这中间的关键是什么呢,就是变量a,在这里,变量a充当了数据模型的角色,输入框的数据变更会同步到模型上,然后再分发给绑定到这个模型的其他UI元素。

注意,任意绑定到数据模型的输入元素,它的数据变更都会导致模型变更,比如说,我们有另外一个需求,两个输入框,我们想要在任意一个中输入的时候,另外一个的值始终跟它保持同步。如果用传统的方式,需要在两个输入框上都添加事件,但是有了数据绑定之后,这一切都很简单:

<input type="text" ng-model="b"/>
<input type="text" ng-model="b"/>

这样的代码就可以了,核心要素还是这个数据模型b。

到目前为止的两个例子都很简单,但可能有人有问题要问,因为我们什么js都没有写,这个a跟b是哪里来的,为什么就能起作用呢?对于这个问题,我来作个类比。

比如大家写js,都知道变量可以不声明就使用:

a = 1;

这时候a被赋值到哪里了呢,到了全局的window对象上,也就是说其实相当于:

window.a = 1;

在AngularJS里,变量和表达式都附着在一种叫做作用域(scope)的东西上,可以自己声明新的作用域,也可以不声明。每个Angular应用默认会有一个根作用域($rootScope),凡是没有预先声明的东西,都会被创建到它上面去。

作用域的相关概念,我们会在下一章里面讲述。在这里,我们只需要知道,如果在界面中绑定了未定义的某变量,当它被赋值的时候,就会自动创建到对应的作用域上去。

前面我们在例子中提到的{{}}这种符号,称为插值表达式,这里面的内容将会被动态解析,也可以不使用这种方式来进行绑定,Angular另有一个ng-bind指令用于做这种事情:

<input ng-model="a"/>
<div>{{a}}</div>
<div ng-bind="a"></div>

对模型的二次计算

嗯,有时候,实际情况没有这么简单,比如说,我们可能会需要对数据作一点处理,比如,在每个表示价格的数字后面添加一个单位:

<input type="number" ng-model="price"/>
<span>{{price + "(元)"}}</span>

当然我们这个例子并不好,因为,其实你可以把无关的数据都放在绑定表达式的外面,就像这样:

<input type="number" ng-model="price"/>
<span>{{price}}(元)</span>

那么,考虑个稍微复杂一些的。我们经常会遇到,在界面上展示性别,但是数据库里面存的是0或者1,那么,总要对它作个转换。有些比较老土的做法是这样,在模型上添加额外的字段给显示用:

这是原始数据:

var tom = {
    name: "Tom",
    gender: 1
};

被他转换之后,成了这样:

var tom = {
    name: "Tom",
    gender: 1,
    genderText: ""
};

转换函数内容如下:

if (person.gender == 0)
    person.genderText = "";

if (person.gender == 1)
    person.genderText = "";

这样的做法虽然能够达到效果,但破坏了模型的结构,我们可以做些改变:

<div>{{formatGender(tom.gender)}}</div>
$scope.formatGender = function(gender) {
    if (gender == 0)
        return "";

    if (gender == 1)
        return "";
    }
};

这样我们就达到了目的。这个例子让我们发现,原来,在绑定表达式里面,是可以使用函数的。像我们这里的格式化函数,其实作用只存在于视图层,所以不会影响到真实数据模型。

注意:这里有两个注意点。

第一,在绑定表达式里面,只能使用自定义函数,不能使用原生函数。举个例子:

<div>{{Math.abs(-1)}}</div>

这句就是有问题的,因为Angular的插值表达式机制决定了它不能使用这样的函数,它是直接用自己的解释器去解析这个字符串的,如果确实需要调用原生函数,可以用一个自定义函数作包装,在自定义函数里面可以随意使用各种原生对象,就像这样:

<div>{{abs(-1)}}</div>
$scope.abs = function(number) {
    return Math.abs(number);    
};

第二,刚才我们这个例子只是为了说明可以这么用,但不表示这是最佳方案。Angular为这类需求提供了一种叫做filter的方案,可以在插值表达式中使用管道操作符来格式化数据,这个我们后面再细看。

数组与对象结构的绑定

有时候,我们的数据并不总是这么简单,比如说,有可能会需要把一个数组的数据展示出来,这种情况下可以使用Angular的ng-repeat指令来处理,这个东西相当于一个循环,比如我们来看这段例子:

$scope.arr1 = [1, 2, 3];

$scope.add = function() {
    $scope.arr1.push($scope.arr1.length + 1);
};
<button ng-click="add()">Add Item</button>
<ul>
    <li ng-repeat="item in arr1">{{item}}</li>
</ul>

这样就可以把数组的内容展示到界面上了。数组中的数据产生变化时,也能够实时更新到界面上来。

有时候,我们会遇到数组里有重复元素的情况,这时候,ng-repeat代码不能起作用,原因是Angular默认需要在数组中使用唯一索引,那假如我们的数据确实如此,怎么办呢?可以指定它使用序号作索引,就像这样:

$scope.arr2 = [1, 1, 3];
<ul>
    <li ng-repeat="item in arr2 track by $index">{{item}}</li>
</ul>

也可以把多维数组用多层循环的方式迭代出来:

$scope.arr3 = [
    [11, 12, 13],
    [21, 22, 23],
    [31, 32, 33]
];
<ul>
    <li ng-repeat="childArr in arr3 track by $index">
        {{$index}}
        <ul>
            <li ng-repeat="item in childArr track by $index">{{item}}</li>
        </ul>
    </li>
</ul>

如果是数组中的元素是对象结构,也不难,我们用个表格来展示这个数组:

$scope.arr4 = [{
    name: "Tom",
    age: 5
}, {
    name: "Jerry",
    age: 2
}];
<table class="table table-bordered">
    <thead>
    <tr>
        <th>Name</th>
        <th>Age</th>
    </tr>
    </thead>
    <tbody>
    <tr ng-repeat="child in arr4">
        <td>{{child.name}}</td>
        <td>{{child.age}}</td>
    </tr>
    </tbody>
</table>

有时候我们想遍历对象的属性,也可以使用ng-repeat指令:

$scope.obj = {
    a: 1,
    b: 2,
    c: 3
};
<ul>
    <li ng-repeat="(key, value) in obj">{{key}}: {{value}}</li>
</ul>

注意,在ng-repeat表达式里,我们使用了一个(key, value)来描述键值关系,如果只想要值,也可以不用这么写,直接按照数组的写法即可。对象值有重复的话,不用像数组那么麻烦需要指定$index做索引,因为它是对象的key做索引,这是不会重复的。

数据监控

有时候,我们不是直接把数据绑定到界面上,而是先要赋值到其他变量上,或者针对数据的变更,作出一些逻辑的处理,这个时候就需要使用监控。

最基本的监控很简单:

$scope.a = 1;

$scope.$watch("a", function(newValue, oldValue) {
    alert(oldValue + " -> " + newValue);
});

$scope.changeA = function() {
    $scope.a++;
};

对作用域上的变量添加监控之后,就可以在变更时得到通知了。如果说新赋值的变量跟原先的相同,这个监控就不会被执行。比如说刚才例子中,继续对a赋值为1,不会进入监控函数。

以上这种方式可以监控到最直接的赋值,包括各种基本类型,以及复杂类型的引用赋值,比如说下面这个数组被重新赋值了,就可以被监控到:

$scope.arr = [0];

$scope.$watch("arr", function(newValue) {
    alert("change:" + newValue.join(","));
});

$scope.changeArr = function() {
    $scope.arr = [7, 8];
};

但这种监控方式只能处理引用相等的判断,对于一些更复杂的监控,需要更细致的处理。比如说,我们有可能需要监控一个数组,但并非监控它的整体赋值,而是监控其元素的变更:

$scope.$watch("arr", function(newValue) {
    alert("deep:" + newValue.join(","));
}, true);

$scope.addItem = function() {
    $scope.arr.push($scope.arr.length);
};

注意,这里我们在$watch函数中,添加了第三个参数,这个参数用于指示对数据的深层监控,包括数组的子元素和对象的属性等等。

样式的数据绑定

刚才我们提到的例子,都是跟数据同步、数据展示相关,但数据绑定的功能是很强大的,其应用场景取决于我们的想象力。

不知道大家有没有遇到过这样的场景,有一个数据列表,点中其中某条,这条就改变样式变成加亮,如果用传统的方式,可能要添加一些事件,然后在其中作一些处理,但使用数据绑定,能够大幅简化代码:

function ListCtrl($scope) {
    $scope.items = [];

    for (var i=0; i<10; i++) {
        $scope.items.push({
            title:i
        });
    }

    $scope.selectedItem = $scope.items[0];

    $scope.select = function(item) {
        $scope.selectedItem = item;
    };
}
<ul class="list-group" ng-controller="ListCtrl">
    <li ng-repeat="item in items" ng-class="{true:'list-group-item active', false: 'list-group-item'}[item==selectedItem]" ng-click="select(item)">
        {{item.title}}
    </li>
</ul>

在本例中,我们使用了一个循环来迭代数组中的元素,并且使用一个变量selectedItem用于标识选中项,然后关键点在于这个ng-class的表达式,它能够根据当前项是否为选中项,作出一个判断,生成对应的样式名。这是绑定的一个典型应用了,基于它,能把一些之前需要依赖于某些控件的功能用特别简单的方式做出来。

除了使用ng-class,还可以使用ng-style来对样式作更细致的控制,比如:

<input type="number" ng-model="x" ng-init="x=12"/>
<div ng-style="{'font-size': x+'pt'}">
    测试字体大小
</div>

状态控制

有时候,我们除了控制普通的样式,还有可能要控制某个界面元素的显示隐藏。我们用ng-class或者ng-style当然都是可以控制元素的显示隐藏的,但Angular给我们提供了一种快捷方式,那就是ng-show和ng-hide,它们是相反的,其实只要一个就可以了,提供两个是为了写表达式的方便。

利用数据绑定,我们可以很容易实现原有的一些显示隐藏功能。比如说,当列表项有选中的时候,某些按钮出现,当什么都没选的时候,不出现这些按钮。

主要的代码部分还是借用上面那个列表,只添加一些相关的东西:

<button ng-show="selectedItem">有选中项的时候可以点我</button>
<button ng-hide="selectedItem">没有选中项的时候可以点我</button>

把这个代码放在刚才的列表旁边,位于同一个controller下,点击列表元素,就能看到绑定状态了。

有时候,我们也想控制按钮的可点击状态,比如刚才的例子,那两个按钮直接显示隐藏,太突兀了,我们来把它们改成启用和禁用。

<button ng-disabled="!selectedItem">有选中项的时候可以点我</button>
<button ng-disabled="selectedItem">没有选中项的时候可以点我</button>

同理,如果是输入框,可以用同样的方式,使用ng-readonly来控制其只读状态。

流程控制

除了使用ng-show和ng-hide来控制元素的显示隐藏,还可以使用ng-if,但这个的含义与实现机制都大为不同。所谓的show和hide,意味着DOM元素已经存在,只是控制了是否显示,而if则起到了流程控制的作用,只有符合条件的DOM元素才会被创建,否则不创建。

比如下面的例子:

function IfCtrl($scope) {
    $scope.condition = 1;

    $scope.change = function() {
        $scope.condition = 2;
    };
}
<div ng-controller="IfCtrl">
    <ul>
        <li ng-if="condition==1">if 1</li>
        <li ng-if="condition==2">if 2</li>

        <li ng-show="condition==1">show 1</li>
        <li ng-show="condition==2">show 2</li>
    </ul>

    <button ng-click="change()">change</button>
</div>

这个例子初始的时候,创建了三个li,其中一个被隐藏(show 2),当点击按钮,condition变成2,仍然是三个li,其中,if 1没有了,if 2创建出来了,show 1隐藏了,show 2显示了。

所以,我们现在看到的是,if的节点是动态创建的。与此类似,我们还可以使用ng-switch指令:

function SwitchCtrl($scope) {
    $scope.condition = "";

    $scope.a = function() {
        $scope.condition = "A";
    };

    $scope.b = function() {
        $scope.condition = "B";
    };

    $scope.c = function() {
        $scope.condition = "C";
    };
}
<div ng-controller="SwitchCtrl">
    <div ng-switch="condition">
        <div ng-switch-when="A">A</div>
        <div ng-switch-when="B">B</div>
        <div ng-switch-default>default</div>
    </div>
    <button ng-click="a()">A</button>
    <button ng-click="b()">B</button>
    <button ng-click="c()">C</button>
</div>

这个例子跟if基本上是一个意思,只是语法更自然些。

数据联动

在做实际业务的过程中,很容易就碰到数据联动的场景,最典型的例子是省市县的三级联动。很多前端教程或者基础面试题以此为例,综合考察其中所运用到的知识点。

如果是用Angular做开发,很可能这个就不成其为一个考点了,因为实现起来非常容易。

我们刚才已经实现了一个单级列表,可以借用这段代码,做两个列表,第一个的数据变动,对第二个的数据产生过滤。

function RegionCtrl($scope) {
    $scope.provinceArr = ["江苏", "云南"];
    $scope.cityArr = [];

    $scope.$watch("selectedProvince", function(province) {
        // 真正有用的代码在这里,实际场景中这里可以是调用后端服务查询的关联数据
        switch (province) {
            case "江苏": {
                $scope.cityArr = ["南京", "苏州"];
                break;
            }
            case "云南": {
                $scope.cityArr = ["昆明", "丽江"];
                break;
            }
        }
    });

    $scope.selectProvince = function(province) {
        $scope.selectedProvince = province;
    };

    $scope.selectCity = function(city) {
        $scope.selectedCity = city;
    };
}
<div ng-controller="RegionCtrl">
    <ul class="list-group">
        <li ng-repeat="province in provinceArr" ng-class="{true:'list-group-item active', false: 'list-group-item'}[province==selectedProvince]" ng-click="selectProvince(province)">
            {{province}}
        </li>
    </ul>
    <ul class="list-group">
        <li ng-repeat="city in cityArr" ng-class="{true:'list-group-item active', false: 'list-group-item'}[city==selectedCity]" ng-click="selectCity(city)">
            {{city}}
        </li>
    </ul>
</div>

这段代码看起来比刚才复杂一些,其实有价值的代码就那个$watch里面的东西。这是什么意思呢?意思是,监控selectedProvince这个变量,只要它改变了,就去查询它可能造成的更新数据,然后剩下的事情就不用我们管了。

如果是绑定到下拉框上,代码更简单,因为AngularJS专门作了这种考虑,ng-options就是这样的设置:

<select class="form-control col-md-6" ng-model="selectedProvince" ng-options="province for province in provinceArr"></select>
<select class="form-control col-md-6" ng-model="selectedCity" ng-options="city for city in cityArr"></select>

从这个例子我们看到,相比于传统前端开发方式那种手动监听事件,手动更新DOM的方式,使用数据绑定做数据联动简直太容易了。如果要把这个例子改造成三级联动,只需对selectedCity也做一个监控就行了。

一个综合的例子

了解了这些细节之后,我们可以把它们结合起来做一个比较实际的综合例子。假设我们在为一家小店创建雇员的管理界面,其中包含一个雇员表格,以及一个可用于添加或编辑雇员的表单。

雇员包含如下字段:姓名,年龄,性别,出生地,民族。其中,姓名通过输入框输入字符串,年龄通过输入框输入整数,性别通过点选单选按钮来选择,出生地用两个下拉框选择省份和城市,民族可以选择汉族和少数民族,如果选择了少数民族,可以手动输入民族名称。

这个例子恰好能把我们刚才讲的绑定全部用到。我们先来看看有哪些绑定关系:

  • 雇员表格可以选中某行,该行样式会高亮
  • 如果选中了某行,其详细数据将会同步到表单上
  • 如果点击过新增或修改按钮,当前界面处于编辑中,则表单可输入,否则表单只读
  • 修改和删除按钮的可点击状态,取决于表格中是否有选中的行
  • 出生地点的省市下拉框存在联动关系
  • 民族名称的输入框,其可见性取决于选择了汉族还是少数民族的单选按钮
  • 新增修改删除、确定取消,这两组按钮互斥,永远不同时出现,其可见性取决于当前是否正在编辑。
  • 确定按钮的可点击状态,取决于当前表单数据是否合法

如果想做得精细,还有更多可以使用绑定的地方,不过上面这些已经足够我们把所有知识用一遍了。

这个例子的代码就不贴了,可以自行查看。

数据绑定的拓展运用

现在我们学会了数据绑定,可以借助这种特性,完成一些很别致的功能。比如说,如果想在页面上用div模拟一个正弦波,只需要把波形数据生成出来,然后一个绑定就可以完成了。

$scope.staticItems = [];

for (var i=0; i<720; i++) {
    $scope.staticItems.push(Math.ceil(100 * (1 + Math.sin(i * Math.PI / 180))));
}

如果我们想让这个波形动起来,也很容易,只需要结合一个定时器,动态生成这个波形数据就可以了。为了形成滚动效果,当波形采点数目超过某个值的时候,可以把最初的点逐个拿掉,保持总的数组长度。

$scope.dynamicItems = [];

var counter = 0;

function addItem() {
    var newItem = Math.ceil(100 * (1 + Math.sin((counter++) * Math.PI / 180)));

    if ($scope.dynamicItems.length > 500) {
        $scope.dynamicItems.splice(0, 1);
    }

    $scope.dynamicItems.push(newItem);

    $timeout(function () {
        addItem();
    }, 10);
}

addItem();

这个例子对应的HTML代码如下:

<div ng-controller="WaveCtrl">
    <style>
        .wave-item {
            float: left;
            width: 1px;
            background-color: #ffab51;
        }
    </style>
    <div>
        <div ng-repeat="item in staticItems track by $index" class="wave-item" ng-style="{'height': item+'px'}"></div>
    </div>
    <div style="clear: left">
        <div ng-repeat="item in dynamicItems track by $index" class="wave-item" ng-style="{'height': item+'px'}"></div>
    </div>
</div>

有时候我们经常看到一些算法可视化的项目,比如把排序算法用可视化的方式展现出来,如果使用AngularJS的数据绑定,实现这种效果可谓易如反掌:

<div ng-controller="SortCtrl">
    <style>
        .data-item {
            float: left;
            width: 20px;
            background-color: #c0c0c0;
            border: 1px solid #080808;
        }
    </style>
    <button ng-click="sort()">Sort</button>
    <div>
        <div ng-repeat="item in arr track by $index" class="data-item" ng-style="{'height': item*5+'px'}"></div>
    </div>
</div>
$scope.arr = [2, 4, 5, 63, 4, 5, 55, 2, 4, 43];

$scope.sort = function () {
    if (!sort($scope.arr)) {
        $timeout(function() {
            $scope.sort();
        }, 500);
    }
};

function sort(array) {
    // 喵的,写到这个才发现yield是多么好啊
    for (var i = 0; i < array.length; i++) {
        for (var j = array.length; j > 0; j--) {
            if (array[j] < array[j - 1]) {
                var temp = array[j - 1];
                array[j - 1] = array[j];
                array[j] = temp;

                return false;
            }
        }
    }

    return true;
}

看,就这么简单,一个冒泡排序算法的可视化过程就写好啦。

甚至,AngularJS还允许我们在SVG中使用数据绑定,使用它,做一些小游戏也是很容易的,比如我写了个双人对战的象棋,这里有演示地址,可以查看源码,是不是很简单?

小结

刚才我们已经看到数据绑定的各种使用场景了,这个东西带给我们的最大好处是什么呢?我们回顾一下之前写Web界面,有一部分时间在写HTML和CSS,一部分时间在写纯逻辑的JavaScript,还有很多时间在把这两者结合起来,比如各种创建或选取DOM,设置属性,添加等等,这些事情都是很机械而繁琐的,数据绑定把这个过程简化了,代码也跟着清晰了。

数据绑定是一种思维方式,一切的核心就是数据。数据的变更导致界面的更新,如果我们想要更新界面,只需改变数据即可。

佛曰:命由己造,相由心生,世间万物皆是化相,心不动,万物皆不动,心不变,万物皆不变。

佛曰:种如是因,收如是果,一切唯心造。

本章所涉及的所有Demo,参见在线演示地址

代码库

演讲幻灯片下载:点这里

@xufei xufei added the ng-tutor label Jan 12, 2015

@xufei

This comment has been minimized.

Copy link
Owner Author

commented Jan 12, 2015

明天晚上在苏宁大学课堂上面讲

@Galen-Yip

This comment has been minimized.

Copy link

commented Jan 12, 2015

飞哥真高产

@dolymood

This comment has been minimized.

Copy link

commented Jan 13, 2015

赞~

@weill01

This comment has been minimized.

Copy link

commented Jan 13, 2015

@kunkun12

This comment has been minimized.

Copy link

commented Jan 13, 2015

@liminjun

This comment has been minimized.

Copy link

commented Jan 13, 2015

对angularjs的双向绑定有了重新认识。但是如果监控的变量非常多,对应用的性能影响是不是很大。

@fouber

This comment has been minimized.

Copy link

commented Jan 13, 2015

飞哥真高产

@xufei

This comment has been minimized.

Copy link
Owner Author

commented Jan 13, 2015

苏宁的非前端部门的兄弟们,欢迎加入我们前端协会,每周四晚上都有内部分享,不定期有面向所有体系的公开分享。

@dyygtfx

This comment has been minimized.

Copy link

commented Jan 13, 2015

最近再看 ng,前来学习

@markyun

This comment has been minimized.

Copy link

commented Jan 13, 2015

mark一下,上班路上看。

@yibuyisheng

This comment has been minimized.

Copy link

commented Jan 13, 2015

@xufei 大神可以来一篇详细介绍$digest的博文吗?,期待哦。 @liminjun 对某些使用一次的,可以试试{{ ::variable }}

@GZShi

This comment has been minimized.

Copy link

commented Jan 13, 2015

很详细,非常感谢。

@soulteary

This comment has been minimized.

Copy link

commented Jan 13, 2015

象棋单位的模块继承蛮巧的

@xufei

This comment has been minimized.

Copy link
Owner Author

commented Jan 14, 2015

@yibuyisheng 这个系列的后面会讲。之前我翻译的这篇你看过吗?

https://github.com/xufei/Make-Your-Own-AngularJS/blob/master/01.md

@xufei

This comment has been minimized.

Copy link
Owner Author

commented Jan 14, 2015

@soulteary 哪里巧了。。。04年写的那个就是这么写的,这部分基本无改变

@xufei

This comment has been minimized.

Copy link
Owner Author

commented Jan 14, 2015

昨天讲的过程中,有同事提出这么一些问题,记录一下:

  1. 与GWT这类技术的对比是怎样的?
    回答:GWT的界面层是用Java代码来写,写的效率并不如HTML这个体系高,跟Angular相比,开发效率也还是低一个档次,而且如果有精通CSS的人员,无法有效参与项目进行改进。但我并不反对它的编译过程,大型项目要使得源码可控,经过一个工程转换是可以接受的。
  2. 它的使用状况如何?
    回答:在这里,我让大家猜测了一下Google内部使用Angular的项目数量,有猜几个的,几十的,几百的,其实是有1600多,这个数据在另外一篇翻译的文章中有提到。猜得最接近的得到了一本书。

此外,还让大家猜测了一下文中那个综合业务实例的JS代码行数,猜得最接近的也得到了一本书。

@yibuyisheng

This comment has been minimized.

Copy link

commented Jan 14, 2015

@xufei 不好意思,之前没看过,现在正在阅读,写的很给力,很详细

@yibuyisheng

This comment has been minimized.

Copy link

commented Jan 14, 2015

在angular源码中有这么一段注释:

The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only that:

       - it will execute after the function that scheduled the evaluation (preferably before DOM rendering).
       - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after `expression` execution.

个人感觉要不要在《构建自己的AngularJS,第一部分:Scope和Digest》中强调第二点。因为我之前不理解$evalSync,导致经常出现“$digest已经在执行”的异常,我想$evalSync能解决我的问题(根据注释第二点)。在《构建自己的AngularJS,第一部分:Scope和Digest》这篇文章中,个人感觉这块叙述的不是太清晰。

@soulteary

This comment has been minimized.

Copy link

commented Jan 14, 2015

@xufei 我觉得棋子属性抽象蛮赞的,给我做的话,估计偷懒就绑定两个对象到作用域顶级对象上了...

@johndeng

This comment has been minimized.

Copy link

commented Jan 14, 2015

赞!

@xufei

This comment has been minimized.

Copy link
Owner Author

commented Jan 14, 2015

@yibuyisheng 你的大部分问题应该可以简单粗暴地使用:

$timeout(function() {
  //aaa
  //$scope.$digest();
}, 0);

来实现。

@atian25

This comment has been minimized.

Copy link

commented Jan 14, 2015

通熟易懂, 👍
再读一遍~

@yibuyisheng

This comment has been minimized.

Copy link

commented Jan 14, 2015

@xufei 谢谢您的指导。根据您的指导,结合angular源码,基本理解了digest机制,这下应该不会再使用网上的那个"safeApply"了。

@atian25

This comment has been minimized.

Copy link

commented Jan 14, 2015

@yibuyisheng safeApply是反模式,官方wiki明确指出了。
apply一般也用不到,只有在Directive里面。 如果遇到交错的地方用$timeout(fn, 0)

@yibuyisheng

This comment has been minimized.

Copy link

commented Jan 14, 2015

@atian25 目前个人认为可以不用$timeout,我的理解是:
digest过程中并没有机会执行非digest代码(即event loop不会切换到其他js代码片段上面去),但是有这种情况:controller的构造函数被放置在了asyncQueue当中,这就直接导致了在这个函数执行的时候,就会是已经处于digest(即beginPhase已经被调用过)过程中的状态,所以如果在这个函数里面直接调用$digest方法,肯定就抛出“digest in progress”的异常了,但是在这个函数中通过$timeout间接地调用digest方法,也是不会抛异常的(个人认为这样干实在不优雅)。
我猜测肯定还有其他类似于controller构造函数的函数,这个等待后续研究。这就是我目前认为的主要原因。
理解有误的地方,还望大牛们指正,谢谢

@atian25

This comment has been minimized.

Copy link

commented Jan 15, 2015

我的意思是,理解digest后,就几乎不会遇到digest in progress的报错了。
但某些情况下,一个方法可能会被内部和外部调用, 这时候$timeout就很有用了。
$timeout就是简单的丢到下一个digest去, 所以@xufei 说简单粗暴很有效。

@yibuyisheng

This comment has been minimized.

Copy link

commented Jan 15, 2015

@atian25 嗯,关键还是要理解digest原理,至于用什么解决方案,随自己喜好。谢谢你的耐心回复

@gnehcc

This comment has been minimized.

Copy link

commented Jan 20, 2015

请教一个问题:
github_question

截图的代码里边点击Add book2和AddItem, 页面上面ng-repeat迭代的数据都会自动更新(双向绑定自动更新很合理嘛),但是Add book按钮通过指令的方式调用service更改数据后同样在controller里面对$scope的books进行了更改,页面上的数据却没自动更新, 必须手动调用apply方法才能达到预期更新的效果
困扰好久,求指导哇 :(

@xufei

This comment has been minimized.

Copy link
Owner Author

commented Jan 20, 2015

@iamwucheng 因为ng-click内部帮你调用了一下。凡是数据想要更新到界面上,都必须通过$digest或者$apply,内置的一些东西不需要自己调用,是因为它们内部帮你调了,自己在指令里用原生事件操作的东西,需要手动调用。

@gnehcc

This comment has been minimized.

Copy link

commented Jan 20, 2015

@xufei 谢谢, 运行demo对比的时候有这样的猜想,但是找源码里面想找ng-click执行的过程相关的代码进行验证的时候就是找不到,在publishExternalAPI的方法里面并没有找到添加ng-click的逻辑(个人觉得应该会在添加指令的地方至少有,ng-click很显然应该是个指令)
颇傻, 补充一点东西:
git-question2
看着“ngAttributeAliasDirectives‘, ”ngEventDirectives“这两个指令有点很奇怪的感觉, 顺着找了一下, 没错的话应该是第二个吧?(截图里面的哪一段)
貌似还用到了自带的解析器?

@dolymood

This comment has been minimized.

Copy link

commented Jan 20, 2015

@gnehcc

This comment has been minimized.

Copy link

commented Jan 20, 2015

@dolymood 嗯, 看到了,谢谢, 最近的版本里面逻辑还变了点

@gnehcc

This comment has been minimized.

Copy link

commented Jan 20, 2015

@xufei

This comment has been minimized.

Copy link
Owner Author

commented Jan 20, 2015

@iamwucheng 看我这篇也可以:#10

@gnehcc

This comment has been minimized.

Copy link

commented Jan 20, 2015

@xufei 赞ヾ(´∀`)ノ

@m1013923728

This comment has been minimized.

Copy link

commented Aug 17, 2015

学习了,点赞!!!!!!!!!!!!

@MichaelYgZhang

This comment has been minimized.

Copy link

commented Aug 24, 2015

👏 👍

@hanxiansen

This comment has been minimized.

Copy link

commented Dec 3, 2015

6得不行,赞一个!

@aicekiller

This comment has been minimized.

Copy link

commented Dec 21, 2015

您好,请教一下,那个综合例子中,民族(editingEmployee.minority)是如何通过这个值,转化成汉族或者(editingEmployee.nation),我是在controller中增加format方法来完成的,我并没有在您的employee.js中看到有任何format方法,是有其他更简洁方法吗?

@xufei

This comment has been minimized.

Copy link
Owner Author

commented Dec 21, 2015

@aicekiller 没有使用minority做显示的转换,这里只是用它来做了radio的选中和输入框的显示隐藏,真正的值在nation里,真实用的话,是需要做个format的,不然提交的时候不好办。

@aicekiller

This comment has been minimized.

Copy link

commented Dec 21, 2015

@xufei 谢谢

@phxcmos

This comment has been minimized.

Copy link

commented Jun 1, 2016

请教一个问题:

<div ng-app="myApp" ng-controller="myCtrl">

  <ul class="list-group">
    <li class="list-group-item" ng-repeat="(id,x) in names" ng-click="item=li(id);selected.id=id">
      {{x.name}}
    </li>

    <div>{{selected.id}}</div>
    <div>{{item}}</div>
    <div>{{str}}</div>


  </ul>
</div>
<script>
  var app = angular.module('myApp', []);

  app.controller('myCtrl', function($scope) {
    $scope.names = [{
      name: 'a'
    }, {
      name: 'b'
    }, {
      name: 'c'
    }];
    $scope.selected = {
      id: 0
    };
    $scope.item = 0;
    $scope.li = function(n) {
      $scope.item = n
    };

  });

</script>

请问上面这段代码我在li的ng-click事件中直接用item=id赋值的话为什么item值不会改变?用controller里面定义的li()方法赋值后就能获取到,或者对controller里面定义的selected对象赋值也能获取到,这是什么原因?谢谢

@yibuyisheng

This comment has been minimized.

Copy link

commented Jun 2, 2016

@manolitowang 隐约记得 ng-click 会创建一个local scope。

@phxcmos

This comment has been minimized.

Copy link

commented Jun 2, 2016

@yibuyisheng 谢谢,你是对的,我看到了相关的介绍

@kuitos

This comment has been minimized.

Copy link

commented Jun 2, 2016

@yibuyisheng @manolitowang ng-click并不会创建一个新作用域。。。这里出现这个问题是因为ng-repeat创建了一个继承作用域,且里面是写操作所以不会更新外部的item

@yibuyisheng

This comment has been minimized.

Copy link

commented Jun 2, 2016

@kuitos 噢,记得错了。刚看了源码,对于 ngClick 只是注入了一个 local 对象:https://github.com/angular/angular.js/blob/a4e60cb6970d8b6fa9e0af4b9f881ee3ba7fdc99/src/ng/directive/ngEventDirs.js#L64 ,并没有创建新 scope 。

@hardfist

This comment has been minimized.

Copy link

commented Aug 3, 2016

写得真好!冒泡排序的实现貌似有点问题,似乎应该把i和j提出sort外,否则每次运行sort都需要从头开始排序,提出去后多个sort之间能保留上一次的状态。

@21-rock

This comment has been minimized.

Copy link

commented Dec 8, 2016

非常感谢你,这篇教程写的真棒!

@daihere1993

This comment has been minimized.

Copy link

commented Dec 10, 2016

看了ng-repeat的源码,想问一下您为什么在遍历collection的时候缺省使用'valueType: value'作为索引,这点我不是特别理解,求指点一下,谢谢。 @xufei

@hellochenk

This comment has been minimized.

Copy link

commented Feb 21, 2017

看完菜鸟的教程,然后刷完大漠老师的视频,然后再看这些内容。哇,这种感觉太爽了

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.