ngModel 值不更新/显示
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 值不更新/显示的更多相关文章
- ng-model 数据不更新 及 ng-repeat【ngRepeat:dupes】错误
一.ng-include 引入的文件中 ,ng-model 数据不更新 例如, $scope.username = “Jones” .此时,在 ng-include 引入的文件中,直接使用 ng-m ...
- vue 值的更新
用了vue开发了一段时间,基本上感觉都是比较好用的一个框架,刚开始还是会存在一些小坑的东西,这里先浅谈下值的更新问题. 1.本组件的一些变量,或者是主view(路由页面)一些值,是通过vuex 中st ...
- 利用Python计算π的值,并显示进度条
利用Python计算π的值,并显示进度条 第一步:下载tqdm 第二步;编写代码 from math import * from tqdm import tqdm from time import ...
- 【AngularJS】解决ng-if中的ng-model值无效的问题(转)
from:http://blog.csdn.net/u013451157/article/details/60866210 与其他指令一样,ng-if指令也会创建一个子级作用域,因此,如果在ng-if ...
- HUD.2795 Billboard ( 线段树 区间最值 单点更新 单点查询 建树技巧)
HUD.2795 Billboard ( 线段树 区间最值 单点更新 单点查询 建树技巧) 题意分析 题目大意:一个h*w的公告牌,要在其上贴公告. 输入的是1*wi的w值,这些是公告的尺寸. 贴公告 ...
- 以A表中的值快速更新B表中记录的方法
1.问题描述 有两张表,A表记录了某些实体的新属性,B表记录了每个实体的旧属性,现在打算用A中的属性值去更新B中相同实体的旧属性,如下图所示: 类似这样的需求,怎样做比较高效呢? 2.制作模拟数据 ...
- Golang使用proto3协议导致零值字段不显示
Golang使用proto3协议导致零值字段不显示 问题描述 proto协议生成的结构体如果使用直接转成json会导致零值字段不显示,这样的json是有毛病的,可以使用如下方法解决 示例Demo pa ...
- [转]Hibernate设置时间戳的默认值和更新时间的自动更新
原文地址:http://blog.csdn.net/sushengmiyan/article/details/50360451 Generated and default property value ...
- LoadRunner参数化之数据取值和更新方式
其实看LR已经很久了,每次看到参数化的取值更新时,都没有看透,了解个大概就为止了,也确实挺搞脑子的. 现在理解下来 分成2部分 取值方式 Select next row 如何从数据列表中取值 Seq ...
随机推荐
- SQL Server 集合处理
UNION ALL 返回两个结果集中所有的行,返回结果集中会存在重复行 UNION 返回两个结果集中去重的行,返回结果集中无重复行 INTERSECT 返回两个结果集都有的行,返回结果集中无重复行 E ...
- Java中抽象类和接口的区别
转载自:http://dev.yesky.com/436/7581936.shtml 在Java语言中, abstract class 和interface 是支持抽象类定义的两种机制.正是由于这两种 ...
- linux下svn常用命令
(如果是第一次提交文件,很可能会出现“svn:'.'不是工作副本”,即当前目录不是工作副本,这个时候需要用到import: eg:svn import . url) 1.将文件checkout到本地目 ...
- 《UML大战需求分析》阅读笔记6
流程分析三剑客之总结 顺序图,活动图相类似表示活动,状态机图表示状态.分析特点: 顺序图: 强调角色之间的交互,信息明确: 从上到下,从左到右,按时间顺序: 不适合表达复杂特殊情况(循环分支,条件分支 ...
- Linux 查看进程、清理缓存、查看磁盘空间、查看宽带的命令
一.查看进程 查看所有的进程命令:ps 查看指定的进程命令:ps -ef|grep java (java 指的是服务名称) 结束进程命令:kill -9 9028 (9028指的是PID) 二.清理 ...
- px4flow通过iic读取具体寄存器数据程序
底层通信用了昨天写好的iic,今天结合官方资料成功读出所有指定寄存器的数据附上源码 include.h主要包括了一些stm32 IO控制的宏定义,具体参考正点原子所有例程中都有的sys.h头文件 in ...
- 用Java程序判断是否是闰年
我们知道,(1)如果是整百的年份,能被400整除的,是闰年:(2)如果不是整百的年份,能被4整除的,也是闰年.每400年,有97个闰年.鉴于此,程序可以作以下设计: 第一步,判断年份是否被400整除, ...
- Kickstart/Anaconda实现自动化安装原理探究
原网页地址:http://molinux.blog.51cto.com/2536040/548247#55918... 内容概要: 1. 系统安装基本流程图示2. Anaconda简介3. K ...
- Windows 商店应用中使用 SharePoint REST API
前面一篇我们介绍了 Office 365 REST API 的官方工具的使用,本篇我们来看一下 SharePoint REST API 本身的描述.结构和使用方法,以及一些使用经验. 首先来看看Sha ...
- UVALive 4329 Ping pong(树状数组)
题目链接:http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=13895 题意:一条街上住有n个乒乓选手,每个人都有一个技能值,现在 ...