AngularJS权威指南-模块加载,路由,依赖注入

160161次阅读
没有评论

共计 11980 个字符,预计需要花费 30 分钟才能阅读完成。

一、模块加载

AngularJS模块子在被加载和执行之前对其自身进行配置。

1.配置

在模块的加载阶段,AngularJS会在提供者注册(provider)和配置的过程中对模块进行配置。在整个AngularJS的工作流中,这个阶段是唯一能够在应用启动前进行修改的部分。config()代码块主要用于:对服务进行自定义配置。

angular.module('my-app', []).config(function () {....})

例如:在某个模块上创建一个指令或者服务时

//第一种
angular.module('my-app', [])
        .factory('MyFactory', function () {
            return {}
        })
        .directive('MyDirective', function () {
            return {
                template: '<button>Click me</button>'
            }
        });

//第二种
angular.module('my-app', [])
        .config(function ($provide, $compileProvider) {
            $provide.factory('MyFactory', function () {
                return {}
            });
            $compileProvider.directive('MyDirective', function () {
                return {
                    template: '<button>Click me</button>'
                }
            });
        });

上面两种是等同的。但是需要注意的是,第二种AngularJS会以这些函数书写顺序执行它们。

提醒:constant()方法,总是在所有配置块之前执行。

当对模块进行配置时,只有少数几种类型对象可以住入到config()函数中:提供者和常量。如果将一个服务注入进去,只能用provider()语法构建的服务。而且把服务注入进去会导致服务的实例化。

当定义多个配置块的时候,它们会按照顺序执行:

angular.module('my-app', [])
        .config(function ($provide) {
            $provide.factory('MyFactory', function () {
                return {}
            });
        })
        .config(function (con) {
            con.setApiKey('...');
        })

*config()函数接受一个参数:configFunction函数,AngularJS在模块加载时会执行这个函数。

2.运行块

运行块是在注入器创建之后被执行,它是所有AngularJS应用中第一个被执行的方法。它通常用来注册全局的事件监听器。

angular.module('my-app', [])
        .run(function ($rootScope, AuthService) {
            $rootScope.$on('$routeChangeStart', function (evt, next, current) {
                //如果用户为登录
                if (!AuthService.userLoggedIn()) {
                    if (next.templateUrl === 'Login.html') {
                        //已经转向登录路由,因此无需重定向
                    } else {
                        $location.path('/login')
                    }
                }
            })
        })

*run()函数接受1个参数:initializeFn函数AngularJS在注入器创建后会执行这个函数。

二、多重视图和路由

1.准备

首先需要加载angular-route:

<script src="/js/angular.min.js"></script>
<script src="/js/angular-route.js"></script>

接着再把ngRoute模块在应用中当做依赖加载进来:

angular.module('my-app', ['ngRoute'])

2.布局模板

<header>
    <h1>hello,Angular</h1>
</header>
<div class="content" style="padding:10px;border:1px solid orange">
    <div ng-view></div>
</div>
<footer>
    <h5>hello,body</h5>
</footer>

ng-view是由ngRoute模块提供的一个特殊指令,它的独特之处就是在HTML中给$route对应的视图内容占位。ng-view是一个优先级为1000的指令,在之上其他低于这个优先级的指令将不会运行。

ngView指令遵循如下规律:

◊.每次触发$routeChangeSuccess事件,视图都会更新。

◊.如果某个模板同当前的路由相关联:

  • 创建一个新的作用域;
  • 移除上一个视图,同时上一个作用域也会被清除;
  • 将新的作用域同当前模板关联在一起;
  • 如果路由中有相关定义,那么就把对应的控制器同当前作用域关联起来;
  • 触发$viewContentLoaded事件;
  • 如果提供了onload属性,调用该属性所指定的函数。

3.路由

AngularJS提供了when()和otherwise()两个方法来定义路由。例如下面所示:

