如果您想了解Angular2学习笔记之依赖注入的相关知识,那么本文是一篇不可错过的文章,我们将对angular依赖注入理解进行全面详尽的解释,并且为您提供关于30行代码让你理解angular依赖注入:
如果您想了解Angular2学习笔记之依赖注入的相关知识,那么本文是一篇不可错过的文章,我们将对angular依赖注入理解进行全面详尽的解释,并且为您提供关于30行代码让你理解angular依赖注入:angular 依赖注入原理、Angular 依赖注入学习笔记之工厂函数的用法、Angular 依赖注入的学习笔记、Angular.JS学习之依赖注入$injector详析的有价值的信息。
本文目录一览:- Angular2学习笔记之依赖注入(angular依赖注入理解)
- 30行代码让你理解angular依赖注入:angular 依赖注入原理
- Angular 依赖注入学习笔记之工厂函数的用法
- Angular 依赖注入的学习笔记
- Angular.JS学习之依赖注入$injector详析
Angular2学习笔记之依赖注入(angular依赖注入理解)
简介
依赖注入是重要的程序设计模式。 Angular 有自己的依赖注入框架,离开了它,几乎没法构建 Angular 应用。 它使用得非常广泛,以至于几乎每个人都会把它简称为 DI。
注入器与提供器
注入器
//注入器 Constructor(private productService: ProductService ){ }
说明:
一般是在组件或者是服务中写入端代码,如果一个组件或者服务中的 constructor 注入为空。 就代表着这个组件或者服务没有注入任何的服务。
提供器
//提供器写法一 Providers:[ProductService] //提供器写法二 Providers:[{provide: ProductService,userClass:ProductService}] //提供器写法三 Providers:[{ provide: ProductService,userClass:AnotherProductService }] //提供器写法四 Providers:[{ provide: ProductService,userFactory: () => { /*新建服务的方法*/ } }]
说明:
写法一:使用这种方法是默认的写法(也是最精简的写法),直接提供一个 ProuctService。 写法二:使用这种方法和默认的写法是一个意思。 provide: ProductService就是提供的是 ProductService服务。 userClass:ProductService 就是我们new 这个服务对象的时候,new的是 ProductService。 写法三:使用这种方式,就是提供器提供的是 ProductService,但是 new 服务对象的时候, new 的是 AnotherProductService。 写法四:使用工厂模式创建提供器
寻找依赖注入的逻辑
在代码中找注入的方法: 1.在具体的组件或者服务中的 constructor 方法中找注入的服务 2.根据 1 中注入的服务找 提供器 3.跟据2 中的提供器找到对应的注入服务类 例: 注入器为 :Constructor(private productService: ProductService ){ } 就去找对应的 providers(提供器) 如果提供器为:Providers:[ProductService] 那么注入的就是 ProductService 如果提供器为:Providers:[{ provide: ProductService,userClass:AnotherProductService }] 那么注入的就是 AnotherProductService
依赖注入的层级结构
一方面,NgModule 中的提供商是被注册到根注入器。这意味着在 NgModule 中注册的提供商可以被整个应用访问。 另一方面,在应用组件中注册的提供商只在该组件及其子组件中可用。
关于 @Injectable()
@Injectable() 标识一个类可以被注入器实例化。 通常,在试图实例化没有被标识为@Injectable()的类时,注入器会报错。 官方建议: 建议:为每个服务类都添加 @INJECTABLE() 建议:为每个服务类都添加@Injectable(),包括那些没有依赖严格来说并不需要它的。因为: 面向未来: 没有必要记得在后来添加依赖的时候添加 @Injectable()。 一致性:所有的服务都遵循同样的规则,不需要考虑为什么某个地方少了一个。 "注意": 总是使用@Injectable()的形式,不能只用@Injectable。 如果忘了括号,应用就会神不知鬼不觉的报错!
最简单的例子
根模块中注入
目的:在新建的工程中将数据从Service中注入到component中,并且在界面上面展示出来 1.新建一个工程: ng new di 2.新建 product1 组件: ng g c product1 3.新建 product 服务(在shared 路径下面新建 product 服务): ng g service shared/product 或者 ng g s shared/product
修改代码 produc.service.ts:
/* 增加 class Product, 以及返回 Product对象供外部调用的方法 getProduct() getProduct方法需要返回一个 Product 。如果需要让外部访问到当前的 Service ,就需要加上一个注解 @Injectable() */ import { Injectable } from '@angular/core'; @Injectable() export class ProductService { constructor() { } getProduct(): Product { return new Product(1,"IPhone X","最牛逼的全面屏手机",8388); } } export class Product{ constructor( public id: number,public name: string,public desc: string,public price: number ){} }
修改 product1.component.ts
/* 在当前的 Product 类中增加 变量 product 以及 注入 ProductService,在初始化的钩子中 调用 ProductService 的 getProduct 方法,返回一个 Product */ import { Component,OnInit } from '@angular/core'; import {Product,ProductService} from "../shared/product.service"; @Component({ selector: 'app-product1',templateUrl: './product1.component.html',styleUrls: ['./product1.component.css'] }) export class Product1Component implements OnInit { product: Product; constructor(private productService: ProductService) { } ngOnInit() { this.product = this.productService.getProduct(); } }
修改 product1.component.html
<!-- 修改界面,用于界面展示 --> <div> <div>商品编码:{{product.id}}</div> <div>商品名称:{{product.name}}</div> <div>商品描述:{{product.desc}}</div> <div>商品价格:{{product.price}}</div> </div>
修改 app.conponent.html
<!-- 将 product.html 加入到 当前界面 --> <h1> 依赖注入的例子 </h1> <div> <app-product1></app-product1> </div>
修改 app.modules.ts
/*添加 Product.service.ts 到 providers 中,在这个地方注入是叫做 “从根组件中注入”,然后所有的都可以访问到。*/ import { browserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { HttpModule } from '@angular/http'; import { AppComponent } from './app.component'; import { Product1Component } from './product1/product1.component'; import {ProductService} from "app/shared/product.service"; @NgModule({ declarations: [ AppComponent,Product1Component ],imports: [ browserModule,FormsModule,HttpModule ],providers: [ProductService],bootstrap: [AppComponent] }) export class AppModule { }
图示:
30行代码让你理解angular依赖注入:angular 依赖注入原理
http://www.cnblogs.com/etoah/p/5460441.html
依赖注入(Dependency Injection,简称DI)是像C#,java等典型的面向对象语言框架设计原则控制反转的一种典型的一种实现方式,angular把它引入到js中,介绍angular依赖注入的使用方式的文章很多,
angular官方的文档,也有很详细的说明。但介绍原理的较少,angular代码结构较复杂,文章实现了一简化版本的DI,核心代码只有30行左右,相看实现效果(可能需FQ)或查看源码
这篇文章用尽量简单的方式说一说 angular依赖注入的实现。
简化的实现原理
要实现注入,基本有三步:
- 得到模块的依赖项
- 查找依赖项所对应的对象
- 执行时注入
1. 得到模块的依赖项
javascript 实现DI的核心api是Function.prototype.toString
,对一个函数执行toString,它会返回函数的源码字符串,这样我们就可以通过正则匹配的方式拿到这个函数的参数列表:
function extractArgs(fn) { //angular 这里还加了注释、箭头函数的处理 var args = fn.toString().match(/^[^\(]*\(\s*([^\)]*)\)/m); return args[1].split(','); }
2. 查找依赖项所对应的对象
java与.net通过反射来获取依赖对象,js是动态语言,直接一个object[name]
就可以直接拿到对象。所以只要用一个对象保存对象或函数列表就可以了
createInjector(cache) { this.cache = cache; } angular.module = function () { modules = {}; injector = new createInjector(modules); return { injector: injector, factory: function (name, fn) { modules[name.trim()] = this.injector.invoke(fn); return this; } } };
3. 执行时注入
最后通过 fn.apply方法把执行上下文,和依赖列表传入函数并执行:
createInjector.prototype = { invoke: function (fn, self) { argsstring = extractArgs(fn); args = []; argsstring.forEach(function (val) { args.push(this.cache[val.trim()]); }, this); return fn.apply(self, args); } };
简化的全部代码和执行效果见(可能需FQ):http://plnkr.co/edit/sJiIbzEXiqLLoQPeXBnR?p=preview
或查看源码
这里是简化的版本,实际angular的实现考虑了很多问题,如模块管理,延迟执行等
angular 的实现
为了简单,我们也按这三步来介绍angular DI
- 得到模块的依赖项
- 查找依赖项所对应的对象
- 执行时注入
注:以下代码行数有就可能变
1. 得到模块的依赖项
https://github.com/angular/angular.js/blob/master/src%2Fauto%2Finjector.js#L81
var ARROW_ARG = /^([^\(]+?)=>/; var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m; var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; extractArgs(fn) { var fnText = fn.toString().replace(STRIP_COMMENTS, ''), args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS); return args; }
2. 查找依赖项所对应的对象
https://github.com/angular/angular.js/blob/master/src%2Fauto%2Finjector.js#L807
getService(serviceName, caller) { if (cache.hasOwnProperty(serviceName)) { if (cache[serviceName] === INSTANTIATING) { throw $injectorminerr('cdep',21)">'Circular dependency found: {0}', serviceName + ' <- ' + path.join(' <- ')); } return cache[serviceName]; } else { try { path.unshift(serviceName); cache[serviceName] = INSTANTIATING; return cache[serviceName] = factory(serviceName, caller); } catch (err) { if (cache[serviceName] === INSTANTIATING) { delete cache[serviceName]; } throw err; } finally { path.shift(); } } }
3. 执行时注入
https://github.com/angular/angular.js/blob/master/src%2Fauto%2Finjector.js#L831
得到参数:
injectionArgs(fn, locals, serviceName) { var args = [], $inject = createInjector.$$annotate(fn, strictDi, serviceName); for (var i = 0, length = $inject.length; i < length; i++) { var key = $inject[i]; if (typeof key !== 'string') { 'itkn', 'Incorrect injection token! Expected service name as string,got {0}', key); } args.push(locals && locals.hasOwnProperty(key) ? locals[key] : getService(key, serviceName)); } return args; }
调用
https://github.com/angular/angular.js/blob/master/src%2Fauto%2Finjector.js#L861
invoke(fn, self, serviceName) { typeof locals === 'string') { serviceName = locals; locals = null; } var args = injectionArgs(fn, locals, serviceName); if (isArray(fn)) { fn = fn[fn.length - 1]; } if (!isClass(fn)) { // http://jsperf.com/angularjs-invoke-apply-vs-switch // #5388; } else { args.unshift(null); new (Function.prototype.bind.apply(fn, args))(); } }
angular模块管理,深坑
angular在每次应用启动时,初始化一个Injector实例:
https://github.com/angular/angular.js/blob/master/src/Angular.js#L1685
var injector = createInjector(modules, config.strictDi);
由此代码可以看出对每一个Angular应用来说,无论是哪个模块,所有的"provider"都是存在相同的providerCache或cache中
所以会导致一个被誉为angular模块管理的坑王的问题:
module 并没有什么命名空间的作用,当依赖名相同的时候,后面引用的会覆盖前面引用的模块。
具体的示例可以查看:
http://plnkr.co/edit/TZ7hpMwuxk0surlcWDvU?p=preview
注:angular di用本文的调用方式压缩代码会出问题:可以用g-annotate转为安全的调用方式。
到此angular di的实现原理已完成简单的介绍,angular用了项目中几乎不会用到的api:Function.prototype.toString 实现依赖注入,思路比较简单,但实际框架中考虑的问题较多,更加详细的实现可以直接看angular的源码。
以后会逐步介绍angular其它原理。
转载时请注明源出处:http://www.cnblogs.com/etoah/p/5460441.html
Angular 依赖注入学习笔记之工厂函数的用法
网址:https://angular.institute/di
We can transfer any data through our apps, transform it and replace it.
我们能传递任何数据到我们的应用里,改变其形态,并且替换。
Another case: document and fetch can work in your browser correctly. But one day you need to build your app in SSR or precompile with nodejs. Global entities might be missing or be different in that environment.
document 和 fetch 能在浏览器环境下运行。但是如果在 SSR 下 build 应用,或者用 nodejs precompile,那么这些对象在新的环境下不再可用。
Dependency Injection mechanism manages dependencies in your application. For you as an Angular developer, it is a straightforward instrument. There are two operations: you can provide something into your DI tree or take something from it.
依赖注入机制管理应用的依赖。对于 Angular 开发者来说,有两种操作:
- 提供数据到依赖注入树中
- 从依赖注入树中获取数据
The magic is the order in which you provide and take your dependencies.
Angular creates a class instance when you ask for this the first time.
当我们试图在 DI 树里第一次获取实例时,Angular 负责实例化。
Providing value is normally used with InjectionToken. This object is like a key for DI mechanism.
我们也可以用依赖注入提供常量,通常借助 InjectionToken. 这个令牌类似依赖注入机制中的 key.
You say "I want to get this data with this key" and ask DI in a component "Do you have something for this key?"
我们使用 InjectionToken 作为 key,询问 Angular 依赖注入机制,“你维护的资源里,有针对这个 key 的值吗?”
看个具体的例子。
export const API_URL = new InjectionToken<string>(''The URL of API'');
在 api-url.token.ts 里,我们从 @angular/core 里导入了标准的 InjectionToken 构造器,其类型为 string,描述信息为:The URL of API.
在 app.module.ts 里,导入这个 API_URL token,然后在 module 的 NgModule 注解里,使用 useValue 提供 token key 代表的具体值:
如何使用这个 token 呢?参考下图代码:
export class AppComponent {
constructor(@Inject(API_URL) readonly apiUrl: string) {
/**
* Here we asked for something with a key API_URL.
* There is our string in DI and we get it
*/
console.log(apiUrl);
}
}
语义就是,在 app Component 里,使用 @Inject 注解,向 DI 树里查询,是否存在 key 为 API_URL 的注入值。
- We can replace token value at any level of DI tree without any changes in a component - 我们可以在 DI 树上的任意层次结构里,替换 token 的值,而不需要修改 Component
- We can mock a token value providing suitable data in tests - 在测试代码里,我们可以 mock 一个 token 值
- The component class is fully isolated and can be used without any context
Providing a factory
这是 Angular 依赖注入的高级用法之一。
You can provide some value combining and transforming data from other tokens.
我们可以在 factory 代码里编写一些业务逻辑,执行一些数据结构变换的操作。
看个例子:
定义一个函数:
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { FormsModule } from "@angular/forms";
import { AppComponent } from "./app.component";
import { PRESSED_KEY } from "./tokens/pressed-key";
import { Observable, fromEvent } from "rxjs";
import { map } from "rxjs/operators";
import { DOCUMENT } from "@angular/common";
/**
* It is a token value factory.
*
* The factory is called when app.component.ts asks for
* the token PRESSED_KEY the first time
*/
function pressedKeyFactory(documentRef: Document): Observable<string> {
return fromEvent(documentRef.body, "keydown").pipe(
map((event: KeyboardEvent) => event.key)
);
}
构造一个 Observable 对象,当键盘被按下时,从 KeyboardEvent 里解析出具体哪一个键被按下,然后将 key 值通过 Observable 对象返回。
这个函数被当成一个工厂函数,本身接收类型为 Document 的参数,这个参数就是其依赖。
我们通过下列的语法,将名为 PRESSED_KEY 的令牌,和该工厂函数绑定在一起。
@NgModule({
imports: [BrowserModule, FormsModule],
declarations: [AppComponent],
bootstrap: [AppComponent],
providers: [
{
provide: PRESSED_KEY,
/**
* Here you can provide any token and its value will be injected
* as a factory function argument
*/
deps: [DOCUMENT],
useFactory: pressedKeyFactory
}
]
})
该 token 的消费方式:
更多Jerry的原创文章,尽在:"汪子熙":
Angular 依赖注入的学习笔记
Angular官方文档
Specifying a provider token
If you specify the service class as the provider token, the default behavior is for the injector to instantiate that class with new.
In the following example, the Logger class provides a Logger instance.
providers: [Logger]
NgModule的providers区域里,指定provider token. 如果将一个服务类直接指定成provider token,默认的行为就是,injector直接用new来初始化那个类。
上面的例子,会触发Logger的构造函数。等价于:
[{ provide: Logger, useClass: Logger }]
这个完整的声明在Angular官方文档里称为expanded provider configuration, 是一个包含两个属性的对象。
- The provide property holds the token that serves as the key for both locating a dependency value and configuring the injector.
provide属性包含一个token,用于定位一个dependency value和配置injector.
- The second property is a provider definition object, which tells the injector how to create the dependency value. The provider-definition key can be useClass, as in the example. It can also be useExisting, useValue, or useFactory. Each of these keys provides a different type of dependency, as discussed below.
第二个属性是一个provider定义对象,告诉injector如何创建dependency value. 这个定义可以是useClass,useExisting,useValue,或者useFactory.
一些例子:
[{ provide: Logger, useClass: BetterLogger }]
当使用Logger token时,injector返回BetterLogger的实例。
Aliasing class providers
To alias a class provider, specify the alias and the class provider in the providers array with the useExisting property.
In the following example, the injector injects the singleton instance of NewLogger when the component asks for either the new or the old logger. In this way, OldLogger is an alias for NewLogger.
下列的配置,会导致Component需要使用OldLogger时,会返回NewLogger的singleton实例。当然,如果请求NewLogger,亦会返回NewLogger实例。因此这种情形下,OldLogger是NewLogger语义层面的alias.
[ NewLogger,
// Alias OldLogger w/ reference to NewLogger
{ provide: OldLogger, useExisting: NewLogger}]
如果使用如下命令行创建 service:
ng generate service User
自动生成的代码:
import { Injectable } from ''@angular/core'';
@Injectable({
providedIn: ''root'',
})
export class UserService {
}
现在,你就可以在应用中到处注入 UserService 了。
该服务本身是 CLI 创建的一个类,并且加上了 @Injectable() 装饰器。默认情况下,该装饰器是用 providedIn 属性进行配置的,它会为该服务创建一个提供者。在这个例子中,providedIn: ‘root’ 指定 Angular 应该在根注入器中提供该服务。
当你把服务提供者添加到应用的根注入器中时,它就在整个应用程序中可用了。 另外,这些服务提供者也同样对整个应用中的类是可用的 —— 只要它们有供查找用的服务令牌。
你应该始终在根注入器中提供这些服务 —— 除非你希望该服务只有在消费方要导入特定的 @NgModule 时才生效。
也可以规定某个服务只有在特定的 @NgModule 中提供。比如,如果你希望只有当消费方导入了你创建的 UserModule 时才让 UserService 在应用中生效,那就可以指定该服务要在该模块中提供:
import { Injectable } from ''@angular/core'';
import { UserModule } from ''./user.module'';
@Injectable({
providedIn: UserModule,
})
export class UserService {
}
上面的例子展示的就是在模块中提供服务的首选方式。之所以推荐该方式,是因为当没有人注入它时,该服务就可以被摇树优化掉。如果没办法指定哪个模块该提供这个服务,你也可以在那个模块中为该服务声明一个提供者。
服务是一个广义的概念,它包括应用所需的任何值、函数或特性。狭义的服务是一个明确定义了用途的类。它应该做一些具体的事,并做好。
Angular 把组件和服务区分开,以提高模块性和复用性。 通过把组件中和视图有关的功能与其它类型的处理分离开,你可以让组件类更加精简、高效。
理想情况下,组件的工作只管用户体验,而不用顾及其它。 它应该提供用于数据绑定的属性和方法,以便作为视图(由模板渲染)和应用逻辑(通常包含一些模型的概念)的中介者。
组件应该把诸如从服务器获取数据、验证用户输入或直接往控制台中写日志等工作委托给各种服务。通过把各种处理任务定义到可注入的服务类中,你可以让它被任何组件使用。 通过在不同的环境中注入同一种服务的不同提供者,你还可以让你的应用更具适应性。
当 Angular 创建组件类的新实例时,它会通过查看该组件类的构造函数,来决定该组件依赖哪些服务或其它依赖项。 比如 HeroListComponent 的构造函数中需要 HeroService:
constructor(private service: HeroService) { }
当 Angular 发现某个组件依赖某个服务时,它会首先检查是否该注入器中已经有了那个服务的任何现有实例。如果所请求的服务尚不存在,注入器就会使用以前注册的服务提供者来制作一个,并把它加入注入器中,然后把该服务返回给 Angular。
是否创建新的依赖服务实例,取决于 provider 的具体类型。
当所有请求的服务已解析并返回时,Angular 可以用这些服务实例为参数,调用该组件的构造函数。
HeroService 的注入过程如下所示:从应用的 Injector 里获取 HeroService 实例。
提供服务
对于要用到的任何服务,你必须至少注册一个提供者。服务可以在自己的元数据中把自己注册为提供者,这样可以让自己随处可用。或者,你也可以为特定的模块或组件注册提供者。要注册提供者,就要在服务的 @Injectable() 装饰器中提供它的元数据,或者在 @NgModule() 或 @Component() 的元数据中。
默认情况下,Angular CLI 的 ng generate service 命令会在 @Injectable() 装饰器中提供元数据来把它注册到根注入器中。本教程就用这种方法注册了 HeroService 的提供者:
@Injectable({
providedIn: ''root'',
})
什么是 Angular 依赖注入的 tree-shaking
When you provide the service at the root level, Angular creates a single, shared instance of HeroService and injects it into any class that asks for it. Registering the provider in the @Injectable() metadata also allows Angular to optimize an app by removing the service from the compiled app if it isn’t used, a process known as tree-shaking.
当你使用特定的 NgModule 注册提供者时,该服务的同一个实例将会对该 NgModule 中的所有组件
可用。要想在这一层注册,请用 @NgModule() 装饰器中的 providers 属性:
@NgModule({
providers: [
BackendService,
Logger
],
...
})
当你在组件级注册提供者时,你会为该组件的每一个新实例提供该服务的一个新实例。 要在组件级注册,就要在 @Component() 元数据的 providers 属性中注册服务提供者。
@Component({
selector: ''app-hero-list'',
templateUrl: ''./hero-list.component.html'',
providers: [ HeroService ]
})
本文同步分享在 博客“汪子熙”(CSDN)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
Angular.JS学习之依赖注入$injector详析
前言
在依赖注入(IoC)之前,我们在程序中需要创建一个对象很简单也很直接,就是在代码中new Object即可,有我们自己负责创建、维护、修改和删除,也就是说,我们控制了对象的整个生命周期,直到对象没有被引用,被回收。诚然,当创建或者维护的对象数量较少时,这种做法无可厚非,但是当一个大项目中需要创建大数量级的对象时,仅仅依靠程序员来进行维护所有对象,这是难以做到的,特别是如果想在程序的整个生命周期内复用一些对象,我们需要自己写一个缓存模块对所有对象进行缓存,这加大了复杂度。当然,IoC的好处并不仅限于此,它也降低了对依赖的耦合度,不必在代码中进行引用或者传参即可操作依赖。
在js中,我们可以这样引入依赖
1、使用全局变量引用
2、在需要的地方通过函数参数传递
使用全局变量的坏处自不必说,污染了全局的名字空间,而通过函参传递引用,也可以通过两种方法实现:
1、闭包传递
2、后台解析出依赖对象,并通过Function.prototype.call
进行传参
而在AngularJS中,依赖注入是通过后者实现的,接下来的几节将会介绍IoC模块的具体实现。
获取依赖
function anonFn(fn) {
// For anonymous functions,showing at the very least the function signature can help in
// debugging.
var fnText = fn.toString().replace(STRIP_COMMENTS,''),args = fnText.match(FN_ARGS);
if (args) {
return 'function(' + (args[1] || '').replace(/[\s\r\n]+/,' ') + ')';
}
return 'fn';
}
function annotate(fn,strictDi,name) {
var $inject,fnText,argDecl,last;
if (typeof fn === 'function') {
if (!($inject = fn.$inject)) {
$inject = [];
if (fn.length) {
if (strictDi) {
if (!isstring(name) || !name) {
name = fn.name || anonFn(fn);
}
throw $injectorminerr('strictdi','{0} is not using explicit annotation and cannot be invoked in strict mode',name);
}
fnText = fn.toString().replace(STRIP_COMMENTS,'');
argDecl = fnText.match(FN_ARGS);
forEach(argDecl[1].split(FN_ARG_SPLIT),function(arg) {
arg.replace(FN_ARG,function(all,underscore,name) {
$inject.push(name);
});
});
}
fn.$inject = $inject;
}
} else if (isArray(fn)) {
last = fn.length - 1;
assertArgFn(fn[last],'fn');
$inject = fn.slice(0,last);
} else {
assertArgFn(fn,'fn',true);
}
return $inject;
}
annotate
函数通过对入参进行针对性分析,若传递的是一个函数,则依赖模块作为入参传递,此时可通过序列化函数进行正则匹配,获取依赖模块的名称并存入$inject
数组中返回,另外,通过函数入参传递依赖的方式在严格模式下执行会抛出异常;第二种依赖传递则是通过数组的方式,数组的最后一个元素是需要使用依赖的函数。annotate
函数最终返回解析的依赖名称。
注入器的创建
AngularJS的API也提供了injector部分,通过injector部分,通过injector可以使用get
,has
,instantiate
,invoke
以及上节提到的annotate
等方法,通过源码可以更清晰的理解。
function invoke(fn,self,locals,serviceName) {
if (typeof locals === 'string') {
serviceName = locals;
locals = null;
}
var args = [],// 解析并获取注入服务列表
$inject = annotate(fn,serviceName),length,i,key;
for (i = 0,length = $inject.length; i < length; i++) {
key = $inject[i];
if (typeof key !== 'string') {
throw $injectorminerr('itkn','Incorrect injection token! Expected service name as string,got {0}',key);
}
// 注入的服务作为参数传入
args.push(
locals && locals.hasOwnProperty(key)
? locals[key]
: getService(key,serviceName)
);
}
if (isArray(fn)) {
fn = fn[length];
}
// http://jsperf.com/angularjs-invoke-apply-vs-switch
// #5388
return fn.apply(self,args);
}
function instantiate(Type,serviceName) {
// Check if Type is annotated and use just the given function at n-1 as parameter
// e.g. someModule.factory('greeter',['$window',function(renamed$window) {}]);
// Object creation: http://jsperf.com/create-constructor/2
var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype);
var returnedValue = invoke(Type,instance,serviceName);
return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
}
return {
invoke: invoke,instantiate: instantiate,get: getService,annotate: annotate,has: function(name) {
return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
}
};
}
createInternalInjector
方法创建$injector对象,传递的参数分别为缓存对象和工厂函数。在具体实现中,AngularJS创建了两个injector对象--providerInjector和instanceInjector(这两个对象的不同主要是createInternalInjector方法传递的缓存对象不同),而通过angular.injector()导出的就是instanceInjector。对于providerInjector,主要用来获取服务的提供者,即serviceProvider。而对于instanceInjector而言,主要用于执行从providerInjector获取的provider对象的$get
方法,生产服务对象(依赖),并将服务对象传递给相应的函数,完成IoC。
首先从get方法说起,get方法主要获取指定名称的服务,通过angular的injector方法获取的是instanceInjector,而当缓存中没有该服务对象(依赖)时,我们需要执行factory(serviceName,caller)
方法,我们看看对于的factory函数:
红色部分即为factory函数,它显示通过providerInjector获取相应服务的提供者serviceProvider,然后调用instanceInjector的invoke方法在serviceProvider上下文执行serviceProvider的$get方法,返回服务对象并保存在缓存中。这样,便完成了服务对象(依赖)的获取和缓存。
invoke方法也很简单,它的入参分别问fn
,self
,locals
,serviceName
,即要执行的函数,函数执行的上下文,提供的options选项和服务名。首先获取函数的所有依赖名,通过annotate方法完成之后,如果options中提供了对于名称的依赖,则使用,否则通过get方法获取依赖,最后传入函数,并将函数的执行结果返回。invoke返回的结果往往是一个服务对象。
instantiate方法主要根据提供的构造函数创建一个示例,用作依赖或提供服务。值得一提的是并没有通过new关键字创建对象,而是通过ECMA5提供的Object.create
来继承函数的原型对象实现,非常巧妙。
has方法则是相继判断serviceProvider和service是否存在于缓存中。
至此,$injector对象创建完毕。
注册服务(依赖)
服务不可能凭空而来,我们需要自己实现或者外部引入服务或依赖。所以,注册服务的模块也是值得深究的。AngularJS提供了多种注册服务的API,但是我们着重关注的是provider方法,其他factory,service方法都是基于此进行构建的。
这些方法(provider,factory等)绑定在providerCache.provide
对象上,而我们通过angular.module(′app′,[]).provider(...)
方式调用的provider函数,会在module加载期间将调用(该调用抽象成一个数组,即[provider,method,arguments])绑定在内部的invokeQueue数组中,最终在providerCache.provide
对象上,而我们通过angular.module(′app′,arguments])绑定在内部的invokeQueue数组中,最终在
providerCache.provide
对象上调用provider方法,其他的controller,directive等方法类似,不过是绑定在providerCache.controllerProvider,providerCache.controllerProvider,providerCache.compileProvider
对象上。
rush:js;">
function provider(name,provider_) {
assertNotHasOwnProperty(name,'service');
if (isFunction(provider_) || isArray(provider_)) {
provider_ = providerInjector.instantiate(provider_);
}
if (!provider_.$get) {
throw $injectorminerr('pget',"Provider '{0}' must define $get factory method.",name);
}
return providerCache[name + providerSuffix] = provider_;
}
function enforceReturnValue(name,factory) {
return function enforcedReturnValue() {
var result = instanceInjector.invoke(factory,this);
if (isUndefined(result)) {
throw $injectorminerr('undef',"Provider '{0}' must return a value from $get factory method.",name);
}
return result;
};
}
function factory(name,factoryFn,enforce) {
return provider(name,{
$get: enforce !== false ? enforceReturnValue(name,factoryFn) : factoryFn
});
}
function service(name,constructor) {
return factory(name,['$injector',function($injector) {
return $injector.instantiate(constructor);
}]);
}
function value(name,val) { return factory(name,valueFn(val),false); }
function constant(name,value) {
assertNotHasOwnProperty(name,'constant');
providerCache[name] = value;
instanceCache[name] = value;
}
// 在服务实例化之后进行调用(拦截),拦截函数中注入实例化的服务,可以修改,扩展替换服务。
function decorator(serviceName,decorFn) {
var origProvider = providerInjector.get(serviceName + providerSuffix),orig$get = origProvider.$get;
origProvider.$get = function() {
var origInstance = instanceInjector.invoke(orig$get,origProvider);
return instanceInjector.invoke(decorFn,null,{$delegate: origInstance});
};
}
provider方法需要两个参数,一个是服务名(依赖名),另外是工厂方法或者是一个包含依赖和工厂方法的数组。首先通过providerInjector创建工厂方法的一个实例,并添加到providerCache中,返回。
factory方法只是将第二个参数封装成了一个包含$get方法的对象,即serviceProvider,缓存。并不复杂。
而service方法则嵌套注入了$injector
服务,即instanceInjector,它会创建构造函数的实例,作为服务对象。
value方法仅仅封装了一个provider,其$get
方法返回value值。
constant方法则将value的值分别存入providerCache和instanceCache中,并不需要invoke获取其value值。
而比较特殊且扩展性较高的decorator方法,是在serviceProvider的get方法后面添加一个拦截函数,并通过传递依赖get方法后面添加一个拦截函数,并通过传递依赖delegate来获取原先invoke $get
方法返回的服务对象。我们可以通过decorator来对服务进行扩展,删除等操作。
流程
最后,在基本的实现已经完成的基础上,我们走一遍具体的注入流程,更易于我们的深入理解。
rush:js;">
angular.module("app",[])
.provider("locationService",function(){
...
})
.controller("WeatherController",function($scope,locationService,$location){
locationService.getWeather()
.then(function(data){
$scope.weather = data;
},function(e){
console.log("error message: "+ e.message)
});
})
我们不关心具体的代码实现,仅仅使用上述代码作为演示。
首先确定AngularJS上下文的范围,并且获取依赖模块(在此处为空);
继续注册服务(依赖),将serviceProvider缓存至providerCache中;
声明控制器;
在此获取injector示例,通过执行invoke函数,获取[“injector示例,通过执行invoke函数,获取[“scope”,”locationService”,”location”]依赖列表,通过location”]依赖列表,通过injector的get方法获取相应的依赖对象。对于scope和scope和location服务而言,在AngularJS初始化时已经注入到Angular中,因此可以获取相应的provider对象,执行相关的方法返回scope和scope和location对象,而locationService则在provider中进行了声明,因此获取到locationServiceProvider对象,通过调用instanceInjector.invoke(locationServiceProvider.$get,locationServiceProvider,“locationService”)
返回locationService对象。
最后将所有的依赖组装成数组[scope,scope,location]作为参数传递给匿名函数执行。
总结
至此,依赖注入完成。大家对依赖注入$injector有没有进一步的了解呢?以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。
关于Angular2学习笔记之依赖注入和angular依赖注入理解的问题就给大家分享到这里,感谢你花时间阅读本站内容,更多关于30行代码让你理解angular依赖注入:angular 依赖注入原理、Angular 依赖注入学习笔记之工厂函数的用法、Angular 依赖注入的学习笔记、Angular.JS学习之依赖注入$injector详析等相关知识的信息别忘了在本站进行查找喔。
本文标签: