angular中的$scope是页面(view)和数据(model)之间的桥梁,它链接了页面元素和model,也是angular双向绑定机制的核心。

而ngModel是angular用来处理表单(form)的最重要的指令,它链接了页面表单中的可交互元素和位于$scope之上的model,它会自动把ngModel所指向的model值渲染到form表单的可交互元素上,同时也会根据用户在form表单的输入或交互来更新此model值。

在源码中,model值的格式化、解析、验证都是由ngModel指令所对应的控制器ngModelController来实现的。

在笔者所维护的国内ng群中,经常被问到一个问题:

    为什么我的ng-model=“xxx”值不能在页面显示了呢?

对于ngModel的这类问题主要分为两类:

  • model值不满足表单验证条件,所以angular不会渲染它
  • 由于JavaScript特殊的原型链继承机制,对$scope中属性的赋值并不能更新到父$scope

在本节中,我们将会详细分析此类问题,借此深入剖析ngModel的工作原理。

验证引起的model值不显示

我们先来看一个修改商品数量的例子,要求为必须输入1-100的个数;

下面是对应的html代码:

<body class="container">
<div ng-controller="DemoCtrl as demo">
<div ng-form="form" class="form-horizontal">
<div class="form-group" ng-class="{'has-error': form.amount.$invalid }">
<label for="amount">Amount</label>
<!-- 这个input将工作不正常 -->
<input id="amount" name="amount" type="number" ng-model="demo.amount" class="form-control" placeholder="1 - 100" min="1" max="100"/>
</div>
</div>
</div>
</body>

javascript代码:

angular.module("com.ngbook.demo", [])
.controller("DemoCtrl", [function(){
var vm = this; vm.amount = 0; return vm;
}]);

在代码中我们已经为ngModel变量amount赋值了整数“0”,可是界面显示效果仍然显示”1 – 100”的placeholder(如下图)。

下面是关于angular number组件ngModel转换函数代码:

var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/;

function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
textInputType(scope, element, attr, ctrl, $sniffer, $browser); ctrl.$parsers.push(function(value) {
var empty = ctrl.$isEmpty(value);
if (empty || NUMBER_REGEXP.test(value)) {
ctrl.$setValidity('number', true);
return value === '' ? null : (empty ? value : parseFloat(value));
} else {
ctrl.$setValidity('number', false);
return undefined;
}
}); addNativeHtml5Validators(ctrl, 'number', numberBadFlags, null, ctrl.$$validityState); ctrl.$formatters.push(function(value) {
return ctrl.$isEmpty(value) ? '' : '' + value;
}); if (attr.min) {
var minValidator = function(value) {
var min = parseFloat(attr.min);
return validate(ctrl, 'min', ctrl.$isEmpty(value) || value >= min, value);
}; ctrl.$parsers.push(minValidator);
ctrl.$formatters.push(minValidator);
} if (attr.max) {
var maxValidator = function(value) {
var max = parseFloat(attr.max);
return validate(ctrl, 'max', ctrl.$isEmpty(value) || value <= max, value);
}; ctrl.$parsers.push(maxValidator);
ctrl.$formatters.push(maxValidator);
} ctrl.$formatters.push(function(value) {
return validate(ctrl, 'number', ctrl.$isEmpty(value) || isNumber(value), value);
});
}

ngModel作为angular双向绑定中的重要组成部分,负责view控件交互数据到$scope上model的同步。当然这里存在一些差异,view上的显示和输入都是字符串类型,而在model上的数据则是有特定类型的,如常用的int、float、Date、Array、Object等。ngModel为了实现数据到model的类型转换,在ngModelController中提供了两个管道数组$formatters和$parsers,它们分别是将model的数据转换为view交互控件显示的值和将交互控件得到的view值转换为model数据,它们都是一个数组对象,在ngModel启动数据转换时,会以UNIX管道式传递执行这一些列的转换。我们也可以手动的添加$formatters和$parsers的转换函数(unshift、push),当然在这里也是做数据验证的最佳时机,能够转换意味应该是合法的数据。

在number组件代码中,我们清晰看见:依次添加了对数字验证转换、最小值合法性验证、最大值合法验证。首先会启动$parsers转换,如果在转换过程中出现不合法验证则会设置ngModelController.$setValidity验证错误,则返回undefined。对于model数据到交互控件显示,同样也会经过$formatters转换管道,对于没有通过验证的逻辑,同样也会ngModelController.$setValidity设置验证错误,返回undefined,因此这不合法的model数据不会显示在交互控件上。

原型链继承问题