var app = angular.module('my-app', ['ngRoute', 'routeCtrls']);
app.config(['$routeProvider',function ($routeProvider) {
    //这里定义路由
    $routeProvider
            .when('/home', {
                templateUrl: 'view/home.html',
                controller: 'HomeController'
            }).when('/cs', {
                template: '<h2>Route {{cs.text}}</h2>',
                controller: 'csController'
            }).when('/about', {
                templateUrl: 'view/about.html',
                controller: 'aboutController'
            }).otherwise({
                redirectTo: '/home'
            })
}]);

现在来解释一下when()方法。这个方法接受两个参数:

第一个参数是路由路径,这个路径会与$location.path(当前路径)进行匹配,如果需要配置参数,参数需要以冒号开头。如/cs:id等。访问这个参数的方法是通过$routeParams读取。

.when('/cs/:name', {
    template: '<h2>{{cs1.text}} zhou</h2>',
    controller: 'csController2'
})
.when('/cs:id', {
    template: '<h2>{{cs2.text}} 12</h2>',
    controller: 'csController3'
})
.when('/Chapter/:chapterId/Section/:sectionId', {
    template: '<h1>chapterId: 1;sectionId: 2;search:moby</h1>',
    controller: 'csController4'
})
<!---------------------------------------------------------------------->
routeCtrls.controller('csController2', function ($scope, $routeParams) {
    $scope.cs1 = {
        text: 'cs/:name'
    };
    $routeParams == {name: 'zz'};
});
routeCtrls.controller('csController3', ['$scope', '$routeParams', function ($scope, $routeParams) {
    $scope.cs2 = {
        text: 'cs:ID'
    };
    $routeParams == {id: 12};
}]);
routeCtrls.controller('csController4', ['$scope', '$routeParams', function ($scope, $routeParams) {
    // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
    // Route: /Chapter/:chapterId/Section/:sectionId
    $routeParams == {chapterId: '1', sectionId: '2', search: 'moby'}
}]);

注意上面的集中设置参数的方式。我觉得第1,2中有点不靠谱,因为值不唯一,也可以通过其他的值来访问,最有一种可能是参数比较多,所以访问的地址值是唯一的。这里我也是一知半解,希望有懂的朋友留言,解答一下这个疑惑。

第二个参数是配置对象,配置对象中进行设置的属性包括controller,template,templateURL,resolve,redirectTo和reloadOnSearch。

  • controller:字符串或者函数;如果配置对象中设置了这个属性,那么这个指定的控制器会与路由所创建的新作用域关联起来。
  • template:字符串;如”<h2>{{cs2.text}} 12</h2>”,HTML模板渲染到对应的具有ng-view指令的DOM元素中。
  • templateURL:’字符串’;如”/Chapter/Section.html”,通过指定路径通过XHR读取视图。
  • resolve:对象;如
    return {
        'data':['$http', function ($http) {
            return $http.get('/api').then(function success(resp) {
                return response.data;
            }, function error(reason) {
                return false;
            })
        }]
    }

    如果设置resolve属性,AngularJS会将列表中的元素都注入到控制器中。如果这些依赖是promise对象,它们在控制器加载以及$routeChangeSuccess被触发之前,会被resolve并设置成一个值。列表对象可以是:键,键值是会被注入到控制器中的依赖的名字;工厂,即可以是一个服务的名字,也可以是一个返回值,它是会被注入到控制器中的函数或者可以被resolve的promise对象。上面的例子,resolve会发送一个$http请求,并将data的值替换为返回的结果值。列表中的键data会被注入到控制器中,所以在控制器中可以使用它。

  • redirectTo:字符串或者函数function(route,path,search){}。如果是字符串,那么路径会替换成这个值;如果是函数,那么路径会替换成函数返回的值,这个函数的3个参数route表示从当前路径中提取出的路由参数,path表示当前路径,search表示当前URL中查询串。
  • reloadOnSearch:true(默认)/false。当被设置为true时,$location.search()发生变化时会重新加载路由。反之,false,$location.search()发生变化时则不会重新加载。

这里说一下在view文件夹下有两个html模板,

home.html如下

<h3>首页</h3>
<p>{{ greeting.text }} , Angular</p>
<p>欢迎来带iTattoo的博客</p>

about.html如下

<a href="http://www.ninthmirage.com">iTattoo</a>

都是很简单的页面。

