代码改变世界

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>的方式