首页 > 技术文章 > AngularJS 模块加载

a-lonely-wolf 2016-08-05 23:58 原文

AngularJS模块可以在被加载和执行之前对其自身进行配置。我们可以在应用的加载阶段应用不同的逻辑组。
在模块的加载阶段, AngularJS会在提供者注册和配置的过程中对模块进行配置。在整个AngularJS的工作流中,这个阶段是唯一能够在应用启动前进行修改的部分。

config()函数接受一个参数。

angular.module('myApp', [])
  .config(function($provide) {
});

使用config()函数的语法糖,并在配置阶段执行。例如,我们在某个模块之上创建一个服务或指令时:

angular.module('myApp', [])
       .factory('myFactory', function(){
            var service = {};
            return service;
     }).directive('myDirective', function(){
            return {
                template: '<button>Click me</button>'
            }
    })

AngularJS会在编译时执行这些辅助函数。它们在功能上等同于下面的写法:

angular.module('myApp', [])
       .config(function($provide ,$compileProvider) {      
            $provide.factory('myFactory', function() {
                var service = {};
                return service;
            });
            $compileProvider.directive('myDirective', function() {
                return {
                        template: '<button>Click me</button>'
                };
            });
});

AngularJS会以这些函数书写和注册的顺序来执行它们。

 唯一例外的是constant()方法,这个方法总会在所有配置块之前被执行。

 

当对模块进行配置时,只有少数几种类型的对象可以被注入到config()函数中:提供者和常量。

这种对配置服务进行严格限制的另外一个副作用就是,我们只能注入用provider()语法构建的服务,其他的则不行。
定义多个配置块,它们会按照顺序执行,这样就可以将应用不同阶段的配置代码集中在不同的代码块中

angular.module('myApp', [])
       .config(function($routeProvider) {
            $routeProvider.when('/', {
                controller: 'WelcomeController',
                template: 'views/welcome.html'
            });
        })
       .config(function(ConnectionProvider) {
            ConnectionProvider.setApiKey('SOME_API_KEY');
        });

 运行块

和配置块不同,运行块在注入器创建之后被执行,它是所有AngularJS应用中第一个被执行的方法。run
运行块是AngularJS中与main方法最接近的概念。运行块通常用来注册全局的事件监听器。例如,我们会在.run()块中设置路由事件的监听器以及过滤未经授权的请求。

假设我们需要在每次路由发生变化时,都执行一个函数来验证用户的权限,放置这个功能唯一合理的地方就是run方法:

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

 

多重视图和路由

  从1.2版本开始, AngularJS将ngRoutes从核心代码中剥离出来成为独立的模块。我们需要安装并引用它,才能够在AngularJS应用中正常地使用路由功能

创建一个独立的路由:用when方法来添加一个特定的路由。有两个参数(when(path,route))。

angular.module('myApp', []).
       .config(['$routeProvider', function($routeProvider) {
            $routeProvider.when('/', {
                templateUrl: 'views/home.html',
                controller: 'HomeController'
            });
        }]);

稍微复杂一点:

angular.module('myApp', []).
       .config(['$routeProvider', function($routeProvider) {
            $routeProvider.when('/', {
                templateUrl: 'views/home.html',
                controller: 'HomeController'
            })
            .when('/login', {
                templateUrl: 'views/login.html',
                controller: 'LoginController'
            })
            .when('/dashboard', {
                templateUrl: 'views/dashboard.html',
                controller: 'DashboardController',
                resolve: {
                    user: function(SessionService) {
                        return SessionService.getCurrentUser();
                    }
                }
            })
            .otherwise({
                redirectTo: '/'
            });
        }]);

 1. controller     controller: 'MyController'  或者   controller: function($scope) {}

如果配置对象中设置了controller属性,制器会与路由所创建的新作用域关联在一起。如果参数值是字符型,会在模块中所有注册过的控制器中查找,如果参数值是函数,函数会作为模板中DOM元素的控制器并与模板进行关联

2. template
template: '<div><h2>Route</h2></div>'    渲染到对应的具有ng-view指令的DOM元素中

5. redirectTo
redirectTo: '/home'
// 或者
redirectTo: function(route,path,search)
如果redirectTo属性的值是一个字符串,那么路径会被替换成这个值,如果redirectTo属性的值是一个函数,那么路径会被替换成函数的返回值,并根据这个目标路径触发路由变化。

$routeParams

如果我们在路由参数的前面加上:, AngularJS就会把它解析出来并传递给$routeParams。

$routeProvider.when('/inbox/:name', {
    controller: 'InboxController',
    templateUrl: 'views/inbox.html'
})

在$routeParams中添加一个名为name的键,它的值会被设置为加载进来的URL中的值。如果浏览器加载/inbox/all这个URL,$routeParams:{  name:'all'  }
如果想要在控制器中访问这些变量,需要把$routeParams注入进控制器:

app.controller('InboxController',function($scope,$routeParams) {
    // 在这里访问$routeParams
});;

$location 服务

用以解析地址栏中的URL,可以访问应用当前路径所对应的路由。它同样提供了修改路径和处理各种形式导航的能力。
$location服务对JavaScript中的window.location对象的API进行了更优雅地封装,并且和AngularJS集成在一起。

当应用需要在内部进行跳转时是使用$location服务的最佳场景,比如当用户注册后、修改或者登录后进行的跳转.
$location服务没有刷新整个页面的能力。如果需要刷新整个页面,需要使用$window.location对象(window.location的一个接口)。
$location.path(); // 返回当前路径

$location.path('/'); // 把路径修改为'/'路由

如果你希望跳转后用户不能点击后退按钮.AngularJS提供了replace()方法来实现

$location.path('/home');
$location.replace();
// 或者
$location.path('/home').replace();

路由模式

路由模式决定你的站点的URL长成什么样子。

标签模式

 标签模式是HTML5模式的降级方案, URL路径会以#符号开头。  使用标签模式的URL看起来是这样的:http://yoursite.com/#!/inbox/all
要显式指定配置并使用标签模式,需要在应用模块的config函数中进行配置:

angular.module('myApp', ['ngRoute'])
       .config(['$locationProvider', function($locationProvider) {
            $locationProvider.html5Mode(false);
       $locationProvider.hashPrefix('!'); //这一句要不要都行 }]);

HTML5 模式

 $location服务通过HTML5历史API让应用能够使用普通的URL路径来路由。当浏览器不支持HTML5历史API时, $location服务会自动使用标签模式的URL作为替代方案。

在HTML5模式中, AngularJS会负责重写<a href=""></a>中的链接。也就是说AngularJS会根据浏览器的能力在编译时决定是否要重写href=""中的链接

 例如<a href="/person/42?all=true">Person</a>这个标签,在老式浏览器中会被重写成标签模式的URL: /index.html#!/person/42?all=true。但在现代浏览器中会URL会保持本来的样子

 

当在HTML5模式的AngularJS中写链接时,永远都不要使用相对路径。如果你的应用是在根路径中加载的,这不会有什么问题,但如果是在其他路径中, AngularJS应用就无法正确处理路由了。

12.5.2 路由事件

$route服务在路由过程中的每个阶段都会触发不同的事件,可以为这些不同的路由事件设置监听器并做出响应。
我们需要给路由设置事件监听器,用$rootScope来监听这些事件。

1. $routeChangeStart

在路由变化之前会广播  $routeChangeStart事件,路由服务会开始加载路由变化所需要的所有依赖,并且模板和resolve键中的promise也会被resolve。

angular.module('myApp', [])
       .run(['$rootScope', '$location', function($rootScope, $location) {
            $rootScope.$on('$routeChangeStart', function(evt, next, current) {
                
            });
        }]);

$routeChangeStart事件带有两个参数:
   将要导航到的下一个URL;     路由变化前的URL。

2. $routeChangeSuccess
AngularJS会在路由的依赖被加载后广播$routeChangeSuccess事件。

angular.module('myApp', [])
       .run(['$rootScope', '$location', function($rootScope, $location) {
            $rootScope.$on('$routeChangeSuccess', function(evt, next, previous) {
                
            });
        }]);

$routeChangeStart事件带有三个参数:  

   原始的AngularJS evt对象;     用户当前所处的路由;     上一个路由(如果当前是第一个路由,则为undefined)。

3. $routeChangeError
AngularJS会在任何一个promise被拒绝或者失败时广播$routeChangeError事件。

angular.module('myApp', [])
       .run(function($rootScope, $location) {
            $rootScope.$on('$routeChangeError', function(current, previous, rejection) {
                
            });
        });

$routeChangeError事件有三个参数:
   当前路由的信息;     上一个路由的信息;     被拒绝的promise的错误信息。

 

依赖注入

一个对象通常有三种方式可以获得对其依赖的控制权:
(1) 在内部创建依赖;
(2) 通过全局变量进行引用;
(3) 在需要的地方通过参数进行传递。

 

依赖注入是通过第三种方式实现的。其余两种方式会带来各种问题,例如污染全局作用域,使隔离变得异常困难等。

依赖注入会事先自动查找依赖关系,并将注入目标告知被依赖的资源,这样就可以在目标需要时立即将资源注入进去。

 在运行期,注入器会创建依赖的实例,并负责将它传递给依赖的消费者。

// 出自Angular文档的优秀示例
function SomeClass(greeter) {
    this.greeter = greeter;
}
SomeClass.prototype.greetName = function(name) {
    this.greeter.greet(name);
};

//示例代码在全局作用域上创建了一个控制器,这并不是一个好主意,这里只是为了方便演示。

SomeClass能够在运行时访问到内部的greeter,不关心如何获得对greeter的引用。为了获得对greeter实例的引用, SomeClass的创建者会负责构造其依赖关系并传递进去。


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

 

 在运行时, 任何模块启动时$injetor都会负责实例化,并将其需要的所有依赖传递进去。

 一个简单的应用,声明了一个模块和一个控制器:

angular.module('myApp', [])
       .factory('greeter', function() {
            return {
                greet: function(msg) {alert(msg);}
            }
        })
        .controller('MyController',function($scope, greeter) {
            $scope.sayHello = function() {
                greeter.greet("Hello!");
            };
        });

当AngularJS实例化这个模块时,会查找greeter并自然而然地把对它的引用传递进去:

<div ng-app="myApp">
    <div ng-controller="MyController">
        <button ng-click="sayHello()">Hello</button>
    </div>
</div>

而在内部, AngularJS的处理过程是下面这样的:

var injector = angular.injector(['ng', 'myApp']);    // 使用注入器加载应用
var $controller = injector.get('$controller');        // 通过注入器加载$controller服务: 
var scope = injector.get('$rootScope').$new();
// 加载控制器并传入一个作用域,同AngularJS在运行时做的一样
var MyController = $controller('MyController', {$scope: scope})

代码中没有说明是如何找到greeter,但是它的确能正常工作,因为$injector会负责为我们查找并加载它。
通过annotate函数,在实例化时从传入的函数中把参数列表提取出来。


任何一个AngularJS的应用中,都有$injector在进行工作,当编写控制器时,如果没有使用[]标记或进行显式的声明, $injector就会尝试通过参数名推断依赖关系

推断式注入声明

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

例如: 注入过程如下:  injector.invoke(function($http, greeter) {});    只适用于未经过压缩和混淆的代码,因为AngularJS需要原始未经压缩的参数列表来进行解析。(此时,参数的顺序 没有什么 意义了)

 

显式注入声明

显式的方法来明确定义一个函数在被调用时需要用到的依赖关系。通过这种方法声明依赖,即使在源代码被压缩、参数名称发生改变的情况下依然能够正常工作。

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

var aControllerFactory =function aController($scope, greeter) {
        console.log("LOADED controller", greeter);
// ……控制器
};
aControllerFactory.$inject = ['$scope', 'greeter']; // Greeter服务

// 我们应用的控制器
angular.module('myApp', [])
       .controller('MyController', aControllerFactory)
       .factory('greeter', greeterService);

// 获取注入器并创建一个新的作用域
    var injector = angular.injector(['ng', 'myApp']),
    controller = injector.get('$controller'),
    rootScope = injector.get('$rootScope'),
    newScope = rootScope.$new();
    
// 调用控制器
controller('MyController', {$scope: newScope});

这种声明方式来讲,参数顺序是非常重要的,$inject数组元素的顺序必须和注入参数的顺序一一对应。这种声明方式可以在压缩后的代码中运行,

行内注入声明

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

angular.module('myApp')
       .controller('MyController', ['$scope', 'greeter', function($scope, greeter) {

        }]);

处理的是一个字符串组成的列表,行内注入声明也可以在压缩后的代码中正常运行。  也是有顺序的

服 务:

出于内存占用和性能的考虑,控制器只会在需要时被实例化,并且不再需要就会被销毁。这意味着每次切换路由或重新加载视图时,当前的控制器会被AngularJS清除掉。
服务提供了一种能在应用的整个生命周期内保持数据的方法,它能够在控制器之间进行通信,并且能保证数据的一致性。(能够在控制器中 复用)
服务是一个单例对象,在每个应用中只会被实例化一次(被$injector实例化),并且是延迟加载的(需要时才会被创建)。

以AngularJS的$http服务为例,提供了对浏览器的XMLHttpRequest对象的底层访问功能,通过$http的API同XMLHttpRequest进行交互,而不需要因为调用这些底层代码而污染应用。

// 示例服务,在应用的整个生命周期内保存current_user
angular.module('myApp', [])
       .factory('UserService', function($http) {
            var current_user;
            return {
                getCurrentUser: function() {
                    return current_user;
                },
                setCurrentUser: function(user) {
                    current_user = user;
                }
            };
        });

在AngularJS中创建自己的服务是非常容易的:只需要注册这个服务即可。服务被注册后,AngularJS编译器就可以引用它,并且在运行时把它当作依赖加载进来。

注册一个服务

使用angular.module的factory API创建服务,是最常见也是最灵活的方式:

angular.module('myApp.services', [])
       .factory('githubService', function() {
            var serviceInstance = {};
            // 我们的第一个服务
            return serviceInstance;
        });

用githubService作为名字注册成为这个AngularJS应用的一个服务了。

服务的工厂函数用来生成一个单例的对象或函数,这个对象或函数就是服务,它会存在于应用的整个生命周期内。当我们的AngularJS应用加载服务时,这个函数会被执行并返回一个单例的服务对象

同创建控制器的方法一样,服务的工厂函数既可以是一个函数也可以是一个数组:

// 用方括号声明工厂,声明了一个 服务: githubService
angular.module('myApp.services', [])
       .factory('githubService', [function($http) { }]);

 

使用服务

可以在控制器、指令、过滤器或另外一个服务中通过依赖声明的方式来使用服务。

将服务的名字当作参数传递给控制器函数,可以将服务注入到控制器中。当服务成为了某个控制器的依赖,就可以在控制器中调用任何定义在这个服务对象上的方法。

angular.module('myApp', ['myApp.services'])
       .controller('ServiceController', function($scope, githubService) {
            // 我们可以调用对象的事件函数
            $scope.events = githubService.events('auser');
        });

  githubService服务已经被注入到ServiceController中,可以像使用任何其他服务一样使用它.

//自己修改后的,原来的代码,用到$http 所以就没用。了解原理就行
angular.module('services', [])
       .factory('githubService', function($http) {
            // 返回带有一个events函数的服务对象
            return {
                events: function(username) {
                    return [{actor:"一号",name:"Linda"},{actor:"二号",name:"Joke"},
                    {actor:"三号",name:"elm"},{actor:"四号",name:"LIm"}];
                }
            }
        });

var app = angular.module("app",['services']);
app.controller('ServiceController', function($scope,githubService) { 
        //     注意username属性的变化,    如果有变化就运行该函数    
        $scope.$watch('username',function(newname){
            console.log($scope.count);
            // 从使用JSONP调用Github API的$http服务中返回promise
            $scope.events = githubService.events(newname);
            /*.success(function(data, status, headers) {
                // success函数在数据中封装响应
                // 因此我们需要调用data.data来获取原始数据
                $scope.events = data.data;
            })*/
        });
    });

<div ng-controller="ServiceController">
  <label for="username">Type in a GitHub username</label>
  <input type="text" ng-model="username" placeholder="Enter a GitHub username" /><br>
  <ul>
    <li ng-repeat="event in events">
    {{ event.actor }} {{ event.name }}
    </li>
  </ul>
</div>

不推荐在控制器中使用$watch,这里只是为了方便演示。在实际生产中会将这个功能封装进一个指令,并在指令中设置$watch

 

内置服务$timeout来介绍一下这个延时。同注入githubService一样,需要将$timeout服务注入到控制器中:
  app.controller('ServiceController', function($scope, $timeout, githubService) {
});  

我觉得最好写成这样: 行内形式声明

app.controller('ServiceController',['$scope','$timeout',"githubService",function($scope, $timeout, githubService) {
    }]);

在自定义服务之前注入所有的AngularJS内置服务,这是约定俗成的规则。

 

$timeout服务会取消所有网络请求,并在输入字段的两次变化之间延时350 ms。换句话说,如果用户两次输入之间有350 ms的间隔,就推断用户已经完成了输入,然后开始向GitHub发送请求:

app.controller('ServiceController', function($scope, $timeout, githubService) {
        // 和上面的示例一样, 添加了$timeout服务
        var timeout;
        $scope.$watch('username', function(newUserName) {
            if (newUserName) {
                // 如果在进度中有一个超时(timeout)
                if (timeout) $timeout.cancel(timeout);
                    timeout = 
                    $timeout(function() {
                        githubService.events(newUserName)
                                .success(function(data, status) {
                                    $scope.events = data.data;
                                });
                            }, 350);
                    }
            });
        });

 在控制器之间共享数据,需要在服务中添加一个用来储存用户名的方法。服务在应用的生命周期内是单例模式的,因此可以将用户名安全地储存在其中。

angular.module('app',[])
       .factory('githubService', function($http) {
            var githubUsername;
            var runUserRequest = function(path) {
                    // 从使用JSONP的Github API的$http服务中返回promise
                    return $http({    });
                };
        
            return {
                events: function() {
                    return runUserRequest('events');
                },
                setUsername: function(username) {
                    githubUsername = username;
                }
            };
        });
//服务中有一个setUsername方法,用来保存当前的GitHub用户名了。
//可以来获取用户名
angular.module('myApp', ['myApp.services'])
       .controller('ServiceController',
            function($scope, githubService) {
                $scope.setUsername = githubService.setUsername;
});

创建服务时的设置项

有5种方法用来创建服务:
 factory()
 service()
 constant()
 value()
 provider()

factory()    是创建和配置服务的最快捷方式。 接受两个参数, name(字符串)  服务名,   getFn(函数)或者   一个包含可被注入对象的数组  【一个特殊的数组,前面是字符串,最后一个是函数】,  创建服务实例时被调用。

angular.module('myApp')
       .factory('myService', function() {
            return {
                'username': 'auser'
            };
        })
angular.module('myApp')
       .factory('githubService', ['$http', function($http) {
            return {
                getUserEvents: function(username) {
                    
                }
            };
        }]);

 

服务是单例对象, getFn在应用的生命周期内只会被调用一次。

 

service()

一个支持构造函数的服务,它允许我们为服务对象注册一个构造函数
两个参数。   name(字符串)   constructor(函数)  构造函数,我们调用它来实例化服务对象。
service()函数会在创建实例时通过new关键字来实例化服务对象。

var Person = function($http) {
    this.getName = function() {
        return $http({ method: 'GET', url: '/api/user'});
    };
};
angular.service('personService', Person);

provider()
所有服务工厂都是由$provide服务创建的, $provide服务负责在运行时初始化这些提供者。提供者是一个具有$get()方法的对象, $injector通过调用$get方法创建服务实例。
$provider提供了数个不同的API用于创建服务,每个方法都有各自的特殊用途

所有创建服务的方法都构建在provider方法之上。 provider()方法负责在$providerCache中注册服务。

我们假定传入的函数就是$get()时, factory()函数就是用provider()方法注册服务的简略形式。

//两种方法的作用完全一样,并且会创建同一个服务

angular.module('myApp')
       .factory('myService', function() {
            return {
                'username': 'auser'
            };
        })
        // 这与上面工厂的用法等价
        .provider('myService', {
            $get: function() {
                return {
                    'username': 'auser'
                };
            }
        });

是否可以一直使用.factory()方法来代替.provider()呢?

取决于是否需要用AngularJS的.config()函数来对.provider()方法返回的服务进行额外的扩展配置。config()方法可以被注入特殊的参数。

// 使用`.provider`注册该服务
angular.module('myApp', [])
       .provider('githubService', function($http) {
            // 默认的,私有状态
            var githubUrl = 'https://github.com',
            setGithubUrl: function(url) {
                // 通过.config改变默认属性
                if (url) { githubUrl = url }
            },
            method: JSONP, // 如果需要,可以重写
            $get: function($http) {
                self = this;
                return $http({ method: self.method, url: githubUrl + '/events'});
            }
        });

通过使用.provider()方法,可以在多个应用使用同一个服务时获得更强的扩展性,特别是在不同应用或开源社区之间共享服务时。
如果希望在config()函数中可以对服务进行配置,必须用provider()来定义服务

 

provider()  两个参数。   name(字符串)  name参数在providerCache中是注册的名字。name+Provider会成为服务的提供者。同时name也是服务实例的名字
            aProvider(对象/函数/数组)    

                如果aProvider是函数,那么它会通过依赖注入被调用,并且负责通过$get方法返回一个对象
                如果aProvider是数组,会被当做一个带有行内依赖注入声明的函数来处理。数组的最后一个元素应该是函数,可以返回一个带有$get方法的对象。
                如果aProvider是对象,它应该带有$get方法。

   provider()函数返回一个已经注册的提供者实例。

直接使用provider() API是最原始的创建服务的方法:
// 在模块对象上直接创建provider的例子
angular.module('myApp', [])
       .provider('UserService', {
            favoriteColor: null,
            setFavoriteColor: function(newColor) {
                this.favoriteColor = newColor;
            },
            // $get函数可以接受injectables
            $get: function($http) {
                return {
                    'name': 'Ari',
                    getFavoriteColor: function() {
                        return this.favoriteColor || 'unknown';
                    }
                };
            }
        });

用这个方法创建服务,必须返回一个定义有$get()函数的对象,否则会导致错误。

 

可以通过注入器来实例化服务

var injector = angular.injector(['myApp']); // Invoke our service
injector.invoke(['UserService', function(UserService) {
            // UserService returns
            // {
            //         'name': 'Ari',
            //         getFavoriteColor: function() {}
            // }
        }]);

 

constant()       

将一个已经存在的变量值注册为服务,并将其注入到应用的其他部分当中。constant()方法返回一个注册后的服务实例。

angular.module('myApp') .constant('apiKey','123123123');

这个常量服务可以像其他服务一样被注入到配置函数中:

angular.module('myApp')
       .controller('MyController', function($scope, apiKey) {
            // 可以像上面一样用apiKey作为常量
            // 用123123123作为字符串的值
            $scope.apiKey = apiKey;
});

value()

如果服务的$get方法返回的是一个常量,通过value()函数方便地注册服务。

value()方法返回以name参数的值为名称的注册后的服务实例。

angular.module('myApp').value('apiKey','123123123');

何时使用value()constant()
value()方法和constant()方法之间最主要的区别是, 常量可以注入到配置函数中,而值不行。

可以通过value()来注册服务对象或函数,用constant()来配置数据。

angular.module('myApp', [])
       .constant('apiKey', '123123123')
       .config(function(apiKey) {
        // 在这里apiKey将被赋值为123123123
        // 就像上面设置的那样
       })
       .value('FBid','231231231')
       .config(function(FBid) {
        // 这将抛出一个错误,未知的provider: FBid
        // 因为在config函数内部无法访问这个值
       });

 

decorator()

$provide服务提供了在服务实例创建时对其进行拦截的功能,可以对服务进行扩展,或者用另外的内容完全代替它。

装饰器是非常强大的,它不仅可以应用在我们自己的服务上,也可以对AngularJS的核心服务进行拦截、中断甚至替换功能的操作。事实上 AngularJS中很多功能的测试就是借助$provide.decorator()建立的。


例如,我们想给之前定义的githubService服务加入日志功能,可以借助decorator()函数方便地实现这个功能,而不需要对原始的服务进行修改

decorator()  接受两个参数。  name(字符串) 将要拦截的服务名称。       decoratorFn(函数)  在服务实例化时调用该函数,这个函数由injector.invoke调用,可以将服务注入这个函数中
$delegate是可以进行装饰的最原始的服务,为了装饰其他服务,需要将其注入进装饰器。

一个例子:

var githubDecorator = function($delegate,$log) {
            var events = function(path) {
                var startedAt = new Date();
                var events = $delegate.events(path);
                // 事件是一个promise 
                events.finally(function() {
                    $log.info("Fetching events" +" took " +(new Date() - startedAt) + "ms");});
                return events;
            };
            return {
                    events: events
            };
};

angular.module('myApp')
       .config(function($provide) {
            $provide.decorator('githubService',githubDecorator);
       });

 

XHR和服务器通信

使用$http

使用内置的$http服务直接同外部进行通信,$http服务简单的封装了原生的XMLHttpRequest对象。

$http只能接受一个参数,参数是一个对象,包含了用来生成HTTP请求的配置内容。    返回一个promise对象,具有success和error两个方法。

$http({method: 'GET',url: '/api/users.json'})
    .success(function(data,status,headers,config) {
        // 当相应准备就绪时调用
    }).error(function(data,status,headers,config) {
        // 当响应以错误状态返回时调用
    });

 

$http方法返回一个promise对象,可以在响应返回时用then方法来处理回调。如果使用then方法,会得到一个特殊的参数,它代表了相应对象的成功或失败信息,还可以接受两个可选的函数作为参数。或者可以使用success和error回调代替

var promise = $http({method: 'GET',url: '/api/users.json'});
promise.then(function(resp){
                // resp是一个响应对象
            }, function(resp) {
                // 带有错误信息的resp
        });
// 或者使用success/error方法
promise.success(function(data, status, headers, config){
        // 处理成功的响应
});
// 错误处理
promise.error(function(data, status, headers, config){
// 处理非成功的响应
});
//如果响应状态码在200和299之间,会认为响应是成功的, success回调会被调用,否则error回调会被调用。

 

then()方法与其他两种方法的主要区别是,它会接收到完整的响应对象,而success()和error()则会对响应对象进行析构。

//一个小例子:
var app = angular.module("app",[]);
app.controller("appcontrol",["$scope","$http",function($scope,$http){
        $http({
            method:"GET",
            url:"name.json"
        }).success(function(data,status,headers,config){
            console.log("OK");
            $scope.books = data;
            console.log($scope.books);
        }).error(function(data,status,headers,config){
            console.log("error");
        });
    }])

<div ng-controller="appcontrol">
  <ul ng-repeat="book in books">
    <li>{{book.name}}=={{book.price}}</li>
  </ul>
</div>

 

 

 

 

快捷方法:

// 快捷的GET请求
$http.get('/api/users.json');  可以接受两个参数。   url(字符串)    config(可选,对象)  这是一个可选的设置对象。 

4. jsonp()
这是用来发送JSONP请求的快捷方式。  参数 同上

$http.jsonp("/api/users.json?callback=JSON_CALLBACK");

5. post()
这是用来发送POST请求的快捷方式。

post()函数可以接受三个参数。
 url(字符串) 代表请求的目的地。    data(对象或字符串) 这个对象包含请求的数据。   config(可选,对象)  这是一个可选的设置对象

 

设置对象

当我们将$http当作函数来调用时,需要传入一个设置对象,用来说明如何构造XHR对象。

$http({
    method: 'GET',
    url: '/api/users.json',
    params: {
        'username': 'auser'
    }
});

设置对象可以包含以下键。

1. method(字符串)    ‘GET’、‘DELETE’、‘HEAD’、 ‘JSONP’、 ‘POST’、 ‘PUT’。

2. url(字符串)

 

3.params(字符串map或对象)这个键的值是一个字符串map或对象,会被转换成查询字符串追加在URL后面。如果值不是字符串,会被JSON序列化。

// 参数会转化为?name=ari的形式

$http({  params: {'name': 'ari'}  })

4. data(字符串或对象)
这个对象中包含了将会被当作消息体发送给服务器的数据。通常在发送POST请求时使用。

var blob = new Blob(['Hello World'], {type: 'text/plain'});
$http({
    method: 'POST',
    url: '/',
    data: blob
});

 

响应对象


AngularJS传递给then()方法的响应对象包含四个属性。

 

 data(字符串或对象)
这个数据代表转换过后的响应体(如果定义了转换的话)。
 status(数值型)
响应的HTTP状态码。
 headers(函数)
这个函数是头信息的getter函数,可以接受一个参数,用来获取对应名字的值。例如,用如
下代码获取X-Auth-ID的值:
$http({
  method: 'GET',
  url: '/api/users.json'
  }).then (resp) {
  // 读取X-Auth-ID
  resp.headers('X-Auth-ID');
  });
 config(对象)
这个对象是用来生成原始请求的完整设置对象。
 statusText(字符串)
这个字符串是响应的HTTP状态文本。

 

缓存 HTTP 请求

默认情况下, $http服务不会对请求进行本地缓存。在发送单独的请求时,我们可以通过向$http请求传入一个布尔值或者一个缓存实例来启用缓存。

$http.get('/api/users.json',{ cache: true })
.success(function(data) {})
.error(function(data) {});

第一次发送请求时, $http服务会向/api/users.json发送一个GET请求。第二次发送同一个GET请求时, $http服务会从缓存中取回请求的结果,而不会真的发送一个HTTP GET请求。

如果想要对AngularJS使用的缓存进行更多的自定义控制,可以向请求传入一个自定义的缓存实例代替true。

每次发送请求时都传入一个自定义缓存是很麻烦的事情(即使是在服务中)。可以通过应用的.config()函数给所有$http请求设置一个默认的缓存:

angular.module('myApp', [])
       .config(function($httpProvider, $cacheFactory) {
            $httpProvider.defaults.cache = $cacheFactory('lru', {
                    capacity: 20
                });
        });
//$cacheFactory('lru', {capacity: 20});  就是自定义的缓存实例

 

拦截器

果我们想要为请求添加全局功能,例如身份验证、错误处理等,在请求发送给服务器之前或者从服务器返回时对其进行拦截,是比较好的实现手段。
一共有四种拦截器,两种成功拦截器,两种失败拦截器。

拦截器的核心是服务工厂,通过向$httpProvider.interceptors数组中添加服务工厂,在$httpProvider中进行注册。

 request    $http设置对象来对请求拦截器进行调用。    返回一个更新过的设置对象,或者一个可以返回新的设置对象的promise。

 response      $http设置对象来对响应拦截器进行调用      返回一个更新过的响应,或者一个可以返回新响应的promise。

 requestError

 responseError

 

 

调用模块的.factory()方法来创建拦截器,可以在服务中添加一种或多种拦截器:

angular.module('myApp', [])
       .factory('myInterceptor', function($q) {
            var interceptor = {
                'request': function(config) {
                    // 成功的请求方法
                    return config; // 或者 $q.when(config);
                },
                'response': function(response) {
                    // 响应成功
                    return response; // 或者 $q.when(config);
                },
                'requestError': function(rejection) {
                    // 请求发生了错误,如果能从错误中恢复,可以返回一个新的请求或promise
                    return response; // 或新的promise
                    // 或者,可以通过返回一个rejection来阻止下一步
                    // return $q.reject(rejection);
                },
                'responseError': function(rejection) {
                    // 请求发生了错误,如果能从错误中恢复,可以返回一个新的响应或promise
                    return rejection; // 或新的promise
                    // 或者,可以通过返回一个rejection来阻止下一步
                    // return $q.reject(rejection);
                }
            };
            return interceptor;
        });

//我们需要使用$httpProvider在.config()函数中注册拦截器:
angular.module('myApp', [])
       .config(function($httpProvider) {
            $httpProvider.interceptors.push('myInterceptor');
        });

 

 

设置$httpProvider

使用.config()可以向所有请求中添加特定的HTTP头

默认的请求头保存在$httpProvider.defaults.headers.common对象中。默认的头如下所示:
Accept: application/json, text/plain, */*
通过.config()函数可以对这些头进行修改或扩充,如下所示:

angular.module('myApp', [])
       .config(function($httpProvider) {
             $httpProvider.defaults.headers
                .common['X-Requested-By'] = 'MyAngularApp';
        });

 

 

 

Restangular是一个专门用来从外部读取数据的AngularJS服务。

 

尽管$http和$resource是AngularJS的内置服务,但这两个服务在某些方面的功能是有限的。 Restangular通过完全不同的途径实现了XHR通信,并提供了良好的使用体验

1. promise
Restangular支持promise模式的异步调用,使用起来更符合AngularJS的习惯。可以像使用原
始的$http方法一样对响应进行链式操作。
2. promise展开
也可以像使用$resource服务一样使用Restangular,通过很简单的方式同时操作promise和对象。
3. 清晰明了
Restangular库几乎没有复杂或神奇的东西,无需通过猜测或研究文档就可以知道它是如何工
作的。
4. 全HTTP方法支持
Restangular支持所有的HTTP方法。
5. 忘记URL
$resource要求明确的指定想要拉取数据的URL, Restangular并不需要事先知道URL或提前
指定它们(除基础URL外)。
6. 资源嵌套
Restangular可以直接处理嵌套的资源,无需创建新的Restangular实例。
7. 一个实例
同$resource不同,使用过程中仅需要创建一个Restangular资源对象的实例。

 

Restangular依赖Lo-Dash或Underscore,因此为了确保Restangular可以正常运行,需要引入这两个库中的一个。

<script type="text/javascript" src="/js/vendor/lodash/dist/lodash.min.js"></script>
<script type="test/javascript" src="js/vendor/restangular.min.js"></script>

 

同其他的AngularJS库一样,我们需要将restangular资源当作依赖加载进应用模块对象。
    angular.module('myApp', ['restangular']);
完成后,就可以将Restangular服务注入到AngularJS对象中:
angular.module('myApp', [])
    .factory('UserService', ['Restangular', function(Restangular) {
      // 现在我们已经在UserService中访问了Restangular
}])

Restangular有两种方式创建拉取数据的对象。可以为拉取数据的对象设置基础路由:

var User = Restangular.all('users');  会让所有的HTTP请求将/users路径作为根路径来拉取数据。

    调用上述对象的getList()方法会从/users拉取数据:  var allUsers = User.getList(); // GET /users

也可通过单个对象来发送嵌套的请求,用唯一的ID来代替路由发送请求:  var oneUser = Restangular.one('users', 'abc123');

调用oneUser上的get()时向/users/abc123发送请求。
oneUser.get().then(function(user) {

    // GET /users/abc123/inboxes
    user.getList('inboxes');
});

Restangular非常聪明,知道如何根据在Restangular源对象上调用的方法来构造URL。但设置拉取数据的URL是很方便的,特别是当后端不支持纯粹的RESTful API时。

var messages = Restangular.all('messages');
通过这个对象,可以使用getList()来获取所有信息。 getList()方法返回了一个集合,其中包含了可以用来操作特定集合的方法

Restangular返回的是增强过的promise对象,因此除了可以调用then方法,还可以调用一些特殊的方法,比如$object。 $object会立即返回一个空数组(或对象),在服务器返回信息后,数组会被用新的数据填充。

// POST到/messages
var newMessage = {
    body: 'Hello world'
};

var messages = Restangular.all('messages');

// 然后在promise中调用
messages.post(newMessage).then(function(newMsg){
        // 首先将消息设置成空数组
        // 然后一旦getList是完整的就填充它
        $scope.messages = messages.getList().$object;
    }, 
    function(errorReason)
        // 出现了一个错误
    });

 

使用remove()方法发送一个DELETE HTTP请求,调用集合中一个对象(或元素)的remove()方法来发送删除请求。

var message = messages.get(123);
message.remove(); // 发送DELETE HTTP请求

 

更新和储存对象 由HTTP PUT方法完成。 Restangular 通过put()方法来支持这个功能。通过put()方法来支持这个功能。
更新一个对象,首先查询这个对象,然后在实例中设置新的属性值,再调用对象的put()方法将更新保存到后端。  在更新对象时使用Restangular.copy()是一个比较好的实践。

嵌套资源是指包含在其他组件内部的组件。

//一个 作者所写的所有的书籍, 作家: abc123
var author = Restangular.one('authors', 'abc123');
// 构建一个GET到/authors/abc123/books的请求
var books = author.getList('books');

也可以在服务器返回的对象上调用:

Restangular.one('authors', 'abc123').then(function(author) {
    $scope.author = author;
});

// 构建一个GET到/authors/abc123/authors的请求
// 使用$scope.author,它是从服务器返回的真实对象
$scope.author.getList('books');

 

Restangular支持所有的HTTP方法。它支持GET、 PUT、 POST、 DELETE、 HEAD、 TRACE、OPTIONS和PATCH。

author.get();         // GET/authors/abc123
author.getList('books');    // GET/authors/abc123/books
author.put();         // PUT/authors/abc123
author.post();         // POST/authors/abc123
author.remove();     // DELETE/authors/abc123
author.head();         // HEAD/authors/abc123
author.trace();     // TRACE/authors/abc123
author.options();     // OPTIONS/authors/abc123
author.patch();     // PATCH/author/abc123

 

下面是一些例子。

      • GET /zoos:                        列出所有动物园
      • POST /zoos:                    新建一个动物园
      • GET /zoos/ID:                 获取某个指定动物园的信息
      • PUT /zoos/ID:                  更新某个指定动物园的信息(提供该动物园的全部信息)
      • PATCH /zoos/ID:              更新某个指定动物园的信息(提供该动物园的部分信息)
      • DELETE /zoos/ID:            删除某个动物园
      • GET /zoos/ID/animals:    列出某个指定动物园的所有动物
      • DELETE /zoos/ID/animals/ID:    删除某个指定动物园的指定动物

 

设置 Restangular

Restangular具有高度的可定制性,可以根据应用的需要进行相应的设置。每个属性都有默认值,所以我们也无需在不必要的情况下对其进行设置。

将RestangularProvider注入到config()函数中,或者将Restangular注入到一个run()函数中,
如果设置Restangular时需要用到其他服务,那么就在run()方法中设置,否则就在config()中进行设置。

通过setBaseUrl()方法给所有后端 API 请求设置 baseUrl

angular.module('myApp', ['restangular'])
       .config(function(RestangularProvider) {
            RestangularProvider.setBaseUrl('/api/v1');
});

 

2. 添加元素转换

使用elementTransformers可以在Restangular对象被加载后为其添加自定义方法。

例如,如果我们只想更新authors资源,可以用如下方法:

angular.module('myApp', ['restangular'])
       .config(function(RestangularProvider) {
                // 3个参数:
                            // route
            RestangularProvider.extendModel('authors', function(element) {
                    element.getFullName = function() {
                        return element.name + ' ' + element.lastName;
                    };
                return element;
            });
        });

 

设置responseInterceptors

Restangular可以设置响应拦截器。responseInterceptors在需要对服务器返回的响应进行转换时非常有用。

responseInterceptors在每个响应从服务器返回时被调用。调用时会传入以下参数。
 data:从服务器取回的数据。
 operation:使用的HTTP方法。
 what:所请求的数据模型。
 url:请求的相对URL。
 response:完整的服务器响应,包括响应头。
 deferred:请求的promise对象。

 

会使getList()返回一个带有元信息的数组,,向/customers发送GET请求会返回一个像{customers: []}这样的数组。

angular.module('myApp', ['restangular'])
       .config(function(RestangularProvider) {
            RestangularProvider.setResponseInterceptor(function(data, operation, what) {
                if (operation == 'getList') {
                    var list = data[what];
                    list.metadata = data.metadata;
                    return list;
                }
                return data;
            });
        });

 

使用requestInterceptors

在将数据实际发送给服务器之前对其进行操作。

自定义Restangular服务

将Restangular服务注入到工厂函数中,就可以方便地对Restangular进行封装。在工厂函数内部,使用withConfig()函数来创建自定义设置

angular.module('myApp', ['restangular'])
       .factory('MessageService', ['Restangular', function(Restangular) {
            var restAngular = Restangular.withConfig(function(Configurer) {
                Configurer.setBaseUrl('/api/v2/messages');
            });
            var _messageService = restAngular.all('messages');
            return {
                getMessages: function() {
                    return _messageService.getList();
                }
            };
        }]);

 

 

XHR实践

跨域和同源策略:同源策略允许页面从同一个站点加载和执行特定的脚本。站外其他来源的脚本同页面的交互则被严格限制。

跨域资源共享(Cross Origin Resource Sharing, CORS)是一个解决跨域问题的好方法,从而可以使用XHR从不同的源加载数据和资源。

JSONP是一种可以绕过浏览器的安全限制,从不同的域请求数据的方法。使用JSONP需要服务器端提供必要的支持。

JSONP的原理是通过<script>标签发起一个GET请求来取代XHR请求。 JSONP生成一个<script>标签并插到DOM中,然后浏览器会接管并向src属性所指向的地址发送请求。

当服务器返回请求时,响应结果会被包装成一个JavaScript函数,并由该请求所对应的回调函数调用。

$http服务中提供了一个JSONP辅助函数。

$http
.jsonp("https://api.github.com?callback=JSON_CALLBACK") .success(function(data) {
// 数据
});

当请求被发送时, AngularJS会在DOM中生成一个如下所示的<script>标签:

<script src="https://api.github.com?callback=angular.callbacks._0"  type="text/javascript"></script> 
//JSON_CALLBACK被替换成了一个特地为此请求生成的自定义函数。

当支持 JSOPN的服务器返回数据时,数据会被包装在由 AngularJS生成的具名函数angular.callbacks._0中。

使用JSONP需要意识到潜在的安全风险。首先,服务器会完全开放,允许后端服务调用应用中的任何JavaScript。

不受我们控制的外部站点(或者蓄意攻击者)可以随时更改脚本,使我们的整个站点变得脆弱。服务器或中间人有可能会将额外的JavaScript逻辑返回给页面,从而将用户的隐私数据暴露出来。

请求是由<script>标签发送的,所以只能通过JSONP发送GET请求。并且脚本的异常也很难处理使用JSONP一定要谨慎,同时只跟信任并可以控制的服务器进行通信

使用 CORS

CORS规范简单地扩展了标准的XHR对象,以允许JavaScript发送跨域的XHR请求。它会通过预检查(preflight)来确认是否有权限向目标服务器发送请求。

告诉AngularJS我们正在使用CORS。使用config()方法在应用模块上设置两个参数以达到此目的,

首先,告诉AngularJS使用XDomain,并从所有的请求中把X-Request-With头移除掉。X-Request-With头默认就是移除掉的,但是再次确认没有坏处。

angular.module('myApp', [])
       .config(function($httpProvider) {
            $httpProvider.defaults.useXDomain = true;
            delete $httpProvider.defaults.headers
            .common['X-Requested-With'];
        });

 

服务器端CORS支持,确保服务器支持CORS是很重要的

CORS请求分为简单和非简单两种类型。

CORS并不是一个安全机制,只是现代浏览器实现的一个标准。在应用中设置安全策略依然是我们的责任。

使用 XML

 假如服务器返回的是XML而非JSON格式的数据,需要将其转换成JavaScript对象。

 以X2JS库为例,这是一个非常好用的开源库,将XML格式转换成JavaScript对象
首先引入:

<script type="text/javascript" src="https://x2js.googlecode.com/hg/xml2json.js"></script>

 创建一个工厂服务,功能就是在DOM中解析XML

angular.factory('xmlParser', function() {
            var x2js = new X2JS();
            return {
                xml2json: x2js.xml2json,
                json2xml: x2js.json2xml_str
            };
        });
//借助这个轻量的解析服务,可以将$http请求返回的XML解析成JSON格式,如下所示:
angular.factory('Data', [$http, 'xmlParser', function($http, xmlParser) {
            $http.get('/api/msgs.xml', {
                transformResponse: function(data) {
                    return xmlParser.xml2json(data);
                }
            });
        });

 

使用 AngularJS 进行身份验证

 

纯客户端身份验证

 

通过令牌授权来实现客户端身份验证,服务器需要做的是给客户端应用提供授权令牌。
令牌本身是一个由服务器端生成的随机字符串,由数字和字母组成,它与特定的用户会话相关联。  uuid库是用来生成令牌的好选择。

 

当用户登录到我们的站点后,服务器会生成一个随机的令牌,并将用户会话同令牌之间建立关联,用户无需将ID或其他身份验证信息发送给服务器

客户端发送的每个请求都应该包含此令牌,这样服务器才能根据令牌来对请求的发送者进行身份验证。
服务器端则无论请求是否合法,都会将对应事件的状态码返回给客户端,这样客户端才能做出响应。

下面的表格中是一些常用的状态码:
状 态 码 含 义

200  一切正常
401 未授权的请求
403 禁止的请求
404 页面找不到
500 服务器错误


当客户端收到这些状态码时会做出相应的响应。

 

数据流程如下:
(1) 一个未经过身份验证的用户浏览了我们的站点;
(2) 用户试图访问一个受保护的资源,被重定向到登录页面,或者用户手动访问了登录页面;
(3) 用户输入了他的登录ID(用户名或电子邮箱)以及密码,接着AngularJS应用通过POST请求将用户的信息发送给服务端;
(4) 服务端对ID和密码进行校验,检查它们是否匹配;
(5) 如果ID和密码匹配,服务端生成一个唯一的令牌,并将其同一个状态码为200的响应一起返回。如果ID和密码不匹配,服务器返回一个状态码为401的响应

有下面几种方法可以将路由定义为公共或非公共。

1. 保护API访问的资源

对一个会发送受保护的API请求的路由进行保护,但又希望可以正常加载页面,可以简单地通过$http拦截器来实现。

创建一个$http拦截器并能够处理未通过身份验证的API请求,首先要创建一个拦截器

 

我们在应用的.config()代码块内设置$http响应拦截器,并将$httpProvider注入其中。

angular.module("app",[])
       .config(function($httpProvider){
               //这里构造拦截器,会处理所有请求的响应 以及 响应错误。
               var interceptor = function($q,$rootScope,Auth){
                   return {
                       "response":function(resp){
                           if(resp.config.url=="/api/login")
                           {
                               //假设后台返回的数据格式 如下:{token:"AUTH_TOKEN"}
                               Auth.setToken(resp.data.token);
                           }
                           return resp;
                       },
                       "responseError":function(rejection){
                           //错误处理,
                           switch(rejection.status){
                               case 401:     if(rejection.config.url !="api/login"){
                                               //如果当前不是登陆页面
                                               $rootScope.$broadcast('auth:loginRequired');
                                           }
                                           break;
                               case 403:     $rootScope.$broadcast('auth:forbidden');
                                        break;

                               case 404:     $rootScope.$broadcast('page:notFound');
                                        break;

                               case 500:     $rootScope.$broadcast('server:error');
                                        break;
                           }

                           return $q.reject(rejection);
                       }
                   }
               }
         // 将拦截器和$http的request/response链整合在一起
               $httpProvider.interceptors.push(interceptor);
       });

 

如果我们希望始终对某些路径进行保护,或者请求的API不会对路由进行保护,那就需要监视路由的变化,以确保访问受保护路由的用户是处于登录状态的。

为了监视路由变化,需要为$routeChangeStart事件设置一个事件监听器。这个事件会在路由属性开始resolve时触发,

通过监听器对事件进行监听,并检查路由,看它是否定义为可被当前用户访问。

先要定义应用的访问规则。可以通过在应用中设置常量,然后在每个路由中通过对比这些常量来判断用户是否具有访问权限。

angular.module('myApp', ['ngRoute'])
     .constant('ACCESS_LEVELS', {
      pub: 1,
      user: 2
   });

把ACCESS_LEVELS设置为常量,可以将它注入到.confgi()和.run()代码块中,并在整个应用范围内使用

//使用这些常量来为每个路由都定义访问级别:
angular.module('myApp', ['ngRoute'])
       .config(function($routeProvider, ACCESS_LEVELS) {
                $routeProvider
                    .when('/', {
                        controller: 'MainController',
                        templateUrl: 'views/main.html',
                        access_level: ACCESS_LEVELS.pub
                    })
                    .when('/account', {
                        controller: 'AccountController',
                        templateUrl: 'views/account.html',
                        access_level: ACCESS_LEVELS.user
                    })
                    .otherwise({
                        redirectTo: '/'
                    });
        });

 

为了验证用户的身份,创建一个服务来对已经存在的用户进行监视。同时让服务能够访问浏览器的cookie,这样当用户重新登录时,只要会话有效就无需再次进行身份验证。

angular.module('myApp.services', [])
      .factory('Auth', function($cookieStore,ACCESS_LEVELS) {
            var _user = $cookieStore.get('user');
            var setUser = function(user) {
                if (!user.role || user.role < 0) {
                    user.role = ACCESS_LEVELS.pub;
                }
                _user = user;
                $cookieStore.put('user', _user);
            };
            return {
                isAuthorized: function(lvl) {
                    return _user.role >= lvl;
                },
                setUser: setUser,
                isLoggedIn: function() {
                    return _user ? true : false;
                },
                getUser: function() {
                    return _user;
                },
                getId: function() {
                    return _user ? _user._id : null;
                },
                getToken: function() {
                    return _user ? _user.token : '';
                },
                logout: function() {
                    $cookieStore.remove('user');
                    _user = null; }
            }
        };
    });

 

当用户已经通过身份验证并登录后,可以在$routeChangeStart事件中对其有效性进行检查。

angular.module('myApp', [])
       .run(function($rootScope, $location, Auth) {
            // 给$routeChangeStart设置监听
            $rootScope.$on('$routeChangeStart', function(evt, next, curr) {
                if (!Auth.isAuthorized(next.$$route.access_level)) {
                    if (Auth.isLoggedIn()) {
                        // 用户登录了,但没有访问当前视图的权限
                        $location.path('/');
                    } else {
                        $location.path('/login');
                    }
                }
            });
        });

如果提供的令牌是合法的,且与一个合法用户是关联的状态,那服务器就会认为用户的身份是合法且安全的。

 

推荐阅读