4.$location服务

AngularJS提供一个服务用以解析地址栏中的URL,并让你可以访问应用当前路径所对应的路由。$location服务对JS中window.location对象的API进行了封装,并和AngularJS集成在一起。$location服务没有刷新整个页面的能力。如果需要刷新页面,需要使用window.location对象。如下

window.location.href='/reload/page';

$location服务的方法有如下:

  • path():用来获取或修改页面当前的路径。获取$location.path()和修改$location.path(‘/…..’)。
  • replace():实现跳转页面后不能点击后退的按钮。使用方法$location.path(‘/home’);$location.replace();或者$location.path(‘/home’).replace()。
  • absUrl():获取编码后完整的URL。使用方法为$location.absUrl()。
  • hash():获取URL中的hash片段。使用方法为$location.hash()。
  • host():获取URL中的主机。使用方法为$location.host()。
  • port():获取URL中的端口号。使用方法为$location.port()。
  • protocol():获取URL中的协议。使用方法为$location.protocol()。
  • search():获取URL中的查询串。使用方法为$location.search()。这个方法可以接受2个参数。第一个参数search有两种形式(字符串和对象)。如下
    //对象设置查询
    $location.search({name:'Ari',username:'auser'})
    //字符串设置查询
    $location.search("name=Ari&username=auser")
    第二个参数paramValue(字符串)。如果search参数类型是字符串,那么paramValue会做为该参数的值覆盖URL当中的对应值;如果paramValue的值为null,那么对应的参数会被移除掉。
  • url():获取当前页面的URL。使用方法为$location.url()。这个方法接受两个参数。第一个url(字符串),新的url的基础的前缀。replace(字符串),修改后的路径值。

5.路由模式和事件

◊标签模式

标签是AngularJS用来同你的应用内部进行链接的技巧。标签模式是HTML5模式的降级方案,URL路径会以#符号开头。标签模式下不需要重写<a>标签,也不需要任何服务端的支持。没有额外的指定,默认为标签模式。

使用标签模式的URL是这样的。如下

http://www.ninthmirage.com/#!/inb/all

如果要显配置并使用标签模式,需要在模块config函数中进行配置:

angular.module('my-app', ['ngRoute', 'routeCtrls']).config(['$locationProvider', function ($locationProvider) {
    //表示不适用html5模式,使用标签模式
    $locationProvider.html5Mode(false);
    //设置标签模式默认的钱最符号。
    $locationProvider.hashPrefix("#");
}])

◊HTML5模式

使用HTML5模式的URL是这样的。如下

http://www.ninthmirage.com/inb/all

注意当在HTML5模式下的AngularJS中写链接时,永远不要使用相对路径。

◊路由事件

$route服务在不同阶段会触发不同的事件。我们需要给路由设置事件监听器,用$rootScope来监听这些事件。

  • $routeChangeStart:AngularJS在路由变化之前会广播$routeChangeStart事件。这一步中,路由服务会开始加载由变化所需要的所有依赖,并且模板和resolve键中promise也会被resolve。
    var app = angular.module('my-app', []);
    app.run(['$rootScope', '$location', function ($rootScope, $location) {
        $rootScope.$on('$routeChangeStart', function (next, current) {
            //next:表示将要导航到的下一个URL。
            //current:表示路由变化前的URL。
        })
    }]);
  • $routeChangeSuccess:AngularJS在路由的依赖被加载后广播$routeChangeSuccess事件。
    var app = angular.module('my-app', []);
    app.run(['$rootScope', '$location', function ($rootScope, $location) {
        $rootScope.$on('$routeChangeSuccess', function (evt, next, previous) {
            //evt:原始的AngularJS evt对象。
            //next:用户当前所处的路由。
            //previous:上一个路由(如果当前为第一个路由,则为undefined)。
        })
    }])
  • $routeChangeError:AngularJS会在任何一个promise被拒绝或者失败时广播$routeChangeError事件。
    var app = angular.module('my-app', []);
    app.run(['$rootScope', '$location', function ($rootScope, $location) {
        $rootScope.$on('$routeChangeError', function (current, previous, rejection) {
            //current:当前路由的信息。
            //previous:上一个路由的信息。
            //rejection:被拒绝的promise的错误信息。
        })
    }])
  • $routeUpdate:AngularJS在reloadOnSearch属性被设置为false的情况下,重新使用某个控制器的实例时,会广播$routeUpdate事件。