JavaScript中每个对象都会链接到一个原型对象,并且他可以从中继承属性。即使通过字面量创建的对象也会链接到Object.prototype,它是JavaScript中的标配对象。JavaScript的原型链继承相对于其他语言常见的继承,是一种另类的继承,它是实施于对象上的动态继承方式,而非常见的实施与类型class之上的静态继承体系。JavaScript的这种继承方式很灵活,一个对象可以被多个对象继承,而且他们共享同一实例对象,但理解起来显得格外复杂,从JavaScript原型和原型链可以看出它的复杂性。在Javascript中,每个函数都有一个原型属性prototype指向自身的原型,而由这个函数创建的对象也有一个proto属性指向这个原型,而函数的原型是一个对象,所以这个对象也会有一个proto指向自己的原型,这样逐层深入直到Object对象的原型,这样就形成了原型链。下面的是JavaScript原型继承基础原型和原型链展示图。

函数是由Function函数创建的对象,因此函数也有一个proto属性指向Function函数的原型。需要注意的是,真正形成原型链的是每个对象的proto属性,而不是函数的prototype属性。更多的内容关于原型和原型链的知识,请参考《Javascript模式》这本书。

JavaScript的原型链连接只在属性检索的时候才会启用,如果我们尝试去获取对象的某个属性值,但该对象没有此属性名,则JavaScript会试着从原型对象中获取该属性值。如果那个对象也没有该属性名,那么在继续从它的原型中寻找,依次类推,直到Object.prototype,如果仍然没有找到该属性值,则返回结果为undefined。不幸的是,这种原型链连接检索,只会在属性检索的的时候启用,并不会在更新属性值时启用,因此当我们对于基础类型(非引用对象上的属性,换句通俗的话来说,就是不会出现“.”运算符)的属性更新的时候,它并不能更新父对象的属性,替代方式是在自身对象上创建了该属性。这也是angular中对于基础类型的属性,不能在子controller中被修改的原因,导致在子controller中ngModel的更新并不会反应在父controller上。

下边是关于该问题的一个简化例子:

HTML:

<div ng-controller="ParentCtrl">
<div class="form-group">
<h4>Parent Controller:</h4>
<pre></pre>
<input type="text" ng-model="greet" class="form-control" />
</div>
<div ng-controller="ChildCtrl">
<div class="form-group">
<h4>Child controller:</h4>
<pre></pre>
<input type="text" ng-model="greet" class="form-control" />
</div>
</div>
</div>

JavaScript:

angular.module("com.ngbook.demo", [])
.controller("ParentCtrl", ["$scope", function($scope) { $scope.greet = "hello angular!"; }])
.controller("ChildCtrl", angular.noop);

从初始化显示效果中,我们能看出子$scope之继承了来自父$scope的greet属性,都显示为”hello angular!“。如果我们尝试利用父controller提供了input控件改变父$scope的greet属性,你也能看见子controller区域的显示也会被及时更新。对于ngController默认会使用原型链继承其父对象的属性,所有的$scope的根$scope或称祖$scope是来自ngApp节点创建的$rootScope,换句话说,$rootScope是万物之源,所有的$scope都直接或者间接继承至它。

当我们尝试去改变输入框的greet属性的时,则发生了下面的情况:子controller区域发生了更新,父controller区域却无法更新。因为上面所说的JavaScript的原型链检索并不对更新启用,对于基础类型JavaScript在自身对象(这里是子$scope)上创建了一个同名的变量。你也想可以从下面angular调试插件batarang截图中看出来。一旦利用子controller的input控件修改了greet属性,再次之后我再次尝试修改父controller区域的greet属性,子controller区别不会在像初始化时候那样及时同步了,它们之间完全独立了,各自拥有了自己的greet属性。

batarang插件截图

经过上面的例子分析,相信作为读者的你已经能够理解这类由于继承链引用问题导致的ngModel不能更新问题了,请记住:这是JavaScript原型继承的issue,并不是angular的issue。

那么我们在子controller中如何更新父controller的属性值呢?这个问题已经很简单了,issue的问题在于没有启用原型链的检索,那么如果我们将ngModel的属性变为引用对象,换句话说:在ngModel的属性值中加了“.”,那么在JavaScript的原型链检索就会启动了。

HTML:

<div ng-controller="ParentCtrl">
<div class="form-group">
<h4>Parent Controller:</h4>
<pre></pre>
<input type="text" ng-model="vm.greet" class="form-control" />
</div>
<div ng-controller="ChildCtrl">
<div class="form-group">
<h4>Child controller:</h4>
<pre></pre>
<input type="text" ng-model="vm.greet" class="form-control" />
</div>
</div>
</div>

JavaScript:

angular.module("com.ngbook.demo", [])
.controller("ParentCtrl", ["$scope", function($scope) { $scope.vm = {
greet: "hello angular!"
}; }])
.controller("ChildCtrl", angular.noop);

jsbin demo: http://jsbin.com/metufi/1/edit?html,js,output

这里在ngModel属性值多引入了“vm”变量,这个时候,不管我们尝试修改greet值,整个页面都会得到相应的同步。关于这个问题,作者更推荐使用angular 1.2后的controller as vm的方式解决,更多的信息请阅读《使用controller as vm方式.md》一节。

