angularjs实践
2019-09-25 10:02 king0222 阅读(192) 评论(0) 编辑 收藏 举报整个angular的开发过程都是可以使用yeoman构建的。譬如以下的命令。以coffescript形式生成angular的各个组件
yo angular:route myroute --coffee
yo angular:controller user --coffee
yo angular:directive myDirective --coffee
yo angular:filter myFilter --coffee
yo angular:view user --coffee
yo angular:service myService --coffee
yo angular:decorator serviceName --coffee
理解ngModel中的$parsers和$formatters
formatters改变model中的值如何显示在view上
parsers改变view上的如何存储在model中
下面是一个例子
//format text going to user (model to view)
ngModel.$formatters.push(function(value) {
return value.toUpperCase();
});
//format text from the user (view to model)
ngModel.$parsers.push(function(value) {
return value.toLowerCase();
});
在下面的地址中查看: http://plnkr.co/UQ5q5FxyBzIeEjRYYVGX
<input type="button" value="set to 'misko'" ng-click="data.name='misko'"/>
<input type="button" value="set to 'MISKO'" ng-click="data.name='MISKO'"/>
<input changecase ng-model="data.name" />
When you type a name in (view to model), you will see that the model is always lowercase. But, when you click a button and programatically change the name (model to view), the input field is always uppercase.
$apply & $digest应该什么时候使用:
官方给出了明确的答案,那就是在数据模型的改变不是通过angularjs自身的api来调用的时候就需要我们手动调用$apply或者$digest,例如:
setTimeout(function(){scope.name='ken';},1000);
这个时候模型数据改变了,但是不会反映到视图中,所以需要我们手动添加$apply,可以像下面这样:
setTimeout(function(){scope.$apply(scope.name='ken';);},1000);
或者也可以使用$timeout,在angularjs提供的$timeout里面会帮我们自动调用$apply;
一段代码弄清楚$apply跟$digest的关系
1 Scope.prototype.$apply = function(expr) { 2 try { 3 this.$beginPhase("$apply"); 4 return this.$eval(expr); 5 } finally { 6 this.$clearPhase(); 7 this.$digest(); 8 } 9 };
$interpolate的使用方式:
var getFullName = $interpolate('{{first}}{{last}}');
var scope = { first:'Pete',last:'Bacon Darwin' };
var fullName = getFullName(scope);
绑定验证反馈信息
为了在我们的字段模板中显示错误信息,我们可以像下面这样做:
<span class="help-inline" ng-repeat="error in $fieldErrors">
{{$validationMessages[error](this)}}
</span>
this指向的是当前的scope
在angularjs的routes中使用resolve
.when('/admin/users/:userid', {
templateUrl: 'tpls/users/edit.html'
controller: 'EditUserCtrl',
resolve: {
user: function($route, Users) {
return Users.getById($route.current.params.userid);
}
}
})
这里resolve的作用是返回内容提供给controller,返回的内容可以通过参数的形式传递到controllerz中
.controller('EditUserCtrl', function($scope, user){
$scope.user = user;
...
})
对于这种模式有一个益处,就是我们可以设计不同的路由来对应同一个controller,只要resolve中返回不同的值就可以了
$routeProvider.when('/admin/users/new', {
templateUrl:'admin/users/users-edit.tpl.html',
controller:'UsersEditCtrl',
resolve:{
user: function (Users) {
return new Users();
}
}
});
$routeProvider.when('/admin/users/:userId', {
templateUrl:'admin/users/users-edit.tpl.html',
controller:'UsersEditCtrl',
resolve:{
user: function ($route, Users) {
return Users.getById($route.current.params.userId);
}
}
});
使用ng-include来处理多UI模板的情况
$routeProvider.when('/admin/users/new', {
templateUrl:'admin/admin.tpl.html',
contentUrl:'admin/users/users-edit.tpl.html',
menuUrl:'admin/menu.tpl.html',
controller:'UsersEditCtrl',
...
});
在页面中,我们就可以通过$route.current得到对应的模板URL了
<div>
<div ng-include='$route.current.contentUrl'>
<!--menu goes here -->
</div>
<div ng-include='$route.current.menuUrl'>
<!--content goes here -->
</div>
</div>
如何加快$digest循环
1.减少$watch的表达式取值时间
$watch的用法如下:$scope.$watch(watchExpression, modelChangeCallback);
我们应当尽量减少watchExpression的计算时间
2.减少console.log()的使用
对比如下两段代码
$scope.getName = function () {
return $scope.name;
};
$scope.getNameLog = function () {
console.log('getting name');
return $scope.name;
};
他们所花费的时间对比:
因此,我们应该尽量减少在产品阶段的代码中使用console.log打印日志
3.尽量避免在watch-expression中访问DOM
引用“mastering web application development with angularjs中的一段话:
Any DOM operation is slow and computed properties are extra slow.
The real problem is that DOM is implemented in C++. It turns out that
it is very expensive to call a C++ function from JS. So any DOM access
is magnitudes slower than JavaScript object access.
4.限制watches的执行数量
可通过删除不必要的watch来实现
重新审视UI,在布局上是否够轻量级,有没有必要存在大量的DOM,我们应该化繁为简
慎重使用watch来显示隐藏DOM,例如当我们使用ng-show来隐藏某个DOM的时候,如果DOM里面绑定了某个模型数据,在每次input变化的时候模型数据都会变化,$digest循环都会对其进行计算,因此而浪费资源,在这种时候我们使用ng-switch可能会更加适合。
5.删除不再使用的watches
var watchUnregisterFn = $scope.$watch('name', function (newValue,
oldValue) {
console.log("Watching 'name' variable");
...
});
//later on, when a watch is not needed any more:
watchUnregisterFn();
如上代码所示,$scope.$watch()返回一个函数,这个函数可以用来取消我们的监控,只要将这个函数执行一次即可。
6.减少$digest的使用频率
类似于做一个定时器,每秒钟更新一下时间,我们会用到timeout, 但更好是使用angularjs给我们提供的$timeout服务,代码如下:
1 .directive('clock', function ($timeout, dateFilter) { 2 return { 3 restrict: 'E', 4 link: function (scope, element, attrs) { 5 function update() { 6 // get current time, format it and update DOM text 7 element.text(dateFilter(new Date(), 'hh:mm:ss')); 8 //repeat in 1 second 9 $timeout(update, 1000); 10 } 11 update(); 12 } 13 }; 14 })
但是这样有一个问题,每过一秒中,$digest都会执行一次,还好angularjs给我们的$timeout提供了第三个参数来决定是否调用$digest,代码可以改为:
function update() {
element.text(dateFilter(new Date(), 'hh:mm:ss'));
$timeout(update, 1000, false);
}
7.无论什么时候都应该避免深度的watch
例如有这样一个user对象:
$scope.user = {
firstName: 'AngularJS',
lastName: 'Superhero',
age: 4,
superpowers: 'unlimited',
// many other properties go here…
};
我们可以通过如下方式来实现深度监控,就是在$watch中传入第三个值为true的参数
$scope.$watch('user', function (changedUser) {
$scope.fullName =
changedUser.firstName + ' ' + changedUser.lastName;
}, true);
但是这种方式非常不友好,占用内存,计算复杂,我们可以使用更好的方式:
$scope.$watch(function(scope) {
return scope.user.firstName + ' ' + scope.user.lastName;
}, function (newFullName) {
$scope.fullName = newFullName;
});
还有另外一种避免使用$watch的方式,只需要我们修改模板即可
在模板中绑定方法,{{ fullName() }}
然后controller中定义方法
$scope.fullName = function () {
return $scope.user.firstName + ' ' + $scope.user.lastName;
};
8.分析被watch的expression表达式
分析如下一段代码:
<p>This is very long text that refers to one {{variable}} defined on a
scope. This text can be really, really long and occupy a lot of space
in memory. It is so long since… </p>
在angularjs中,angularjs会将整段代码保存到内存中,而不仅仅是{{variable}}这个变量,如果我们希望真个表达式变得更短,消耗内存更小,我们可以给变量添加一个标签:
<p>This is very long text that refers to one <span ngbind='variable'></span> defined on a scope. This text can be really,
really long and occupy a lot of space in memory. It is so long since…
</p>
如何添加依赖
我们可以很简单的注入依赖就像下面这样
angular.module('projects', []).controller('ProjectCtrl', function($scope){//todo...});
但是这样存在一个后期隐患,因为代码压缩后,我们就无法得知传入的参数是什么了,$scope可能会变成a或者b;
因此我们需要换一种方式来添加依赖
angular.module('projects',[]).controller('ProjectCtrl', ['$scope', function($scope){//todo}]);
还有config以及run方法我们都可以用同样的方法来添加依赖
angular.module('app').config(['$routeProvider', '$localtionProvider', function($routeProvider, $locationProvider){
$locationProvider.html5Mode(true);
$routeProvider.otherwise({redirectTo: '/projectsinfo'});
}]);
angular.module('app').run(['security', function(security){
security.requestCurrentUsesr();
}]);
其他provider, derective依葫芦画瓢
预加载模板技术
一般的,模板的加载过程会消耗一个网络延时,angularjs给我们提供了两种解决方案来实现预加载模板
第一种方法是使用script标签来预加载模板
将模板用script标签嵌套起来,添加angularjs可识别的属性,另外,该模板需要写在ng-app范围之内;
<script type="text/ng-template" id="tpls/users/list.html">
<div class="hello">hello world</div>
</script>
应该注意一点,id值与我们的template url一致
<html ng-app> <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.2/angular.js"></script> <body> <div ng-include='"test"'></div> <script type="text/ng-template" id="test"> This is the content of the template </script> </body> </html>
ng-include的值必须用'""'或者"''",只有一个单引号或者双引号的时候会无效
第二种方法是使用$templateCache服务
var myApp = angular.module('myApp', []);
myApp.run(function($templateCache) {
$templateCache.put('templateId.html', 'This is the content of the template');
});
通过下面的方式来倒入模板
<div ng-include=" 'templateId.html' "></div>
或者也可以通过javascript的方式拿到模板
$templateCache.get('templateId.html');
理解$compile
angularjs中的$compile服务可以提供编译服务,如果是$compile()这样使用的话会返回一个link方法,例如
var link = $compile('<p>hello {{name}}</p>');
再传入一个scope的话就能将dom跟scope进行连接返回一个angular element;返回的element我们可以插入DOM树中
代码片段,将模板保存在$templateCache中:
hello.run(function($templateCache) {
$templateCache.put('templateId.html', '<a>this is the content of the template{{name}}</a>');
});
上面的代码需要注意一点,保存到$templateCache中的模板必须有标签包围,否则报错,例如上面有a标签包围;
代码片段,从$templateCache中获取模板并编译插入DOM树中:
$scope.showmyhtml = function() {
//$scope.myhtml = $templateCache.get('templateId.html');
var element = $compile($templateCache.get('templateId.html'))($scope);
console.log(element);
angular.element(document.querySelector('#myhtml')).append(element);
}
注意一点的是angularjs中自带的jqlite不能想jquery那样直接通过$符号来使用,可以通过angular.element来将dom转化为jq对象;
angular装饰者decorator
decorator在$provider的命名空间下面,该方法传递两个参数,一个需要装饰的对象,一个是装饰函数体
1 var Mail = function() { 2 this.receiver = ''; 3 this.body = ''; 4 this.cc = []; 5 }; 6 7 Mail.prototype.setReceiver = function(receiver) { 8 this.receiver = receiver; 9 }; 10 11 Mail.prototype.setBody = function(body) { 12 this.body = body; 13 }; 14 15 angular.module('A', []).service('Mail', Mail);
1 angular.module('B', ['A']).config(function($provide) { 2 $provide.decorator('Mail', function($delegate) { 3 $delegate.addCC = function(cc) { 4 this.cc.push(cc); 5 }; 6 return $delegate; 7 }); 8 }) 9 .controller('TestCtrl', function($scope, Mail) { 10 Mail.addCC('jack'); 11 console.log(Mail); 12 });
angular的调试
常用到的调试工具类似于console.log(), angular提供了$log服务
1 angular.module('app', []) 2 3 .controller('MainCtrl', ['$log', function($log){ 4 $log.debug('Hello Debug!'); 5 }]);
当然我们可以设置是否打开日志的功能,在config中进行配置:
1 angular.module('app', []) 2 3 .config(['$logProvider', function($logProvider){ 4 $logProvider.debugEnabled(false); 5 }]) 6 7 .controller('MainCtrl', ['$log', function($log){ 8 $log.debug('Hello Debug!'); 9 }])
同时,我们还可以利用上面说到的decorator来对$log进行装饰:
1 angular.module('app', []) 2 3 .config(['$provide', function ($provide) { 4 $provide.decorator('$log', ['$delegate', function ($delegate) { 5 // Keep track of the original debug method, we'll need it later. 6 var origDebug = $delegate.debug; 7 /* 8 * Intercept the call to $log.debug() so we can add on 9 * our enhancement. We're going to add on a date and 10 * time stamp to the message that will be logged. 11 */ 12 $delegate.debug = function () { 13 var args = [].slice.call(arguments); 14 args[0] = [new Date().toString(), ': ', args[0]].join(''); 15 16 // Send on our enhanced message to the original debug method. 17 origDebug.apply(null, args) 18 }; 19 20 return $delegate; 21 }]); 22 }]) 23 24 .controller('MainCtrl', ['$log', function ($log) { 25 $log.debug('Hello Debug!'); 26 }]);
内存管理的重要性
angularjs在销毁一个scope和把一个scope从它的父级移除之前会广播一个$destroy事件,监听这个事件对清理任务和资源很重要,例如一个timeout的例子:
1 module.controller("MyController", function($scope, $timeout) { 2 var timeout = function() { 3 $scope.value += 1; 4 $timeout(timeout, 100); 5 }; 6 $timeout(timeout, 100); 7 $scope.value = 0; 8 9 });
如果用户来回导航到一个view来加载这个controller,那每次导航将会添加另一个永远运行的计时器,监听$destroy,我们可以取消掉timeout来移除资源的消耗:
1 module.controller("MyController", function($scope, $timeout) { 2 var timeout = function() { 3 $scope.value += 1; 4 timer = $timeout(timeout, 100); 5 }; 6 7 var timer = $timeout(timeout, 100); 8 $scope.value = 0; 9 $scope.$on("$destroy", function() { 10 11 if (timer) { 12 $timeout.cancel(timer); 13 } 14 }); 15 });
angularjs中对img的src属性应该使用ng-src来代替
angularjs中应该尽可能用promise来处理回调
第三方的回调应该使用$apply来包裹,以便通知angularjs关于环境的变化
如果我们不想让用户在angularjs未加载之前显示html,可以使用ng-cloak来隐藏html
<div class="ng-cloak">...... .ng-cloak{display: none;}
angularjs加载完后会将.ng-cloak变为block;
编写我们自己的directive,最好的实践不是使用ng前缀,可以使用类似于<my-component>的方式