Angular笔记

如何在平级界面模块间进行通信

有两种方法:

  一种是共用服务。

    一种是基于事件。
  共用服务:
    在 Angular 中,通过 factory 可以生成一个单例对象,在需要通信的模块 a 和 b 中注入这个对象即可。
  基于事件:
    这个又分两种方式:
      第一种是借助父 controller。在子 controller 中向父 controller 触发( $emit )一个事件,然后在父 controller 中监听( $on )事件,再广播( $broadcast )     给子 controller ,这样通过事件携带的参数,实现了数据经过父 controller,在同级 controller 之间传播。
      第二种是借助 $rootScope 。每个 Angular 应用默认有一个根作用域 $rootScope , 根作用域位于最顶层,从它往下挂着各级作用域。所以,如果子控制器直接使            用 $rootScope 广播和接收事件,那么就可实现同级之间的通信。

一个 angular 应用应当如何良好地分层?
  目录结构的划分
    对于小型项目,可以按照文件类型组织,比如:

1 css
2 js
3 controllers
4 models
5 services
6 filters
7 templates 

 


    但是对于规模较大的项目,最好按业务模块划分,比如:

 1 css
 2 modules
 3 account
 4 controllers
 5 models
 6 services
 7 filters
 8 templates
 9 disk
10 controllers
11 models
12 services
13 filters
14 templates

 


  逻辑代码的拆分
    作为一个 MVVM 框架,Angular 应用本身就应该按照 模型,视图模型(控制器),视图来划分。
    这里逻辑代码的拆分,主要是指尽量让 controller 这一层很薄。提取共用的逻辑到 service 中 (比如后台数据的请求,数据的共享和缓存,基于事件的模块间通信等),       提取共用的界面操作到 directive 中(比如将日期选择、分页等封装成组件等),提取共用的格式化操作到 filter 中等等。
  在复杂的应用中,也可以为实体建立对应的构造函数,比如硬盘(Disk)模块,可能有列表、新建、详情这样几个视图,并分别对应的有 controller,那么可以建一个 Disk 构       造函数,里面完成数据的增删改查和验证操作,有跟 Disk 相关的 controller,就注入 Disk 构造器并生成一个实例,这个实例就具备了增删改查和验证方法。这样既层次分           明,又实现了复用(让 controller 层更薄了)。


angular 应用常用哪些路由库,各自的区别是什么?


  Angular1.x 中常用 ngRoute 和 ui.router,还有一种为 Angular2 设计的 new router (面向组件)


区别:


  ngRoute 模块是 Angular 自带的路由模块,而 ui.router 模块是基于 ngRoute模块开发的第三方模块。
  ui.router 是基于 state (状态)的, ngRoute 是基于 url 的,ui.router模块具有更强大的功能,主要体现在视图的嵌套方面。
     使用 ui.router 能够定义有明确父子关系的路由,并通过 ui-view 指令将子路由模版插入到父路由模板的 <div ui-view></div> 中去,从而实现视图嵌套。而在 ngRoute 中      不能这样定义,如果同时在父子视图中 使用了 <div ng-view></div> 会陷入死循环。
示例

 1 ngRoute:
 2 
 3 var app = angular.module('ngRouteApp', ['ngRoute']); 
 4 app.config(function($routeProvider){
 5     $routeProvider .when('/main', { 
 6         templateUrl: "main.html", controller: 'MainCtrl' })
 7     .otherwise({ redirectTo: '/tabs' });
 8 })
 9 
10 ui.router:
11 
12 var app = angular.module("uiRouteApp", ["ui.router"]); 
13 app.config(function($urlRouterProvider, $stateProvider){ 
14     $urlRouterProvider.otherwise("/index"); 
15     $stateProvider .state("Main", { url: "/main", templateUrl: "main.html", controller: 'MainCtrl' })
16 })

 

 

angular 的缺点有哪些?
强约束
不利于 SEO
  因为所有内容都是动态获取并渲染生成的,搜索引擎没法爬取。
  一种解决办法是,对于正常用户的访问,服务器响应 AngularJS 应用的内容;对于搜索引擎的访问,则响应专门针对 SEO 的HTML页面。
性能问题
  作为 MVVM 框架,因为实现了数据的双向绑定,对于大数组、复杂对象会存在性能问题。
  可以用来 优化 Angular 应用的性能 的办法:
  减少监控项(比如对不会变化的数据采用单向绑定)
  主动设置索引(指定 track by ,简单类型默认用自身当索引,对象默认使用 $$hashKey ,比如改为 track by item.id )
  降低渲染数据量(比如分页,或者每次取一小部分数据,根据需要再取)
  数据扁平化(比如对于树状结构,使用扁平化结构,构建一个 map 和树状数据,对树操作时,由于跟扁平数据同一引用,树状数据变更会同步到原始的扁平数据)
移动端
  可尝试 Ionic,但并不完善。

 


如何看待 angular 1.2 中引入的 controller as 语法?
最根本的好处
在 angular 1.2 以前,在 view 上的任何绑定都是直接绑定在 $scope 上的
function myCtrl($scope){ $scope.a = 'aaa'; $scope.foo = function(){ ... } }
使用 controllerAs,不需要再注入 $scope ,controller 变成了一个很简单的 javascript 对象(POJO),一个更纯粹的 ViewModel。
function myCtrl(){ // 使用 vm 捕获 this 可避免内部的函数在使用 this 时导致上下文改变 var vm = this; vm.a = 'aaa'; }

依赖注入
依赖注入是一种软件设计模式,目的是处理代码之间的依赖关系,减少组件间的耦合。
原理
AngularJS 是通过构造函数的参数名字来推断依赖服务名称的,通过 toString() 来找到这个定义的 function 对应的字符串,然后用正则解析出其中的参数(依赖项),再去依赖映射中取到对应的依赖,实例化之后传入。

AngularJS中的依赖注入非常的有用,它同时也是我们能够轻松对组件进行测试的关键所在。在本文中我们将会解释AngularJS依赖注入系统是如何运行的。

Provider服务($provide)

$provide服务负责告诉Angular如何创造一个新的可注入的东西:即服务(service)。服务会被叫做provider的东西来定义,你可以使用$provide来创建一个provider。你需要使用$provide中的provider方法来定义一个provider,同时你也可以通过要求改服务被注入到一个应用的config函数中来获得$provide服务。下面是一个例子:

1 app.config(function($provide) {
2   $provide.provider('greeting', function() {
3 this.$get = function() {
4   return function(name) {
5 alert("Hello, " + name);
6   };
7 };
8   });
9 }); 

 

 

代码laycode - v1.1

在上面的例子中我们为一个服务定义了一个叫做greeting的新provider;我么可以把一个叫做greeting的变量注入到任何可注入的函数中(例如控制器,在后面会讲到)然后Angular就会调用这个provider的$get函数来返回这个服务的一个实例。在上面的例子中,被注入的是一个函数,它接受一个叫做name的参数并且根据这个参数alert一条信息。我们可以像下面这样使用它:

1 app.controller('MainController', function($scope, greeting) {
2   $scope.onClick = function() {
3 greeting('Ford Prefect');
4   };
5 }); 

 

 

 

代码laycode - v1.1

现在有趣的事情来了。factory,service以及value全部都是用来定义一个providr的简写,它们提供了一种方式来定义一个provider而无需输入所有的复杂的代码。例如,你可以用下面的代码定义一个和前面完全相同的provider:

1 app.config(function($provide) {
2   $provide.factory('greeting', function() {
3 return function(name) {
4   alert("Hello, " + name);
5 };
6   });
7 }); 

 

代码laycode - v1.1

这非常的重要,所以一定要记住:在幕后,AngularJS实际上是在调用前面出现的代码(就是$provide.provider的版本)。从字面上来说,这两种方法基本上没有差别。value方法也一样 – 如果我们需要从$get函数(也就是我们的factory函数)返回的值永远相同的话,我们可以使用value方法来编写更少的代码。例如如果我们的greeting服务总是返回相同的函数,我们可以使用value来进行定义:

1 app.config(function($provide) {
2   $provide.value('greeting', function(name) {
3 alert("Hello, " + name);
4   });
5 });  

 

 

代码laycode - v1.1

再一次申明,以上两种方式的效果完全一样 – 只不过是代码的量不同而已。

现在你可能已经注意到了我们使用的烦人的

app.config(function($provide){...})  
代码laycode - v1.1

代码了。由于定义一个新的provider是如此的常用,AngularJS在模块对象上直接暴露了provider方法,以此来减少代码的输入量:

1 var myMod = angular.module('myModule', []);
2 
3 myMod.provider("greeting", ...);
4 myMod.factory("greeting", ...);
5 myMod.value("greeting", ...);  

 

代码laycode - v1.1

上面的代码和前面app.config(...)这样?嗦的写法完全相同。

除了上面提到的可以注入的东西之外,还有一个constant方法。基本上,它和value的用法一致。我们会在后面来讨论两者的不同点。

为了巩固前面的学习成果,下面所有的代码所做的都是同一件事情:

 1 myMod.provider('greeting', function() {
 2   this.$get = function() {
 3 return function(name) {
 4   alert("Hello, " + name);
 5 };
 6   };
 7 });
 8 
 9 myMod.factory('greeting', function() {
10   return function(name) {
11 alert("Hello, " + name);
12   };
13 });
14 
15 myMod.value('greeting', function(name) {
16   alert("Hello, " + name);
17 });  

 

 

 

代码laycode - v1.1

注入器($injector)

注入器负责从我们通过$provide创建的服务中创建注入的实例。只要你编写了一个带有可注入性的参数,你都能看到注入器是如何运行的。每一个AngularJS应用都有唯一一个$injector,当应用启动的时候它被创造出来,你可以通过将$injector注入到任何可注入函数中来得到它($injector知道如何注入它自己!)。

一旦你拥有了$injector,你可以动过调用get函数来获得任何一个已经被定义过的服务的实例。例如:

var greeting = $injector.get('greeting');
greeting('Ford Prefect');  

 

 

代码laycode - v1.1

注入器同样也负责将服务注入到函数中;例如,你可以魔法般的将服务注入到任何函数中,只要你使用了注入器的invoke方法:

1 var myFunction = function(greeting) {
2   greeting('Ford Prefect');
3 };
4 $injector.invoke(myFunction);  

 

代码laycode - v1.1

如果注入器只是创建一个服务的实例一次的话,那么它也没什么了不起的。它的厉害之处在于,他能够通过服务名称缓存从一个provider中返回的任何东西,当你下一次再使用这个服务时,你将会得到同一个对象。

因此,你可以通过调用$injector.invike将服务注入到任何函数中也是合情合理的了。包括:

  • 控制器定义函数
  • 指令定义函数
  • 过滤器定义函数
  • provider中的$get方法(也就是factory函数)

由于constant和value总是返回一个静态值,它们不会通过注入器被调用,因此你不能在其中注入任何东西。

配置provider

你可能会感到困惑:既然factorry和value能够节省那么多的代码,为什么还有人要使用provider。答案是provider允许我们进行一些配置。在前面我们已经提到过当你通过provider(或者其他简写方法)创建一个服务时,你实际上创建了一个新的provider,它将定义你的服务如何被创建。我们没有提到的是,这些provider可以被注入到config函数中,你可以和它们进行一些交互。

首先,AngularJS分两个阶段运行你的应用 – config阶段和run阶段。config阶段是你设置任何的provider的阶段。它也是你设置任何的指令,控制器,过滤器以及其它东西的阶段。在run阶段,AngularJS会编译你的DOM并启动你的应用。

你可以在myMod.config和myMod.run中添加任何代码 – 这两个函数分别在两个阶段运行。正如我们看到的,这些函数都是可以被注入的 – 我们在第一个例子中注入了内建的$provide函数。然而,值得注意的是在config阶段,只有provider能被注入(只有两个例外是$provide和$injector)。

例如,下面的代码就是错误的写法:

1 myMod.config(function(greeting) {
2   //不会运行 -- greeting是一个服务的实例
3   //只有服务的provider能被注入到config中
4 });

 

代码laycode - v1.1

但是你可以通过下面的方法注入provider:

myMod.config(function(greetingProvider) {
  // 这下好了!
});  

 

 

代码laycode - v1.1

有一个例外:constant,由于它们不能被改变,因此它不能被注入到config中(这就是它和value之间的不同之处)。它们只能通过名字被获取。

无论何时你为一个服务定义了一个provider,这个provider的名字都是serviceProvider。在这里service是服务的名字。现在我们可以使用provider的力量来做一些更复杂的事情了!

 1 myMod.provider('greeting', function() {
 2   var text = 'Hello, ';
 3 
 4   this.setText = function(value) {
 5      text = value;
 6   };
 7 
 8   this.$get = function() {
 9      return function(name) {
10          alert(text + name);
11      };
12   };
13 });
14 
15 myMod.config(function(greetingProvider) {
16   greetingProvider.setText("Howdy there, ");
17 });
18 
19 myMod.run(function(greeting) {
20   greeting('Ford Prefect');
21 });

 

 

 

代码laycode - v1.1

现在我们在provider中拥有了一个叫做setText的函数,我们可以使用它来自定义我们alert的内容;我们可以再config中访问这个provider,调用setText方法并自定义我们的service。当我们最终运行我们的应用时,我们可以获取greeting服务,然后你会看到我们自定义的行为起作用了。

控制器($controller)

控制器函数是可以被注入的,但是控制器本身是不能被注入到任何东西里面去的。这是因为控制器不是通过provider创建的。然而,有一个内建的AngularJS服务叫做1$controller,它负责设置你的控制器。当你调用myMod.controller(…)时,你实际上是访问了这个服务的provider,就像上面的例子一样。

例如,当你像下面一样定义了一个控制器时:

myMod.controller('MainController', function($scope) {
  // ...
});  
代码laycode - v1.1

你实际上做的是下面这件事:

1 myMod.config(function($controllerProvider) {
2   $controllerProvider.register('MainController', function($scope) {
3 // ...
4   });
5 });

 

 

 

代码laycode - v1.1

当Angular需要创建一个你的控制的实例时,它会使用$controller服务(它反过来会使用$injector来调用你的控制器以便它能够被注入依赖项)。

过滤器和指令

filter和directive和controller的运行方式相同;filter会使用一个叫做$filter的服务以及它的provider $filterProvider,而directive使用一个叫做$compile的服务以及它的provider $compileProvidr。下面是相应的文档:

  • $filter: http://docs.angularjs.org/api/ng.$filter
  • $filterProvider: http://docs.angularjs.org/api/ng.$filterProvider
  • $compile: http://docs.angularjs.org/api/ng.$compile
  • $compileProvider: http://docs.angularjs.org/api/ng.$compileProvider

其中,myMod.filter和myMod.directive分别是这些服务的简写。

总结

总结一下,任何能够被$injector.invoke调用的函数都是能被注入的。包括,但不限于下面的这些:

  • 控制器
  • 指令
  • factory
  • 过滤器
  • provider中的$get函数
  • provider函数
  • 服务

provider创建的新服务都可以用来注入。包括:

  • constant
  • factory
  • provider
  • service
  • value

另外,内建的服务$controller和$filter也可以被注入,同时你也可以使用这些服务来获得新的过滤器和控制器。

posted @ 2017-04-26 22:41  水流目  阅读(303)  评论(0编辑  收藏  举报