ngModel 值不更新/显示的更多相关文章

  1. ng-model 数据不更新 及 ng-repeat【ngRepeat:dupes】错误

    一.ng-include 引入的文件中 ,ng-model 数据不更新 例如, $scope.username = “Jones”  .此时,在 ng-include 引入的文件中,直接使用 ng-m ...

  2. vue 值的更新

    用了vue开发了一段时间,基本上感觉都是比较好用的一个框架,刚开始还是会存在一些小坑的东西,这里先浅谈下值的更新问题. 1.本组件的一些变量,或者是主view(路由页面)一些值,是通过vuex 中st ...

  3. 利用Python计算π的值,并显示进度条

    利用Python计算π的值,并显示进度条  第一步:下载tqdm 第二步;编写代码 from math import * from tqdm import tqdm from time import ...

  4. 【AngularJS】解决ng-if中的ng-model值无效的问题(转)

    from:http://blog.csdn.net/u013451157/article/details/60866210 与其他指令一样,ng-if指令也会创建一个子级作用域,因此,如果在ng-if ...

  5. HUD.2795 Billboard ( 线段树 区间最值 单点更新 单点查询 建树技巧)

    HUD.2795 Billboard ( 线段树 区间最值 单点更新 单点查询 建树技巧) 题意分析 题目大意:一个h*w的公告牌,要在其上贴公告. 输入的是1*wi的w值,这些是公告的尺寸. 贴公告 ...

  6. 以A表中的值快速更新B表中记录的方法

    1.问题描述 有两张表,A表记录了某些实体的新属性,B表记录了每个实体的旧属性,现在打算用A中的属性值去更新B中相同实体的旧属性,如下图所示: 类似这样的需求,怎样做比较高效呢? 2.制作模拟数据   ...

  7. Golang使用proto3协议导致零值字段不显示

    Golang使用proto3协议导致零值字段不显示 问题描述 proto协议生成的结构体如果使用直接转成json会导致零值字段不显示,这样的json是有毛病的,可以使用如下方法解决 示例Demo pa ...

  8. [转]Hibernate设置时间戳的默认值和更新时间的自动更新

    原文地址:http://blog.csdn.net/sushengmiyan/article/details/50360451 Generated and default property value ...

  9. LoadRunner参数化之数据取值和更新方式

    其实看LR已经很久了,每次看到参数化的取值更新时,都没有看透,了解个大概就为止了,也确实挺搞脑子的. 现在理解下来 分成2部分 取值方式  Select next row 如何从数据列表中取值 Seq ...

随机推荐

  1. 匿名访问ReportService报表服务器(一)

    我的数据库版本是sql server 2008 r2,系统环境是windows server2008. 对于sql server 2008 r2上报rs报表的匿名访问问题,我这边尝试过两个可行方案: ...

  2. 史上最全的常用iOS的第三方框架

    文章来源:http://blog.csdn.net/sky_2016/article/details/45502921 图像: 1.图片浏览控件MWPhotoBrowser       实现了一个照片 ...

  3. 手机GPS为什么能在室内定位?

      为什么手机在室内也能定位?大部分人知道手机会通过GPS进行定位,其实手机定位系统并不是和我们的RTK完全一样的,因为那样就无法解释为何在室内也能定位了,这里我来科普一下智能手机的那些定位方法.   ...

  4. babel 配置探究及错误解析

    前面的文章说到了react 15的一些特性,众所周知react搭配es6或者叫es2015的开发模式更加的方便快捷.不过提到es2015这个毕竟没有被浏览器广泛支持的规范,要想能够快快乐乐的应用起来, ...

  5. TCP头部结构

    3.2 TCP头部结构 TCP头部信息出现在每个TCP报文段中,用于指定通信的源端端口,目的端端口,管理TCP连接等,本节详细介绍TCP的头部结构,包括固定头部结构和头部选项. 3.2.1 TCP固定 ...

  6. rails4.2~devise邮箱测试

    1.由于网站无需验证,只需一封欢迎邮件,在config/intiailzers/devise.rb里面配置 config.allow_unconfirmed_access_for = nil #2.d ...

  7. SAS实验室之PROC TRANSPOSE

    首先,抛开SAS,回忆我们在数学课本上学习的转置是什么概念,转置如下图: 以上就是数学中的转置. 那么在SAS里该如何转置呢? 先看语法格式: PROC TRANSPOSE <DATA=inpu ...

  8. Struts2基础

    访问ServletApi (1)用ActionConten访问Api, ActionContent.getContext().getSession().put("user",&qu ...

  9. 基于OpenCv的人脸检测、识别系统学习制作笔记之三

    1.在windows下编写人脸检测.识别系统.目前已完成:可利用摄像头提取图像,并将人脸检测出来,未进行识别. 2.在linux下进行编译在windows环境下已经能运行的代码. 为此进行了linux ...

  10. Mac下git命令自动补全

    当我第一次在mac上安装git,[tab]补全装成功了,但是我没有记录,当我过一段时间在重装的时候,我已经忘记了,又是各种查资料,再次做一下简单的记录. 首先,我因为还是mac小白,所以使用Homeb ...