◊搜索引擎:<meta name=’fragment’ content=”!”>

◊异步的地址变化

如果我们想要在作用域的生命周期外使用$location服务,必须用$apply函数将变化抛到应用外部。因为$location服务是基于$digest来驱动浏览器的地址变化,以使路由事件正常工作的。

这里提一点,有一个比较好的AngularJS库,叫Ui-Route,这里留在后面去说明。

三、依赖注入

一个对象通常有三种方式可以获得对其依赖的控制权:

  1. 在内部创建依赖;
  2. 通过全部变量引用;
  3. 在需要的地方通过参数进行传递(这种方式容易带来全局污染)。

AngularJS使用$injetor(注入器服务)来管理依赖关系的查询和实例化。实际上,$injetor负责实例化AngularJS中所有组件,包括应用的模块,指令和控制器等。例如下面:

<div ng-controller="myController">
    <button ng-click="sayHello()">hello</button>
</div>
<script>
    var app = angular.module('my-app', [])
    app.factory('greeter', function () {
        return {
            greet: function (msg) {
                alert(msg);
            }
        }
    });
    app.controller('myController', function ($scope, greeter) {
        $scope.sayHello = function () {
            greeter.greet('hello,Angular');
        }
    });
</script>

再例如

// 创建myModule模块、注册服务
var myModule = angular.module('myModule', []);
myModule.service('myService', function() {
 this.my = 0;
});
// 创建herModule模块、注册服务
var herModule = angular.module('herModule', []);
herModule.service('herService', function() {
 this.her = 1;
});
// 加载了2个模块中的服务
var injector = angular.injector(["myModule","herModule"]);
alert(injector.get("myService").my);
alert(injector.get("herService").her);

Δ.$injector注入器的方法

  • annotate():该方法返回值是一个服务名称组成的数组,这些服务会在实例化时被注入到目标函数中。annotate()方法可以帮助$injector判断哪些服务会在函数被调用时注入进去。该方法接受一个参数:fn(函数或者数组),返回的这个数组,数组元素的值是在调用时被注入到目标函数中的服务的名称。
    angular.injector(['ng','my-app']).annotate(function($q,greeter){})//==>['$q','greeter']
  • get():该方法返回一个服务的实例,可以接受一个参数:name(字符串),表示想要获取实例的名称。
  • has():该方法返回一个布尔值,在$injector能够从自己的注册列表中找到对应的服务时返回true,否则返回false。接受一个参数:name(字符串),表示我们想要注入器在注册列表中查询的服务名称。
  • instantiate():该方法可以创建某个JS类型(也就是Type)的实例。它会通过new操作符调用构造函数,并将所有参数都传递给构造函数。接受两个参数:Type(函数),表示构造函数;local(对象,可选)提供另一种传参的方式。
  • invoke():该方法会调用方法并从$injector中添加方法参数。接受三个参数:fn(函数),表示要调用的函数;self(对象,可选),表示允许我们设置调用方法的this参数;locals(对象,可选),表示提供另一种方法在函数被调用时传递参数名给该函数。invoke()方法返回fn函数返回的值。

Δ.ngMin工具

这个工具,能够减少我们定义依赖关系所需要的工作量。ngMin是一个为AngularJS应用设计的预压缩工具,它会遍历整个AngularJS应用并帮组我们设置好依赖注入。比如:

app.controller('myController', function ($scope, greeter) {
    $scope.sayHello = function () {
        greeter.greet('hello,Angular');
    }
});

会被转换为:

app.controller('myController', ['$scope', 'greeter', function ($scope, greeter) {
    $scope.sayHello = function () {
        greeter.greet('hello,Angular');
    }
}]);

安装方法:

$ npm install -g ngmin

使用方法:

$ npm input.js output.js 或者 $ npm < input.js > output.js

input.js表示源文件,output.js表示转换过后的输出文件。

angular.module()创建、获取、注册angular中的模块

该函数既可以创建新的模块,也可以获取已有模块,是创建还是获取,通过参数的个数来区分。

angular.module(name, [requires], [configFn]);

name:字符串类型,代表模块的名称;

requires:字符串的数组,代表该模块依赖的其他模块列表,如果不依赖其他模块,用空数组即可;

configFn:用来对该模块进行一些配置。

// 传递参数不止一个,代表新建模块;空数组代表该模块不依赖其他模块
var createModule = angular.module("myModule", []);
// 只有一个参数(模块名),代表获取模块
// 如果模块不存在,angular框架会抛异常
var getModule = angular.module("myModule");
// true,都是同一个模块
alert(createModule == getModule);

现在来讲angular依赖注入最重要的注入声明。它有三种注入声明。分别为:推断式注入声明,显式注入声明,行内注入声明

推断式注入声明

顾名思义,如果没有明确声明,AngularJS会假定参数名称就是依赖名称。因此,它会在内部调用函数对象的toString()方法,分析并提取出函数参数列表,然后通过$injector将这些参数注入进对象实例。注入过程如下:

injector.invoke(function($http,gretter){})

注意:这种方法只适用于未经过压缩和混淆的代码。而且AngularJS会帮助我们把属性的参数顺序正确的注入进去。

显式注入声明

AngularJS提供了显式的方法来明确定义一个函数在被调用时需要用到的依赖关系。

可通过$inject属性来实现显式注入声明的功能。函数对象的$inject属性是一个数组,数组元素的类型都是字符串,它们的值就是需要被注入的服务的名称。下面是示例代码:

var aControllerFactory = function aCtroller($scope, greeter) {
    console.log("LOADED controller", greeter);
    //...控制器
};
aControllerFactory.$inject = ['$scope', 'greeter'];//Greeter服务
function greeterService(greeter) {
    console.log("greeter service");
}
//我们应用的控制器
angular.module('my-app', [])
        .controller('myController', aControllerFactory)
        .factory('greeter', greeterService);
//获取注入器并创建一个新的作用域
var injector = angular.injector(['ng', 'my-app']);
controller = injector.get('$controller');
rootScope = injector.get('$rootScope');
newScope = rootScope.$new();
//调用控制器
controller('myController', {$scope: newScope});

注意:这种方法主要适用于源代码被压缩,参数名称发生改变的情况下。而且这种声明,参数顺序很重要,因为$injector数组元素的顺序必须和注入参数的顺序一一对应。

行内注入声明

这种方式其实是一个语法糖,它同前面提到的通过$inject属性进行注入声明的原理完全一样,但允许我们在函数定义时从行内将参数传入。此外,它可以避免定义过程中使用临时变量。

在定义一个AngularJS对象时,行内声明的方式允许我们直接传入一个参数数组而不是一个函数。数组的元素是字符串,它们代表的是可以被注入到对象中依赖的名字,最后一个参数就是依赖注入的目标函数对象本身。例如:

angular.module('my-app').controller('myController', ['$scope', 'greeter', function ($scope, greeter) {
    $scope.sayHello = function () {
        greeter.greet('hello,Angular');
    }
}]);

通常通过括号和声明数组的[]符号来使用。是可以随时使用行内注入声明的。

下面angular中三种声明依赖的方式,如下

// 创建myModule模块、注册服务
var myModule = angular.module('myModule', []);
myModule.service('myService', function() {
 this.my = 0;
});
// 获取injector
var injector = angular.injector(["myModule"]);
// 第一种inference
injector.invoke(function(myService){alert(myService.my);});
// 第二种annotation
function explicit(serviceA) {alert(serviceA.my);};
explicit.$inject = ['myService'];
injector.invoke(explicit);
// 第三种inline
injector.invoke(['myService', function(serviceA){alert(serviceA.my);}]);

$injector扩展:理清angularJS中的$injector、$rootScope和$scope的概念和关联关系

正文完
 0
Chou Neil
版权声明:本站原创文章,由 Chou Neil 于2015-10-23发表,共计11980字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。