Angular 坑
一、ng-include指令
<div ng-include="views/include/footer.html"></div>
错在哪里,记得在第一次使用ng-include的时候,这样引用文件,始终在页面上都不显示footer.html的内容,后来明白ng-include需要的是一个变量,所以我们可以改写成
<div ng-include=" ’views/include/footer.html’ "></div>
或定义一个scope变量,如:scope变量,
如:
$scope.footerHtml="views/include/footer.html";
页面引用:
<div ng-include="footerHtml"></div>。
总之,最好的的办法就是使用指令解决。
二、angular 压缩
使用Javascript数组方式构造控制器:把要注入的服务放到一个字符串数组(代表依赖的名字)里,数组最后一个元素是控制器的方法函数:
Var BookCtrl = ['$scope′,′$http', function($scope,$http) {
/* constructor body */
}];
三、IE11下get请求缓存问题,页面绑定数据不及时更新的bug
在项目开发的过程中,我曾经发现过一个奇葩的问题,就是在IE11浏览器下,当点击某个button触发一个函数,从而改变ng-model绑定的某个变量时,明确变量的值已经改变,但是只有在F12开启开发者调试工具时,页面才能及时更新显示改变后的变量值,一旦F12开发者调试工具关闭,变量值将不更新。但是这一现象在chrome、firefox下确不曾见,究其原因竟是IE浏览器下ajax请求缓存的问题,后来把缓存禁用后,竟恢复正常了。具体解决方法就是:在配置路由的angularJS文件中,添加如下配置代码即可:
四、使用ng-repeat或ng-options进行数据循环时,track by的使用
在使用ng-repeat进行数据循环时,如果后端数据库采用的是moogodb的话,当 ng-repeat 的数组被替换时, 它默认并不会重新利用已有的 Dom 元素,而是直接将其全部删除并重新生成新的数组 Dom 元素,Dom 的频繁操作是非常不友好的,为什么 ng-repeat 不能利用已有的 dom 元素去更新数据呢?因为你没有把数组元素的标识属性告诉它,那么两次替换的时候它就没办法追踪了,所以当首次使用ng-repeat渲染出列表数据,再次请求渲染数据的时候,ng-repeat会往数组中每个元素加上$$hashKey属性,这个 key 是由 Angular 内部的 nextUid() 方法生成,类似数据库自增,但是是使用字符串。因此,我们使用 track by 来避免,
五、空字符和有空格的区别
最近做一个项目,应该来说比较简单的输入框验证ip地址而已,允许空值
首先是对ip地址进行检验匹配是否正确,开始我在指令中是这样写的。我输入空格后会提示输入有误,可是这就有一个问题了,我在输入框中删除空格后提示并不会消失,我就想应该先判断dns是否输入空格,可是各种姿势过后我发现没有办法区分是否输入空格。然后我想是不是angular的ng-model 对空格进行了处理,遂在本地进行了一个测试,监听ipt的值变化,发现空字符和有空格的字符串是有区别的,果然是ng-model对字符串进行了trim()操作
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<input
type="text"
name="item"
id='ipt' οninput="OnInput (event)"
onpropertychange="OnPropChanged (event)"
focused>
<div id='iptLength'></div>
</body>
<script type="text/javascript">
var ipt=document.getElementById('ipt')
var iptLength=document.getElementById('iptLength');
var value=ipt.value;
iptLength.innerHTML='当前字符串为'+value.length
function OnInput (event) {
value=ipt.value;
iptLength.innerHTML='当前字符串为'+value.length
}
</script>
</html>
接下来,通过google,发现需要加上ng-trim="false"这个指令就可以了,看来google才是程序员真爱
六、同一页面多次使用同一指令
$scope.assistData={}; //辅助对象,定义与业务和数据无关的变量
$scope.assistData.uploadShow=false; //上传的自定义指令显示还是隐藏
$scope.assistData.step=0; //上传的自定义指令初始化标志
$scope.assistData.type='administrator'; //管理员身份
//查看服务详情
$scope.goToDetail=function(resourceId){
//console.log(resourceId);
if(resourceId){
$scope.resourceId=resourceId;
$scope.assistData.uploadShow=true; //上传的自定义指令显示还是隐藏
$scope.assistData.step++; //
}else {
return false;
}
};
$scope.uploadFileDirectiveFinish=function(str){ //上传完成之后
$scope.navPuhs();
};
$scope.assistData.step1 = 0; //上传的自定义指令初始化标志
$scope.assistData.responseShow = false; //上传的自定义指令显示还是隐藏
//查看从表订单详情
$scope.goOrderListsDetail = function (relationId, resourceId) {
if(relationId&&resourceId){
$scope.assistData.step1++;
$scope.assistData.orderDetailShow = true;
$scope.relationId = relationId;
$scope.resourceId = resourceId;
}else {
return false;
}
};
<div ng-class="(assistData.uploadShow)||(assistData.orderDetailShow)?'
container-fluid page-content oneScreen hide':'container-fluid page-content oneScreen'">
</div>
<div>
<my-service-details ng-if="assistData.uploadShow"
fn="uploadFileDirectiveFinish(str)"
step="{{assistData.step}}"
type="assistData.type"
close="assistData.uploadShow"
id='resourceId'
intention="{{assistData.intention}}"
resource-status="{{assistData.resourceStatus}}"
ng-class="assistData.uploadShow?'':'hide'">
</my-service-details>
<order-lists-detail ng-if="assistData.orderDetailShow"
step1="{{assistData.step1}}"
close="assistData.orderDetailShow"
relation-id='relationId'
resource-id='resourceId'
type="assistData.type"
intention="{{assistData.intention}}"
resource-status="{{assistData.resourceStatus}}"
ng-class="assistData.orderDetailShow?'':'hide'">
</order-lists-detail>
</div>
说明: 同一页面触发点击事件在切换页面的的时候,最好添加一个显示隐藏开关,并且使用ng-if 这样会删除元素,以免指令内部查看图片的的指令冲突(原因是该指令是一个弹窗,使用的时元素ID ,同一页面不能出现两个ID)
七、自定指令在监听绑定对象时,获取取不到对象
首先,在自定义指令父级作用域中定义一个辅助对象,用于存储指令的监听点击次数的标记属性,然后绑定到指令上,在指令内部监听此标记属性的变化
【父级controller】
$scope.assistData={};//辅助对象,定义与业务和数据无关的变量
$scope.assistData.step2=0;//上传的自定义指令初始化标志
$scope.clickFeedBackBtn = function (relationId,nickName) {
$scope.dialogParams = {};//提交反馈
$scope.par = false;
$scope.sup = false;
$scope.passParameter.nickName = nickName;
//console.log($scope.tradeState);
$('#dialogIntentionComment').val('').removeAttr('placeholder').removeClass('border-color');
$scope.dialogInvestorLists = null;
if (!$scope.dialog.resourceId && !relationId) {
return false;
} else {
if(!angular.equals({},$scope.passParameter)){
$scope.dialogParams.resourceId = $scope.dialog.resourceId;//请求参数
$scope.dialogParams.serviceType = $scope.dialog.serviceType;//设置请求参数
$scope.dialogParams.relationId = relationId;//设置请求参数
getRecipient.recipientParams.resourceId = $scope.dialog.resourceId;
getRecipient.recipientParams.serviceType = $scope.dialog.serviceType;
getRecipient.getServiceResourceRecipient().then(function (data) {
if (data && (data.retCode = '200')) {
$scope.dialogInvestorLists = data.data;
$scope.recipientIdChange();
feedBackMd();
$scope.assistData.step2++;//因为指令是一个弹窗,所以在此做处递增
}
});
}else {
return false
}
}
};
function feedBackMd() {
$("#feedBackModal").modal({backdrop: 'static', keyboard: true});//js
}
【Html 页面】
<!--意见反馈弹窗-->
<feed-back dialog-investor-lists="dialogInvestorLists"
pass-parameter="passParameter"
fn="feedbackLists(resourceId, e, serviceType,index,tradeState)"
view-state="viewState"
recipient-id-change="recipientIdChange()"
sup="sup"
par="par"
step="{{assistData.step2}}"
nav-puhs="navPuhs()"
tap-state="tradeState"
err-msg="errMsg"
suc-msg="sucMsg"
dialog-params="dialogParams">
</feed-back>
【指令内部逻辑】
scope: {
dialogInvestorLists: '=dialogInvestorLists',//接收者列表数据
dialogParams: '=dialogParams',//提交反馈信息参数
passParameter: '=passParameter',//保存列表传递的参数及身份(媒介而已)
fn: '&',//跟踪反馈列表刷新
sup: '=',//后台服务推送---供应商显示
par: '=',//后台服务推送---合伙人显示
tapState: '=',//意见反馈选择时不同table的显示
viewState: '=',//身份标记
errMsg: '=',//错误提示信息
sucMsg:'=',
navPuhs: '&',//服务列表刷新,
step:'@',
recipientIdChange: '&'//接收人改变事件
},
$scope.$watch('step', function (e) {//指令js开始信号(可以区分是上传编辑还是详情)
if (e) {
if($scope.passParameter.serviceType){
//控制显示隐藏
$scope.serviceTyp=$scope.passParameter.serviceType;
$scope.nickName=$scope.passParameter.nickName;
}
}
});
八、Angular作用需要注意的坑
以下情况下,就会遇到作用所带来的坑,先来看看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // html <div ng-controller="OuterCtrl"> <span ng-bind="a"></span> <div ng-controller="InnerCtrl"> <span ng-bind="a"></span> <button ng-click="a=a+1">递增</button> </div> </div>
// javascript function OuterCtrl($scope) { $scope.a = 1; } function InnerCtrl() {} |
上面的代码就是一个极其简单的双向绑定的例子。页面加载以后,外部 div 和内部 div 中都会显示1。当按了递增按钮之后,会发现只有内部的1变成了2
查看官方文档,最后发现确实有解决方法,比如说将 a 写成一个对象的属性 data.a :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // html <div ng-controller="OuterCtrl"> <span ng-bind="data.a"></span> <div ng-controller="InnerCtrl"> <span ng-bind="data.a"></span> <button ng-click="data.a=data.a+1">递增</button> </div> </div>
// javascript function OuterCtrl($scope) { $scope.data = { a: 1 }; } function InnerCtrl() {} |
然后发现,居然可以工作了,两个数字都跟着递增了,研究内部的原理。
这个坑是为什么会出现为什么写成对象的属性就能解决。其实知道了原理之后,是很容易理解的。
angular的内外层作用域之间是基于javascript的原型链来实现的继承,并且只使用了原型链继承方法,子类原型对象中的引用类型值和父类实例对象中的引用类型值是引用的同一个,而基本类型值则会覆盖父类对象中的基本类型值,
总之这里,我们可以这样来看待第一个例子:
1 2 3 4 5 6 7 8 9 | function OuterCtrl() { this.a = 1; } function InnerCtrl() {}
var outer = new OuterCtrl(); InnerCtrl.prototype = outer; var inner = new InnerCtrl(); inner.a = inner.a + 1; |
这里,我们将内部的控制器的构造函数的原型对象设置为外部作用域对象,这样产生的内部作用域对象就继承了外部对象 outer 中的属性 a 。这个属性是个基本类型值,对内部对象的属性 a 进行访问的时候,由于内部对象本身不存在这样的属性,就会从它的原型对象中查找,原型对象 outer 中存在这样的属性,于是返回值,没有问题,但是如果我们对内部作用域对象的 a 属性赋值的话,问题就出来了。 inner.a = inner.a + 1; 这个语句实际上进行了前面说的查找 a 属性值的过程,然后将返回的值赋值给了内部作用域对象的 a 属性,由于 a 属性是不存在的,因此创建了一个 a 的基本类型值属性,屏蔽了外层作用域对象 outer 中的 a 属性,这个坑就这么出来了。
因此,如果我们将基本类型值属性换成引用类型值属性的话,问题就能够得到解决,因为他们两个对象对应的属性是引用的同一个引用类型值,不管在哪对它进行修改都会反应在所有引用他的对象上。
JavaScript值类型和引用类型有哪些
(1)值类型:数值、布尔值、null、undefined。
(2)引用类型:对象、数组、函数。
原型链
对象具有‘自有属性’,也有一些属性是从原型对象继承而来。
假设有一个对象O的属性x,如果O中不存在x,那么继续会在O的原型对象中查询这个属相x,如果原型对象中没有属性x,就会在原型对象的原型对象上查询,直到找到x或查询到一个对象是null的对象为止,这个链就可以实现属性的继承。
假设给对象O的属性X赋值,如果O中已经有了这个属性(这个属性不是继承而来的),那么这个赋值操作只改已有的属性X的值。
如果O中不存在这个属相X,那么赋值操作会给O添加一个新的属性X,如果之前的O继承自属性X(属性X是继承而来的),那么这个属性就会被新创建的同名属性覆盖。
JavaScript 中只有查询属性时才会体会到继承的存在,而设置属性则和继承无关,属性赋值操作,它总是在原始对象上创建属性或对属性赋值,而不会修改原型链。
九、Disabled 无法正常工作
下面代码displed进行绑定时会出现无法正常工作,采用ng-display才可以正常工作。
<span style="font-size:18px;">
Click me to toggle:
<input type="checkbox" ng-model="checked"><br/>
<button ng-model="button" disabled="{{checked}}">Button</button>
<!-- 无法正常工作 -->
<button ng-model="button" ng-disabled="checked" >Button</button>
<!-- 可以正常工作 -->
</span>
原因是:HTML规范不要求浏览器对布尔型属性必须给出值,例如disabled (它们存在表示true,不存在表示false)。如果我们放置了一个Angular动态表达式到这样的属性上,在浏览器删除属性时绑定信息将会丢失。ngDisabled指令解决了disabled属性存在的这个问题。这个指令不会被浏览器删除,并提供了一个永久的可靠的地方存放绑定信息。
十、关于ng-options获取id值的问题
使用ng-options绑定后dom结构中options的value值为“0,1,2...”,无法自定义设置。
获取id值有两种方式:
$scope.cities = [
{name: 'Seattle',id:'999'},
{name: 'San Francisco',id:'888'},
{name: 'Chicago',id:'777'},
{name: 'New York',id:'666'},
{name: 'Boston',id:'555'}
];
//第一种方式
<select ng-model="sel" ng-options="city.name for city in cities">
<option value="">Choose City</option>
</select>
{{sel.id}}
//上面代码中会把city.name展示到option中
//且把选中的city绑定到sel。通过sel.id就能获取选中的id值。
//第二种方式
<select ng-model="cityId" ng-options="city.id as city.name for city in cities">
<option value="">Choose City</option>
</select>
{{cityId}}
//上面代码会把选中的city的id值绑定到cityId。通过cityId就能获取选中的id值。
//第三种方式
<div class="form-group">
<label>地区选择:</label>
<select
class="form-control"
ng-model="params.provinceCode" ng-change="selectProvince()">
<option value="">全部</option>
<option
value="{{x.province}}"
ng-repeat="x in allArea"
ng-bind="x.province">
</option>
</select>
<select class="form-control" ng-model="params.cityCode">
<option value="">请选择</option>
<option value="{{x.city}}"
ng-repeat="x in Citys"
ng-bind="x.city">
</option>
<</select>
</div>
//地区三级联动开始
getData.sessionData("allArea").then(//全国所有地区
function (data) {
if (data.retCode = 200) {
$scope.allArea = data.data.all;
// console.log($scope.allArea);
}
}
);
$scope.$watch("params.provinceCode", function (e) {
if (!e) {
if ($scope.params.provinceCode) {
delete $scope.params.provinceCode;
}
if ($scope.params.cityCode) {
delete $scope.params.cityCode;
}
return;
}
angular.forEach($scope.allArea, function (v) {
if (v.areaCode == e) {
$scope.Citys = v.citys;
return;
}
});
});
十一、angularJs使用$window控制页面控制跳转
在controller控制页面跳转有下面几种方式:
1:$window.location.href="www.csdn.net";//原也签打开
2:$window.open("www.csdn.net");//新也签中打开
3:ui-router:$state.go(stateName,params);
十二、angularJs使用$apply
首先你当然要检查有没有错误以及是否确实是scope变量,如果这些都没问题,那么多半儿是apply导致的。对于大多数操作,apply都会自动执行,所以你不用担心,但是如果你使用了angular之外的功能,比如直接调用了setTimeout函数、挂接了jquery的事件、使用了jquery的ajax操作等等,那么系统就没有机会帮你调用apply,界面也就没有机会刷新了,但是你如果之后又做了其他会导致apply的操作,你会发现以前“欠下”的那次界面刷新被正常执行了了 …… 迟到的刷新仍然是bug。 典型代码如下: setTimeout(function() {scope.time = new Date()
}, 1000);
这种情况下你在页面中绑定的time变量将不会被自动刷新,无论是通过{{}}表达式,还是通过ng-*属性或者其他任何形式。怎么改呢?这样:
setTimeout(function() {
$scope.$apply(function() {
$scope.time = new Date();
});
}, 1000);
不过,这不是最好的形式,最好的形式是什么呢?当然是使用angular内置的timeout服务,它就是干这个的: timeout(function()$scope.time=newDate(););没有apply,却正常工作,没bug,而且漂亮多了吧?不过这里别忘了你得把timeout服务进行依赖注入,不然它是undefined。
十三、第三方指令不起作用
最可能的原因是你没有加入模块依赖。第三方指令通常会定义在自己的模块中,所以这个模块必须被你的app模块所依赖,其中包含的指令才能在view中使用。比如你要使用ui-select2指令,就必须在自己的模块定义中加入这个依赖:
angular.module(‘app’, [
‘ngCookies’,
‘ngResource’,
‘ngSanitize’,
‘ui.select2’
])…..
十四、当把jQuery插件整合到directive里时
Directive永远不会‘完成’
在directive中,一个令人掉头发的事就是directive已经‘完成’但你永远不会知道。当把jQuery插件整合到directive里时,这个通知尤为重要。假设你想用ng-repeat把动态数据以jQuery datatable的形式显示出来。当所有的数据在页面中加载完成后,你只需要调用$(‘.mytable).dataTable()就可以了。 但是,臣妾做不到啊!
为啥呢?Angular的数据绑定是通过持续的digest循环实现的。基于此,Angular框架里根本没有一个时间是‘休息’的。 一个解决方法就是将jQuery dataTable的调用放在当前digest循环外,用timeout方法就可以做到。
angular.module('table', []).directive('mytable&', ['$timeout', function ($timeout) {
return {
restrict: 'E',
template: '<table class="mytable"' +
'<thead><tr></tr><th></th>counting</th></tr></thead>' +
'<tr ng-repeat="data in datas"><td></td></td></tr>' +
'</table>',
link: function (scope, element, attrs, ctrl) {
scope.datas = ['one ' , 'two','three'];
// Doesn't work, shows an empty table:
// $('.mytable', element).dataTable()
// But this does:
$timeout(function () {
$('.mytable', element).dataTable();
}, 0)
}
}
}]);
十五、angularJs 图片动态获取,ng-src问题
<img ng-src="{{imgSrc[index]}}" alt="" id="focusphoto">
Angular的ng-src指令并不会立刻让图片更新,而是在更新其他scope下的变量后才能更新。给$scope.imgSrc[index]赋值为字符串是有效果的,只是无法实现立刻更新。
Angular的视图更新机制是,执行序列执行结束后都去检查是否有数据的改变,发现数据改变后就马上调用$scope.$apply()方法,再由$scope.$apply()方法调用$scope.$digest()改变视图。而我们在Angular下所写的代码,几乎全部都被包含在$scope.$apply()之下。
如果视图没有立即更新,那就说明了这部分的代码并没有被包含在$scope.$apply()之下,例如:DOM事件、setTimeout、XHR或其他第三方的库这类事件就有这种情况。
因此我们要让图片立刻更新,可以把js代码改成这样:
var time = new Date().getTime(); //用于提供实时随机数
$scope.$apply(function () {
$scope.logoSrc = 字符串 + '?' + time; //增加随机参数时间可强制刷新
});
增加time作为随机参数,可以避免图片名字没有变化,而实际图片内容已经变化的情况。这样就算图片同名也可以实时更新。
2、图片动态切换 问题
ng-src指令并不会立刻让图片更新,$scope.imgSrc[index]赋值为字符串是有效果的,只是无法实现立刻更新,即dom还没有渲染完成。所以在多组图片切换的时候,无法获取到当前图片的属性值,所以必选判断图片是否加载完成。
解决方案:
定判断图片切换时,下一图片是否加载完场服务。
app.service('base',function($state,locals,$stateParams,$interval) {
this.imgLoad=function(el,callback){
//el 为当前的图片元素 callback 是图片加载完成后的回调方法
//console.log($(el).length);
var timer = $interval(function() {
var len=$(el).length;
$(el).each(function(){
if ($(this)[0].complete&&!--len) {
callback();
$interval.cancel(timer);
}
})
}, 100)};
})
'$timeout 是一个window.setTimeout 的Angular封装,这个 fn 函数被封装成了一个 try/catch 块并且授 $exceptionHandler 服务以任何例外。调用 $timeout 的返回值是一个 promise, 这个 promise 将会在延时已经结束时被解析,超时函数(如果有的话)被执行了。退出一个超时请求,调用 $timeout.cancel(promise)。
$interval是Angular对 window.setInterval 的封装。fn 函数将在每次延时的时候执行。一个注册的间隔函数的返回值是一个 promise。这个通知的值将是已运行迭代的次数。如果想终端一个 interval,则调用 $interval.cancel(promise)。
注意: 用此服务创建的interval必须要在完成之后被明文销毁。特别地,在控制器的作用域和指令的元素被销毁时,interval 也不会被自动销毁。你需要将此纳入考虑之中,确保在适合的时间退出 interval。
//ng-repeat 数据动态渲染,dom加载问题
十六、angularJs 中的iframe标签ng-src 路径
如果直接写路径到iframe标签里的ng-src中会出现报错;
解决方法:
1、ng里面有个属性是专门用来解决跨域问题的 $sce。
用法:
$scope.someUrl = $sce.trustAsResourceUrl('路径');
例:
<ul class="nav nav-tabs" ng-repeat="item in [1,2,3,4]">
<iframe ng-src="{{someUrl}}" height="100%" width="100%"></iframe>
</ul>
2、可以巧用上面方法写一个过滤器。
angular.module('filters-module', [])
.filter('trustAsResourceUrl', ['$sce', function($sce) {
return function(val) {
return $sce.trustAsResourceUrl(val);
};
}])
例:
<ul class="nav nav-tabs" ng-repeat="item in [1,2,3,4]">
<iframe ng-src="{{someUrl |trustAsResourceUrl }}" height="100%" width="100%"></iframe>
</ul>