Angular依赖注入是一种设计模式,它可以帮助我们更好地组织代码,并使我们的应用更加可测试。它的基本原理是,当一个对象需要另一个对象时,它将其注入到该对象中。这样,我们就不必在每个文件中都声明所需的对象,而是将它们注入到所需的位置。
Angular依赖注入使用了一种名为“依赖注入容器”的技术来处理这些依赖性。这个容器存储了所有已注册的服务(也就是要注入到应用中的对象)。当我们想要使用这些服务时,只需要在代码中声明即可。Angular会在运行时将正确的服务注入到应用中。
// 例如: class MyService { constructor(private http: HttpClient) {} getData() { return this.http.get('/data'); } }
本文主要为你介绍了 AngularJS XHR 和依赖注入,这里整理了详细资料和示例代码
在硬编码的数据集中有三款手机的数据,建立一个应用程序足够了!让我们使用Angular内建的服务之一,$http从服务器上取得更大的数据集我们将使用Angular的依赖性注入(DI)来为PhoneListCtrl
控制器提供服务。
把工作空间重置到第五步
git checkout -f step-5
刷新你的浏览器或在线检查这一步:Step 5 Live Demo
下面列出了第四步和第五步之间的最重要的区别。你可以在GitHub里看到完整的差异。
在你的项目中,app/phones/phones.json
文件是一个数据集,包含了一个更大的手机列表,以JSON格式存储。
遵照以下文件示例:
[
{
"age": 13,
"id": "motorola-defy-with-motoblur",
"name": "Motorola DEFYu2122 with MOTOBLURu2122",
"snippet": "Are you ready for everything life throws your way?"
...
},
...
]
我们将在控制器中使用Angular的$http服务向你的Web服务器发出HTTP请求,取回app/phones/phones.json
文件中的数据。$http
是几个用Web应用中来处理常见的操作的内建Angular服务之一。Angular在你需要的地方为你注入了这些服务。
Angular的DI子系统负责管理这些服务。依赖性注入有用助于你的web应用既结构完好(例如,分离表现层、数据和控制三者)以及松弛的耦合(不能由组件自身解决的组件之间的依赖性问题,由DI子系统解决)。
app/js/controllers.js:
var phonecatApp = angular.module("phonecatApp", []);
phonecatApp.controller("PhoneListCtrl", function ($scope, $http) {
$http.get("phones/phones.json").success(function(data) {
$scope.phones = data;
});
$scope.orderProp = "age";
});
$http
向你的Web服务器发出一个HTTP GET请求,要求phones/phones.json
(该url相对于我们的index.html
文件)。服务器在json文件中提供该数据,以响应该请求。(响应可能是由后端服务器动态生成的。但是在浏览器和我们的应用看来,它们没什么不同。为了简单起见,我们在本教程中使用了一个json文件。)
该$http
服务返回了一个promise对象?,带有success
方法。我们调用这个方法以处理异步响应,并假定该作用域的手机数据由该控制器控制,作为一个模块,称为phones
。注意Angular侦测了该json响应,并为我们解析了它。
要想在Angular中使用一个服务,你只要声明你所需要的依赖性的名字,作为控制器的构造函数的参数,如下所示:
phonecatApp.controller("PhoneListCtrl", function ($scope, $http) {...}
在构造控制器时,Angular的依赖性注入器会把这些服务注入到你的控制器中。这些依赖性控制器还负责创建该服务可能需要的任何传递依赖性(一个服务通常会依赖于其它服务)。
注意,参数的名称非常重要,因为注入器会用这些名称去查阅依赖性。
$
前缀名称约定你可以创建你自己的服务,而且实际上我们将在第十一步 AngularJS REST和自定义服务做这个。作为一个命名约定,Angular的内建服务,作用域方法以及一些别的Angular API在命名前面使用一个$
前缀。
Angular提供的服务的命名空间有$
前缀。要想避免冲突,最好避免把你的服务和模块命名成带有$
前缀。
如果你检查一个作用域,你可能还会注意到一些属性以$$
开头。这些属性被视为是私有属性,不能访问或者修改。
因为Angular从参数的名称调用控制器的依赖性到控制器构造器的函数,如果你打算为PhoneListCtrl
控制器缩小JavaScript代码,所有的函数参数都会被压缩,而且依赖性注入器将不能正确的识别服务。
我们可以克服这个问题,通过用依赖性的名称注释这个函数,作为字符串提供,它不会被压缩。提供这种注入注释有两种方法:
在控制器函数中创建一个$inject
属性,它可携带一个字符串数组。在数组中的每个字符串都是要注入到对应的参数上的服务的名称。我们可以在自己的示例中这样写:
function PhoneListCtrl($scope, $http) {...}
PhoneListCtrl.$inject = ["$scope", "$http"];
phonecatApp.controller("PhoneListCtrl", PhoneListCtrl);
在那里使用一个内联注释,并非是只提供这个函数,你还提供了一个数组。这个数组包含了一系列服务名称,后跟着函数本身。
function PhoneListCtrl($scope, $http) {...}
phonecatApp.controller("PhoneListCtrl", ["$scope", "$http", PhoneListCtrl]);
两种方法都能与Angular注入的任何函数完美协作,因此要选用哪种方法完全取决于你的项目的编程风格。
如果使用第二种方法,在注册控制器时,通常以匿名函数的形式提供内联的构造器函数。
phonecatApp.controller("PhoneListCtrl", ["$scope", "$http", function($scope, $http) {...}]);
从此刻开始,我们将在本教程中使用内联方法。考虑到这一点,让我们把注释加到PhoneListCtrl
上:
app/js/controllers.js:
var phonecatApp = angular.module("phonecatApp", []);
phonecatApp.controller("PhoneListCtrl", ["$scope", "$http",
function ($scope, $http) {
$http.get("phones/phones.json").success(function(data) {
$scope.phones = data;
});
$scope.orderProp = "age";
}]);
test/unit/controllersSpec.js
:
因为我们开始使用依赖性注入,而且我们的控制器包含了依赖性,在我们的测试中构造控制器就变得有点复杂了。我们可以使用new
操作符,并提供带有某种假的$http
实现的构造器。然而,Angular提供了一个模拟$http
服务,我们可以用在单元测试中。我们通过调用一个称为$httpBackend
服务上的方法,为服务器请求配置了“假的”响应。
describe("PhoneCat controllers", function() {
describe("PhoneListCtrl", function(){
var scope, ctrl, $httpBackend;
// 在每次测试之前载入我们的应用模块定义
beforeEach(module("phonecatApp"));
// 注入器会忽略前面和后面的下划线(例如_$httpBackend_)。
// 这允许我们注入一个服务,然后把它附加到同名变量上,以避免名称冲突
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET("phones/phones.json").
respond([{name: "Nexus S"}, {name: "Motorola DROID"}]);
scope = $rootScope.$new();
ctrl = $controller("PhoneListCtrl", {$scope: scope});
}));
注意:因为我们在测试环境中载入了Jasmine以及angular-mocks.js
,我们得到了两个辅助方法module和inject,用来访问和配置注入器。
我们在测试环境中创建控制器,如下所示:
inject
辅助方法,向Jasmine的beforeEach
函数注入$rootScope、$controller和$httpBackend服务的实例,这些实例来自于一个注入器,在每一个测试内部都会被重新创建这个注入器。这保证了每次测试都从一个众所周知的起点开始,每次测试与其它测试相互独立。$rootScope.$new()
来为我们的控制器创建一个新的作用域。$controller
函数,以参数的形式传入PhoneListCtrl
控制器的名称和创建范围。因为我们的代码现在使用$http
服务以取回我们的控制器中的手机列表数据,在我们创建PhoneListCtrl
子作用域之前,我们需要告诉测试套件等待一个后面的请求,来自控制器。我们可以这样做:
请求把$httpBackend
服务注入到我们的beforeEach
函数中。这是一个在产品环境中的服务的模拟版本,可以响应各种XHR和JSONP请求。该服务的模拟版本允许你编写测试,不需要处理原生的API和与它相关的全局状态——本来这两者都会使测试变成一个噩梦。
$httpBackend.expectGET
方法规定$httpBackend
服务等待之后的HTTP请求,并告诉它如何响应它。注意,直到我们调用$httpBackend.flush
方法,才会返回响应。现在我们作了断言以核实在响应到达之前,作用域上不存在手机模块:
it("should create "phones" model with 2 phones fetched from xhr", function() {
expect(scope.phones).toBeUndefined();
$httpBackend.flush();
expect(scope.phones).toEqual([{name: "Nexus S"},
{name: "Motorola DROID"}]);
});
通过调用$httpBackend.flush()
,我们清空了浏览器中的请求队列。这导致$http
服务返回的promise对象由规范的应答来处理。可以在模拟$httpBackend文档中了解为什么必须“清空HTTP请求”的完整解释。
最后,我们核实已经正确设置了orderProp
的默认值。
it("should set the default value of orderProp model", function() {
expect(scope.orderProp).toBe("age");
});
现在在Karma标签卡中,你应该看到以下的输出:
Chrome 22.0: Executed 2 of 2 SUCCESS (0.028 secs / 0.007 secs)
index.html
的底部,添加一个<pre>{{phones | filter:query | orderBy:orderProp | json}}</pre>
绑定以查看以json格式显示的手机列表。PhoneListCtrl
控制器中,通过限制手机的数量为列表的前五个来预处理http响应。在$http
回调中使用以下的代码:$scope.phones = data.splice(0, 5);
现在你已经知道了使用Angular服务是多么容易(幸亏Angular的依赖性注入),前往第六步 模板连接和图像,在那里你将添加一些手机的缩略图以及一些链接。
Angular是当下非常流行的前端框架,受到了广大前端开发者的喜爱。下面,将为大家列出一些经典的Angular面试题以及答案,供大家参...
HTMLIndex 文件public/index.html文件是一个会被html-webpack-plugin处理的模板。在构建过程中,资源链接会被自动注入。另外,Vu...
核心概念系统里有两个主要的部分:@vue/cli:全局安装的,暴露vue create app命令;@vue/cli-service:局部安装,暴露vue-cli-se...
自定义指令简介除了默认设置的核心指令(v-model和v-show),Vue 也允许注册自定义指令。注意,在 Vue2.0 里面,代码复用的主要形式...
需要服务端渲染(SSR)吗?在开始服务端渲染前,我们先看看它能给我们带来什么,以及什么时候需要用它。SEO(搜索引擎优化)谷歌...
监听事件可以用v-on指令监听 DOM 事件来触发一些 JavaScript 代码。示例:div id="example-1"button v-on:click="counter += 1"...
稳定性: 2 - 不稳定流用于处理Node.js中的流数据的抽象接口,在Node里被不同的对象实现。例如,对HTTP服务器的请求是流,process...
稳定性: 2 - 不稳定Node.js域包含了能把不同的IO操作看成单独组的方法。如果任何一个注册到域的事件或者回调触发error事件,或者...