依赖注入(DI) 是 Angular 2 的核心,在深入了解DI的工作原理之前,我们必须先搞清楚 Provider 的概念。

在 Angular 2 中我们使用 Provider 来描述与 Token 关联的依赖对象的创建方式。Angular 2 中依赖对象的创建方式有四种,它们分别是:

useClass

useValue

useExisting

useFactory

useClass

@Injectable()exportclassApiService{constructor(publichttp:Http,publicloadingCtrl:LoadingController){}...}@NgModule({...providers:[//可使用简洁的语法,即直接使用ApiService{provide:ApiService,useClass:ApiService}]})exportclassCoreModule{}

useValue

{provide:'API_URL',useValue:'http://my.api.com/v1'}

useExisting

{provide:'ApiServiceAlias',useExisting:ApiService}

useFactory

exportfunctionconfigFactory(config:AppConfig){return()=>config.load();}@NgModule({...providers:[{provide:APP_INITIALIZER,useFactory:configFactory,deps:[AppConfig],multi:true}]})exportclassCoreModule{}

使用 Provider 的正确姿势

1.创建 Token

Token 的作用是用来标识依赖对象,Token值可能是 Type、InjectionToken、OpaqueToken 类的实例或字符串。通常不推荐使用字符串,因为如果使用字符串存在命名冲突的可能性比较高。在 Angular 4.x 以前的版本我们一般使用 OpaqueToken 来创建 Token,而在 Angular 4.x 以上的版本版本,推荐使用 InjectionToken 来创建 Token 。详细的内容可以参考, 如何解决 Angular 2 中 Provider 命名冲突。

2.根据实际需求选择依赖对象的创建方式,如 useClass 、useValue、useExisting、useFactory

3.在 NgModule 或 Component 中注册 providers

4.使用构造注入的方式,注入与 Token 关联的依赖对象

/***封装Http服务,如在每个Http的请求头中添加token,类似于Ng1.x中的拦截器*/@Injectable()exportclassApiService{constructor(//注入Angular2中的Http服务,与Ng1.x的区别://在Ng1.x中调用Http服务后,返回Promise对象//在Ng2.x中调用Http服务后,返回Observable对象publichttp:Http){}...}/***AppModule*/@NgModule({//可使用简洁的语法,即直接使用ApiService...providers:[{provide:ApiService,useClass:ApiService}]})exportclassAppModule{}/***系统首页*/@Component({selector:'page-home',templateUrl:'home.html'})exportclassHomePage{constructor(//使用构造注入的方式,注入ApiService的实例对象publicapiService:ApiService){}ngOnInit():void{//获取首页相关的数据this.apiService.get(HOME_URL).map(res=>res.json())//返回的res对象是Response类型的实例.subscribe(result=>{...})}}

我有话说

1.当DI解析 Providers 时,都会对提供的每个 provider 进行规范化处理,即转换成标准的形式。

function_normalizeProviders(providers:Provider[],res:Provider[]):Provider[]{providers.forEach(b=>{if(binstanceofType){//支持简洁的语法,转换为标准格式res.push({provide:b,useClass:b});}elseif(b&&typeofb=='object'&&(basany).provide!==undefined){res.push(basNormalizedProvider);}elseif(binstanceofArray){_normalizeProviders(b,res);//如果是数组,进行递归处理}else{throwinvalidProviderError(b);}});returnres;}

2.创建 Token 时为了避免命名冲突,尽量避免使用字符串作为Token。

3.若要创建模块内通用的依赖对象,需要在 NgModule 中注册相关的 provider,若在每个组件中,都有唯一的依赖对象,就需要在 Component 中注册相关的 provider。

4.multi providers 的具体作用,具体请参考 - Angular2 Multi Providers

5.Provider 是用来描述与 Token 关联的依赖对象的创建方式。当我们使用 Token 向 DI 系统获取与之相关连的依赖对象时,DI 会根据已设置的创建方式,自动的创建依赖对象并返回给使用者。

Provider接口

exportinterfaceClassProvider{//用于设置与依赖对象关联的Token值,Token值可能是Type、InjectionToken、OpaqueToken//的实例或字符串provide:any;useClass:Type<any>;//用于标识是否multipleproviders,若是multiple类型,则返回与Token关联的依赖对象列表multi?:boolean;}exportinterfaceValueProvider{provide:any;useValue:any;multi?:boolean;}exportinterfaceExistingProvider{provide:any;useExisting:any;multi?:boolean;}exportinterfaceFactoryProvider{provide:any;useFactory:Function;deps?:any[];//用于设置工厂函数的依赖对象multi?:boolean;}

总结

在文章的最后,想举一个现实生活中的例子,帮助初学者更好地理解 Angular 2 DI 和 Provider。

Provider 中的 token 可以理解为菜名,useClass、useValue可以理解为菜的烹饪方式,而依赖对象就是我们所点的菜,而 DI 系统就是我们的厨师了。如果没有厨师,我们就得关心煮这道菜需要哪些原材料,怎么煮菜,重要的是还得自己煮,可想而知多麻烦。而有了厨师(DI),我们只要在菜谱上点菜,必要时备注一下烹饪方式,不过多久香喷喷的菜就上桌鸟~~~。