精华内容
参与话题
问答
  • Angular依赖注入介绍

    2019-06-01 15:39:43
    依赖注入(DI – Dependency Injection)是一种重要的应用设计模式。Angular里面也有自己的DI框架,在设计应用时经常会用到它,它可以我们的开发效率和模块化程度。        依赖,是当类需要执行...

           依赖注入(DI – Dependency Injection)是一种重要的应用设计模式。Angular里面也有自己的DI框架,在设计应用时经常会用到它,它可以我们的开发效率和模块化程度。

           依赖,是当类需要执行其功能时,所需要的服务或对象。DI是一种编码模式,其中的类会从外部源中请求获取依赖,而不需要我们自己创建它们。

           Angular系统中通过在类上添加@Injectable装饰器来告诉系统这个类(服务)是可注入的。当然了这仅仅只是告诉Angular系统这个类(服务)类是可注入的。但是这个服务可以在哪里使用,由谁提供,就得靠注入器和提供商来一起确定了。

    • 注入器: 注入器负责服务实例的创建,并把它们注入到你想要注入的类中。从而确定服务的使用范围和服务的生命周期。

    • 提供商: 服务由谁提供。Angular本身没法自动判断你是打算自行创建服务类的实例,还是等注入器来创建它。如果想通过注入器来创建,必须在每个注入器里面为每个服务指定服务提供商。

    一 注入器(Injector)

           Angular依赖注入中的注入器(Injector),用来管理服务。包括服务的创建,服务的获取。

           Angular依赖注入系统中的注入器(Injector)是多级的。实际上,应用程序中有一个与组件树平行的注入器树。你可以在组件树中的任何级别上重新配置注入器,注入提供商。

           还有一点要特别注意,Angular注入器是冒泡机制的。当一个组件申请获得一个依赖时,Angular先尝试用该组件自己的注入器来满足它。如果该组件的注入器没有找到对应的提供商,它就把这个申请转给它父组件的注入器来处理。如果当前注入器也无法满足这个申请,它就继续转给它在注入器树中的父注入器。这个申请继续往上冒泡—直到Angular找到一个能处理此申请的注入器或者超出了组件树中的祖先位置为止。如果超出了组件树中的祖先还未找到,Angular就会抛出一个错误。

    所有所有的一切最终都是服务组件的。每个组件的注入器其实包含两部分:组件本身注入器的,组件所在NgMoudle对应的注入器。

           在我们的Angular系统中我们可以认为NgModule是一个注入器,Component也是一个注入器。

    1.1 NgMoudle(模块)级注入器

           NgMoudle(模块)级注入器会告诉Angualr系统把服务作用在NgModule上。这个服务器可以在这个NgModule范围下所有的组件上使用。NgModule级注入服务有两种方式:一个是在 @NgModule()的providers元数据中指定、另一种是直接在@Injectable()的providedIn选项中指定模块类。

           NgMoudle级注入器两种方式:@NgModule() providers 元数据中指定、或者直接在@Injectable() 的 providedIn 选项中指定某个模块类。

    1.1.1 通过@NgModule()的providers将服务注入到NgModule中

           通过@NgModule()的providers将服务注入到NgModule中,限制服务只能在当前NgModule里面使用。

    export interface NgModule {
      ...
      
      /**
       * 本模块需要创建的服务。这些服务可以在本模块的任何地方使用。
       * NgModule我们可以认为是注入器,Provider是提供商。注入器通过提供商的信息来创建服务
       */
      providers?: Provider[];
      
      ...
    }
    

    关于Provider(提供商)更加具体的用户我们会在下文做详细的解释。

           比如如下的代码我们先定义一个NgmoduleProvidersService服务类。当前类只是添加了@Injectable()告诉Angular系统这是一个可注入的服务。

    import {Injectable} from '@angular/core';
    
    /**
     * 我们将在NgmoduleProvidersModule中注入该服务。
     * 然后在NgmoduleProvidersComponent里面使用该服务
     */
    @Injectable()
    export class NgmoduleProvidersService {
    
        constructor() {
        }
    
        // TODO:其他逻辑
    }
    

           接下来,想在NgmoduleProvidersModule模块里面所有的地方使用该服务。很简单,我们在@NgModule元数据providers里面指定这个类NgmoduleProvidersService就好了。(该服务的TOKEN是NgmoduleProvidersService,提供商也是他自己。关于提供商更加具体的用法,我们会在下文详细讲解)

    import {NgModule} from '@angular/core';
    import {CommonModule} from '@angular/common';
    import {NgmoduleProvidersComponent} from './ngmodule-providers.component';
    import {NgmoduleProvidersRoutingModule} from './ngmodule-providers-routing.module';
    import {NgmoduleProvidersService} from './ngmodule-providers.service';
    
    @NgModule({
        declarations: [
            NgmoduleProvidersComponent
        ],
        providers: [
            NgmoduleProvidersService,
        ],
        imports: [
            CommonModule,
            NgmoduleProvidersRoutingModule
        ]
    })
    export class NgmoduleProvidersModule {
    }
    

    1.1.2 通过@Injectable()的providedIn将服务注入到NgModule中

           @Injectable()装饰器里面的元数据providedIn也可以直接指定NgModue。来告知服务可以在哪里使用。providedIn的值可以有三种:一种是Type也是NgModule、一种是字符串’root’、一种是null。

    export interface InjectableDecorator {
        /**
         * providedIn有三种值:Type<any>、 ‘root’、 null
         * Type<any>指的是NgModule
         */
        (): TypeDecorator;
        (options?: {
            providedIn: Type<any> | 'root' | null;
        } & InjectableProvider): TypeDecorator;
        new (): Injectable;
        new (options?: {
            providedIn: Type<any> | 'root' | null;
        } & InjectableProvider): Injectable;
    }
    

    当providedIn是null的时候。咱们仅仅是告诉了系统这个类是可注入的。在其他的地方还使用不了。如果想使用需要在NgModule装饰器或者Component装饰器里面的元数据providers中指定。

    1.1.2.1 providedIn: ‘root’

           providedIn: ‘root’。咱们可以简单的认为root字符串就代表顶级AppModule。表明当前服务可以在整个Angular应用里面使用。而且在整个Angular应用中只有一个服务实例。

           比如如下的代码我们定义一个StartupService服务类。 providedIn: ‘root’。则
    StartupService这个类当前项目的任务地方注入使用。而且都是同一份实例对象。

    import {Injectable} from '@angular/core';
    
    /**
     * StartupService可以在系统的任务地方使用
     */
    @Injectable({
        providedIn: 'root'
    })
    export class StartupService {
    
        constructor() {
        }
    
        // TODO: 其他逻辑
    }
    

    1.1.2.2 providedIn: NgModule

           providedIn: NgModule。通过providedIn直接指定一个NgModule。让当前服务只能在这个指定的NgModule里面使用。

           而且providedIn: NgModule这种情况是可以摇树优化。只要在服务本身的 @Injectable() 装饰器中指定提供商,而不是在依赖该服务的NgModule或组件的元数据中指定,你就可以制作一个可摇树优化的提供商。当前前提是这个NgModule是懒加载的。

    摇树优化是指一个编译器选项,意思是把应用中未引用过的代码从最终生成的包中移除。如果提供商是可摇树优化的,Angular编译器就会从最终的输出内容中移除应用代码中从未用过的服务。 这会显著减小你的打包体积。

           providedIn: NgModule使用的时候有一个特别要特别注意的地方。举个例子比如我们想在NgmoduleProvidersModule模块中使用NgmoduleProviderInModuleService服务。如下的写法是不对的。

    import { Injectable } from '@angular/core';
    import {NgmoduleProvidersModule} from './ngmodule-providers.module';
    
    @Injectable({
      providedIn: NgmoduleProvidersModule
    })
    export class NgmoduleProviderInModuleService {
    
      constructor() { }
    }
    

    编译的时候会抛出一个警告信息,编译不过。

    WARNING in Circular dependency detected:
    

           为了解决这个异常信息,让代码能正常编译,我们需要借助一个NgModule(NgmoduleProvidersResolveModule名字你随便来)来过渡下。这个过渡NgModule赋值给providedIn。最后在我们真正想使用该服务的NgModule里面imports这个过渡NgModule。说的有点绕来绕去的。我们直接用代码来说明。

    // 需要在模块NgmoduleProvidersModule里面使用的服务NgmoduleProviderInModuleService
    
    import {Injectable} from '@angular/core';
    import {NgmoduleProvidersResolveModule} from './ngmodule-providers-resolve.module';
    
    /**
     * providedIn中直接指定了当前服务可以在哪个模块使用
     * 特别说明:我们想在NgmoduleProvidersModule模块里面使用该服务,
     * 如果providedIn直接写NgmoduleProvidersModule,会报编译错误,
     * 所以我们定义了一个中间模块NgmoduleProvidersResolveModule,
     * 然后在NgmoduleProvidersModule里面引入了NgmoduleProvidersResolveModule。
     *
     * NgmoduleProvidersResolveModule相当于一个过渡的作用
     */
    @Injectable({
        providedIn: NgmoduleProvidersResolveModule
    })
    export class NgmoduleProviderInModuleService {
    
        constructor() {
        }
    
        // TODO: 其他逻辑
    }
    
    
    // 过渡NgModule NgmoduleProvidersResolveModule
    
    import {NgModule} from '@angular/core';
    
    /**
     * providedIn: NgModule的时候NgModule不能直接写对应的NgModule,
     * 需要一个过渡的NgModule。否则编译报错:WARNING in Circular dependency detected
     */
    @NgModule({
    })
    export class NgmoduleProvidersResolveModule {
    }
    
    
    
    // NgmoduleProvidersModule 服务将在该模块里面使用。
    
    import {NgModule} from '@angular/core';
    import {CommonModule} from '@angular/common';
    import {NgmoduleProvidersComponent} from './ngmodule-providers.component';
    import {NgmoduleProvidersRoutingModule} from './ngmodule-providers-routing.module';
    import {NgmoduleProvidersService} from './ngmodule-providers.service';
    import {NgmoduleProvidersResolveModule} from './ngmodule-providers-resolve.module';
    
    
    @NgModule({
        declarations: [
            NgmoduleProvidersComponent
        ],
        providers: [
            NgmoduleProvidersService,
        ],
        imports: [
            CommonModule,
            /**
             * 导入了过渡的NgModule
             */
            NgmoduleProvidersResolveModule,
            NgmoduleProvidersRoutingModule
        ]
    })
    export class NgmoduleProvidersModule {
    }
    

    1.2 Component(组件)级注入器

           组件级注入器,每个组件也是一个注入器。通过在组件级注入器中注入服务。这样该组件实例或其下级组件实例都可以使用这个服务(当然我们也可以设置只在当前组件使用,子组件不能使用。这个就涉及到viewProviders和providers的区别了)。组件注入器提供的服务具有受限的生命周期。该组件的每个新实例都会获得自己的一份服务实例。当销毁组件实例时,服务实例也会被同时销毁。所以组件级别的服务和组件是绑定在一起的。一起创建一起消失。

           我们通过一个简单的实例来看看组件级注入器的使用。

    首先定义一个ComponentInjectService服务。

    import { Injectable } from '@angular/core';
    
    /**
     * 当前服务在组件里面使用,会在需要使用的组件里面注入
     */
    @Injectable()
    export class ComponentInjectService {
    
      constructor() { }
    }
    

    然后在组件里面注入

    import {ComponentInjectService} from './component-inject.service';
    
    @Component({
        selector: 'app-ngmodule-providers',
        templateUrl: './ngmodule-providers.component.html',
        styleUrls: ['./ngmodule-providers.component.less'],
        providers: [ComponentInjectService],   // providers提供的服务在当前组件和子组件都可以使用
        // viewProviders: [ComponentInjectService], // viewProviders提供的服务在当前组件使用
    })
    export class NgmoduleProvidersComponent implements OnInit {
        
        constructor(private service: ComponentInjectService) {
        }
    
        ngOnInit() {
        }
    
    }
    

    二,提供商(Provider)

           上面所有的实例代码,咱们往注入器里面注入服务的时候,使用的是最简单的一种方式TypeProvider,也是咱们用的最多的一种方式。不管是@NgModule装饰器里面还是@Component装饰器里面。providers元数据里面都是直接写了服务类。类似如下的代码。

    @NgModule({
        ...
        providers: [
            NgmoduleProvidersService,
        ],
        ...
    })
    

    上面代码中的providers对象是一个Provider(提供商)数组(当前注入器需要注入的依赖对象),在注入器中注入服务时咱们还必须指定这些提供商,否则注入器就不知道怎么来创建此服务。Angular系统中我们通过Provider来描述与Token相关联的依赖对象的创建方式。

          简而言之Provider是用来描述与Token关联的依赖对象的创建方式。当我们使用Token向DI系统获取与之相关连的依赖对象时,DI 会根据已设置的创建方式,自动的创建依赖对象并返回给使用者。中间过程我们不需要过。我们只需要知道哪个Token对应哪个(或者哪些)服务就好了。通过Token来获取到对应的服务。所以关于Povider我们重点需要知道以下两个东西:Token,Token对应对象的创建方式。

    2.1 Povider Token

          Token的作用是用来标识依赖对象的,Token值可以是Type、InjectionToken、OpaqueToken类的实例或字符串。通常不推荐使用字符串,因为如果使用字符串存在命名冲突的可能性比较高。

    你可以简单的认为Token是依赖对象的key。在我们需要使用依赖对象的时候我们可以通过这个key找到依赖对象。

    2.2 对象的创建方式

          给出了依赖对象的创建方式,注入器才能知道怎么去创建对象。Provider有如下几种方式:TypeProvider ,ValueProvider,ClassProvider,ConstructorProvider, ExistingProvider,FactoryProvider,any[]。

    export declare type Provider = TypeProvider | ValueProvider | ClassProvider | ConstructorProvider | ExistingProvider | FactoryProvider | any[];
    

    ConstructorProvider这种方式,咱们就不考虑了,我是在是没找到这种方式的使用场景。

    2.2.1 TypeProvider

    export interface TypeProvider extends Type<any> {
    }
    

          TypeProvider用于告诉Injector(注入器),使用给定的Type创建对象,并且Token也是给定的Type。这也是我们用的最多的一种方式。比如如下。就是采用的TypeProvider方式。

    @NgModule({
      ...
      providers: [NgmoduleProvidersService], // NgmoduleProvidersService是我们定义的服务,TypeProvider方式
    })
    

    2.2.2 ClassProvider

          ClassProvider用于告诉Injector(注入器),useClass指定的Type创建的对应对象就是Token对应的对象。

    export interface ClassSansProvider {
        /**
         * token生成对象对应的class.
         * 用该class生成服务对象
         */
        useClass: Type<any>;
    }
    
    export interface ClassProvider extends ClassSansProvider {
        /**
         * 用于设置与依赖对象关联的Token值,Token值可能是Type、InjectionToken、OpaqueToken的实例或字符串
         */
        provide: any;
        /**
         * 用于标识是否multiple providers,若是multiple类型,则返回与Token关联的依赖对象列表
         * 简单来说如果multi是true的话,通过provide(Token)获取的依赖对象是一个列表。
         * 同一个Token可以注入多个服务
         */
        multi?: boolean;
    }
    

          简单使用

    export const TOKEN_MODULE_CLASS_PROVIDER = new InjectionToken<any>('TOKEN_MODULE_CLASS_PROVIDER');
    // ModuleClassProviderService类是我们依赖对象
    
    @NgModule({
        ...
        providers: [
            {
                provide: TOKEN_MODULE_CLASS_PROVIDER, useClass: ModuleClassProviderService
            }
        ],
        ...
    })
    export class ClassProviderModule {
    }
    

    2.2.3 ValueProvider

          ValueProvider用于告诉Injector(注入器),useValue指定的值(可以是具体的对象也可以是string ,number等等之类的值)就是Token依赖的对象。

    export interface ValueSansProvider {
      /**
       * 需要注入的值
       */
      useValue: any;
    }
    
    export interface ValueProvider extends ValueSansProvider {
      /**
       * 用于设置与依赖对象关联的Token值,Token值可能是Type、InjectionToken、OpaqueToken的实例或字符串
       */
      provide: any;
      /**
       * 用于标识是否multiple providers,若是multiple类型,则返回与Token关联的依赖对象列表
       * 简单来说如果multi是true的话,通过provide(Token)获取的依赖对象是一个列表。
       * 同一个Token可以注入多个服务
       */
      multi?: boolean;
    }
    

          简单实例。

    export const TOKEN_MODULE_CONFIG = new InjectionToken<Config>('TOKEN_MODULE_CONFIG');
    
    /**
     * Config是我们自定义的一个配置对象
     */
    const config = new Config();
    config.version = '1.1.2';
    
    @NgModule({
        ...
        providers: [
            {provide: TOKEN_MODULE_CONFIG, useValue: config},
        ],
        ...
    })
    export class ValueProviderModule {
    }
    

    2.2.4 FactoryProvider

          FactoryProvider 用于告诉 Injector (注入器),通过调用 useFactory对应的函数,返回Token对应的依赖对象。

    export interface FactorySansProvider {
      /**
       * 用于创建对象的工厂函数
       */
      useFactory: Function;
      /**
       * 依赖对象列表(你也可以简单的认为是创建对象构造函数里面需要的依赖对象)
       */
      deps?: any[];
    }
    
    export interface FactoryProvider extends FactorySansProvider {
      /**
       * 用于设置与依赖对象关联的Token值,Token值可能是Type、InjectionToken、OpaqueToken的实例或字符串
       */
      provide: any;
      /**
       * 用于标识是否multiple providers,若是multiple类型,则返回与Token关联的依赖对象列表
       * 简单来说如果multi是true的话,通过provide(Token)获取的依赖对象是一个列表。
       * 同一个Token可以注入多个服务
       */
      multi?: boolean;
    }
    

          useFactory对应一个函数,该函数需要的对象通过deps提供,deps是一个Token数组。

    // TOKEN
    export const TOKEN_FACTORY_MODULE_DEPS = new InjectionToken<ModuleFactoryProviderService>('TOKEN_FACTORY_MODULE_DEPS');
    export const TOKEN_FACTORY_MODULE = new InjectionToken<ModuleFactoryProviderService>('TOKEN_FACTORY_MODULE');
    
    
    /**
     * 创建ModuleFactoryProviderService对象,
     * 该对象依赖另一个服务,通过deps提供
     */
    function moduleServiceFactory(initValue) {
        return new ModuleFactoryProviderService(initValue);
    }
    
    @NgModule({
        ...
        providers: [
            { // 创建TOKEN_FACTORY_MODULE对应的服务时候,需要依赖的值
                provide: TOKEN_FACTORY_MODULE_DEPS,
                useValue: 'initValue'
            },
            {
                provide: TOKEN_FACTORY_MODULE,
                useFactory: moduleServiceFactory,
                deps: [TOKEN_FACTORY_MODULE_DEPS]
            }
        ],
        ...
    })
    export class FactoryProviderModule {
    }
    

    2.2.5 ExistingProvider

          ExistingProvider用于告诉Injector(注入器),想获取Token(provide)对应的对象的时候,使用useExisting(Token)对应的对象。

    一定要记住useExisting对应的值也是一个Token。

    export interface ExistingSansProvider {
      /**
       * 已经存在的 `token`  (等价于 `injector.get(useExisting)`)
       */
      useExisting: any;
    }
    
    export interface ExistingProvider extends ExistingSansProvider {
      /**
       * 用于设置与依赖对象关联的Token值,Token值可能是Type、InjectionToken、OpaqueToken的实例或字符串
       */
      provide: Type<any>;
      /**
       * 用于标识是否multiple providers,若是multiple类型,则返回与Token关联的依赖对象列表
       * 简单来说如果multi是true的话,通过provide(Token)获取的依赖对象是一个列表。
       * 同一个Token可以注入多个服务
       */
      multi?: boolean;
    }
    

          实例代码。

    @NgModule({
        ...
        providers: [
            ModuleExistingProviderServiceExtended, // 我们先通过TypeProvider的方式注入了ModuleExistingProviderServiceExtended
            {provide: ModuleExistingProviderService, useExisting: ModuleExistingProviderServiceExtended}
        ],
        ...
    })
    export class ExistingProviderModule {
    }
    

    三,获取依赖对象

          通过上面的讲解,我们已经知道怎么的在指定的注入器里面通过提供商注入相应的依赖对象。如果我们想在指定的地方(一般是组件里面)使用依赖对象,就得先拿到对象。接下来我们就得叨叨怎么拿到这个对象了。

          通过提供者(providers)注入服务的时候,每个服务我们都给定了Token(Provider里面的provide对象对应的值)。TypeProvider例外,其实TypeProvider虽然没有明确的指出Token。其实内部的处理,Token就是TypeProvider设置的Type。

          我们总结出获取依赖对象有三种方式:

    3.1 构造函数中通过@Inject获取

          借助@Inject装饰器获取到指定的依赖对象。@Inject的参数就是需要获取的依赖对象对应的Token。

        /**
         * 通过@Inject装饰器获取Token对应依赖的对象
         */
        constructor(@Inject(TOKEN_MODULE_CLASS_PROVIDER) private service: ModuleClassProviderService) {
        }
    

    3.2 通过Injector.get(Token)获取

          先在构造函数中把Injector对象注入进来,然后在通过Injector.get(Token)获取对象。同样参数也是依赖对象对应的Token。

        service: ModuleClassProviderService;
    
        /**
         * 借助Injector服务来获取Token对应的服务
         */
        constructor(private injector: Injector) {
           this.service = injector.get(TOKEN_MODULE_CLASS_PROVIDER);
        }
    

    3.3 构造函数中通过Type获取

          直接在构造函数中通过Type来获取,这种获取方式有个前提。必须是TypeProvider方式提供的服务。

        constructor(private service: ModuleClassProviderService) {
        }
    
    

    四,Provider中的multi

          上面讲提供商(Provider)的时候多次出现了multi。multi表示同一个Token对应的服务可以是多个。当使用multi的时候。通过Token获取依赖服务的时候是一个服务数组。其实也很好理解。比如网络拦截器。是允许同一个Token有多个服务。每个拦截器做不同的逻辑处理。


          文章最后给出文章涉及到的实例代码下载地址https://github.com/tuacy/angular-inject,同时我们对Angular依赖注入的使用做一个简单的总结。Angular里面使用依赖注入步骤:

    • 1.定义依赖对象的业务逻辑。

    就是定义依赖对象服务类,确定服务类需要干哪些事情。

    • 2.明确我们依赖对象的作用范围。

    确定注入器,是用NgModule注入器呢,实时Component注入器。

    • 3.依赖对象的Token确定。

    依赖对象的获取都是通过Token去获取的。

    • 4.依赖对象提供商的确定。

    Provider用那种方式,TypeProvider呢,还是ValueProvider呢等等。

    • 5.在需要使用依赖对象的地方获取到依赖对象。
    展开全文
  • 依赖注入(Dependency Injection,简称DI)是像C#,java等典型的面向对象语言框架设计原则控制反转的一种典型的一种实现方式,angular把它引入到js中,介绍angular依赖注入的使用方式的文章很多, angular官方的文档...

    依赖注入(Dependency Injection,简称DI)是像C#,java等典型的面向对象语言框架设计原则控制反转的一种典型的一种实现方式,angular把它引入到js中,介绍angular依赖注入的使用方式的文章很多,
    angular官方的文档,也有很详细的说明。但介绍原理的较少,angular代码结构较复杂,文章实现了一简化版本的DI,核心代码只有30行左右,相看实现效果(可能需FQ)或查看源码

    这篇文章用尽量简单的方式说一说 angular依赖注入的实现。

    简化的实现原理

    要实现注入,基本有三步:

    1. 得到模块的依赖项
    2. 查找依赖项所对应的对象
    3. 执行时注入

    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]就可以直接拿到对象。所以只要用一个对象保存对象或函数列表就可以了

    function 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. 得到模块的依赖项
    2. 查找依赖项所对应的对象
    3. 执行时注入

    注:以下代码行数有就可能变

    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;
    
    function 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

        function getService(serviceName, caller) {
          if (cache.hasOwnProperty(serviceName)) {
            if (cache[serviceName] === INSTANTIATING) {
              throw $injectorMinErr('cdep', '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

    得到参数:

        function 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') {
              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));
          }
          return args;
        }

    调用

    https://github.com/angular/angular.js/blob/master/src%2Fauto%2Finjector.js#L861

        function invoke(fn, self, locals, serviceName) {
          if (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
            return fn.apply(self, args);
          } else {
            args.unshift(null);
            return 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

    展开全文
  • 依赖注入式AngularJS的重要特性之一,依赖注入简化了Angular解析模块/组件之间依赖的过程。   什么是依赖注入 wiki 上的解释是:依赖注入(Dependency Injection,简称DI)是一种软件设计模式,在这种模式下, ...

     

    1.背景介绍

    依赖注入式AngularJS的重要特性之一,依赖注入简化了Angular解析模块/组件之间依赖的过程。

     

    什么是依赖注入

    wiki 上的解释是:依赖注入(Dependency Injection,简称DI)是一种软件设计模式,在这种模式下, 一个或更多的依赖(或服务)被注入(或者通过引用传递)到一个独立的对象(或客户端)中,然后成为了该客户端状态的一部分。 该模式分离了客户端依赖本身行为的创建,这使得程序设计变得松耦合,并遵循了依赖反转和单一职责原则。与服务定位器模式形 成直接对比的是,它允许客户端了解客户端如何使用该系统找到依赖 .

    2.知识剖析

    ANGULAR依赖注入机制

    AngularJS 提供了5个核心组件用来作为依赖注入:

    value 
    factory
    service
    provider
    constant

    $PROVIDE (供应商)

    供应商==>泛指provider 
    服务==>泛指service 
    provider==>provider()方法创建的代码块
    service==>service()方法创建的代码块

    $provide服务负责告诉Angular如何创造一个新的可注入的代码块:即服务。服务会被叫做供应商的代码块来定义,我们可以使用$provide来创建一个供应商。

    我们需要使用$provide中的provider()方法来定义一个供应商,同时也可以通过要求$provide被注入到一个应用的config函数中来获得$provide服务。

    $PROVIDE

    $provide负责告诉Angular如何创建新的可注入的事物,即服务。服务是被providers定义的,即当你用$provide创建的。通过$provide服务来定义provider,你可以通过在应用的配置函数中注入$provide来持有该服务。

    
                        angular.module(‘app’,[]).config(function($provide) {
                            $provide.provider('greeting', function() {
                                this.$get = function() {
                                    return function(name) {
                                    alert("Hello, " + name);
                                    };
                                };
                            });
                        });    
                    

    在上面的例子中我们为一个服务定义了一个叫做greeting的新provider。 我们可以把一个叫做greeting的变量注入到任何可注入的函数中 (例如控制器)然后Angular就会调用这个provider的$get函数来返回这个服务的一个实例。

    在上面的例子中,被注入的是一个函数, 它接受一个参数并且根据这个参数alert一条信息。我们可以像下面这样使用它:

    
                        app.controller('MyCtrl', function($scope, greeting) {
                            $scope.onClick = function() {
                                greeting('Ford Prefect');
                            };
                        });
                    

    定义供应商的方法

    1、Constant

    Constant定义常量,Constant定义的值不应该被改变,它可以被注入到任何地方,但是不能被装饰器(decorator)装饰

    
                        var app = angular.module('app', []);
                        app.config(function ($provide) {
                            $provide.constant('myConstant', 'The Matrix');
                        }); 
                    

    语法糖:

    
                        app.constant('myConstant', 'The Matrix');
                    

    2、VALUE

    Value可以是string,number甚至function,它和constant的不同之处在于,它可以被修改,不能被注入到config中,但是它可以被decorator装饰

    
                        var app = angular.module('app', []);
                        app.config(function ($provide) {
                            $provide.value('myValue', 'The Matrix')
                        });
                        

    语法糖:

    
                        app.value('myValue', 'The Matrix');
                    

    3、SERVICE

    Service是一个可注入的构造器,在AngularJS中它是单例的,用它在Controller中通信或者共享数据都很合适. 在service里面可以不用返回东西,因为AngularJS会调用new关键字来创建对象。

    
                        var app = angular.module('app' ,[]);
                        app.config(function ($provide) {
                            $provide.service('myService', function () {
                                this.title = 'The Matrix';
                            });
                        });
                    

    语法糖:

    
                        app.service('myService', function () {
                            this.title = 'The Matrix';
                        });
                    

    4、FACTORY

    Factory是一个可注入的function,它和service的区别就是:factory是普通function,而service是一个构造器(constructor), Angular在调用service时会用new关键字,而调用factory时只是调用普通的function,所以factory可以返回任何东西,而service可以不返回. factory可以返回任何东西,它实际上是一个只有$get方法的provider

    
                        var app = angular.module('app', []);
                        app.config(function ($provide) {
                            $provide.factory('myFactory', function () {
                                return {
                                    title: 'The Matrix'
                                }
                            });
                        });
                    

    语法糖:

    
                        app.factory('movie', function () {
                            return {
                                title: 'The Matrix'
                            }
                        });
                    

    5、PROVIDER

    provider是value、Service、Factory的提供者,除了constant其它都是provider的简写形式。 provider必须有一个$get方法,当然也可以说provider是一个可配置的factory

    
                    app.provider('greeting', function() {
                        var text = 'Hello, ';
                        this.setText = function(value) {
                            text = value;
                        };
                        this.$get = function() {
                            return function(name) {
                                alert(text + name);
                            };
                    };
                    });
                    app.config(function(greetingProvider) {
                    greetingProvider.setText("Howdy there, ");
                    });
                    app.run(function(greeting) {
                    greeting('Ford Prefect');
                    });
                    

    注意这里config方法注入的是greetingProvider,上面定义了一个供应商叫greeting,但是注入到config中不能直接写greeting, 因为前文讲了注入的那个东西就是服务,是供应商提供出来的,而config中又只能注入供应商(两个例外是$provide和$injector), 所以用驼峰命名法写成greetingProvider,Angular就会帮你注入它的供应商。

    factory和value等服务与provider都是提供服务,为什么还有人要使用provider。答案是provider允许我们进行一些配置。 在前面我们已经提到过当你通过provider(或者其他简写方法)创建一个服务时,你实际上创建了一个新的provider, 它将定义我们的服务如何被创建。这些provider可以被注入到config函数中,我们可以和它们进行一些交互。如上文例子:

    AngularJS分两个阶段运行我们的用 – config阶段和run阶段。config阶段是我们设置任何的provider的阶段。 它也是我们设置任何的指令,控制器,过滤器以及其它东西的阶段。在run阶段,AngularJS会编译你的DOM并启动我们的应用。

    总结

    所有的供应商都只被实例化一次,也就说他们都是单例的; 除了constant,所有的供应商都可以被装饰器(decorator)装饰

    value就是一个简单的可注入的值
    service是一个可注入的构造器
    factory是一个可注入的方法
    decorator可以修改或封装其他的供应商,当然除了constant
    provider是一个可配置的factory

    3.常见问题

    AngularJS依赖注入的方法

    4.解决方案

    1. 通过函数的参数进行推断式注入声明

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

    2、显式注入声明

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

    通过这种方法声明依赖,即使在源代码被压缩、参数名称发生改变的情况下依然能够正常工作。

    需要注意的地方: 
    对于这种声明方式来讲,参数的顺序是十分重要的,因为$inject数组元素的顺序必须和注入的参数的顺序一一对应。

    3、行内注入声明

    AngularJS提供的行内注入方法实际上是一种语法糖,它与前面的提到的通过$inject属性进行声明的原理是一样的,但是允许我们在函数定义的时候从行内将参数传入,这种方法方便,简单,而且避免了在定义的过程中使用临时变量。

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

    5.编码实战

    6.扩展思考

    AngularJS中的依赖注入有何优点?

    一、模板功能强大丰富,并且是声明式的,自带了丰富的Angular指令;

    二、是一个比较完善的前端MVC框架,包含模板,数据双向绑定,路由,模块化,服务,过滤器,依赖注入等所有功能;

    三、依赖注入简化了组件之间处理依赖的过程(即解决依赖)。没有依赖注入,就不得不以某种方式自己查找$scope,很可能得使用全局变量。这虽然能够工作,但是不如AngularJS的依赖注入技术这么简单。

    四、在开发中使用依赖注入的主要好处是AngularJS负责管理组件并在需要的时候提供给相应函数。依赖注入还能够为测试带来好处,因为它允许你使用假的或者模拟的对象来代替真实的组件,从而让开发者专注于程序的特定部分。

    7.参考文献

    参考一  玩转 AngualrJS 的依赖注入

    参考二  详解AngularJS中的依赖注入

    参考三  理解AngularJS中的依赖注入

    8.更多讨论

    问:AngularJS依赖注入的方法

    答:

    1. 通过函数的参数进行推断式注入声明

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

    2、显式注入声明

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

    通过这种方法声明依赖,即使在源代码被压缩、参数名称发生改变的情况下依然能够正常工作。

    需要注意的地方: 
    对于这种声明方式来讲,参数的顺序是十分重要的,因为$inject数组元素的顺序必须和注入的参数的顺序一一对应。

    3、行内注入声明

    AngularJS提供的行内注入方法实际上是一种语法糖,它与前面的提到的通过$inject属性进行声明的原理是一样的,但是允许我们在函数定义的时候从行内将参数传入,这种方法方便,简单,而且避免了在定义的过程中使用临时变量。

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

    问:什么时候使用service 

    答:什么时都可以,service和factory功能相同

    问:service和factory的区别

    答:factory 是普通函数,可以返回任何东西。service 是构造函数,可以不反回东西。功能相同。

     

    技能树.IT修真院   

     “我们相信人人都可以成为一个工程师,现在开始,找个师兄,带你入门,掌控自己学习的节奏,学习的路上不再迷茫”。

    这里是技能树.IT修真院,成千上万的师兄在这里找到了自己的学习路线,学习透明化,成长可见化,师兄1对1免费指导。

    快来与我一起学习吧~邀请链接 点击打开链接

     

    展开全文
  • angular4 服务依赖注入的三种方法

    千次阅读 2017-09-28 22:30:18
    假设有服务authservice,现在要把它注入到我们的组件中。有下列三种方法。 方法一:最简单直接,直接生产一个该服务的实例对象。 import { Component, OnInit } from'@angular/core'; //引入AuthService ...

    假设有服务authservice,现在要把它注入到我们的组件中。有下列三种方法。


    方法一:最简单直接,直接生产一个该服务的实例对象。

    import { Component, OnInit } from'@angular/core';

    //引入AuthService

    import { AuthService } from'../core/auth.service';

     

    @Component({

      selector:'app-login',

      template:`

        <div>

          <input #usernameReftype="text">

          <input #passwordReftype="password">

          <button(click)="onClick(usernameRef.value,passwordRef.value)">Login</button>

        </div>

      `,

      styles: []

    })

    exportclassLoginComponentimplements OnInit {

     

      //声明成员变量,其类型为AuthService

      service: AuthService;

     

      constructor() {

        this.service =newAuthService();

      }

     

      ngOnInit() {

      }

     

      onClick(username,password) {

        //调用service的方法

        console.log('auth result is:'+this.service.loginWithCredentials(username, password));

      }

     

    }

     

    方法二:

    import { Component, OnInit } from'@angular/core';

    import { AuthService } from'../core/auth.service';

     

    @Component({

      selector:'app-login',

      template:`

        <div>

          <input #usernameReftype="text">

          <input #passwordReftype="password">

          <button(click)="onClick(usernameRef.value,passwordRef.value)">Login</button>

        </div>

      `,

      styles: [],

      //providers中配置AuthService

      providers:[AuthService]

    })

    exportclassLoginComponentimplements OnInit {

      //在构造函数中将AuthService示例注入到成员变量service

      //而且我们不需要显式声明成员变量service

      constructor(private service: AuthService) {

      }

     

      ngOnInit() {

      }

     

      onClick(username,password) {

        console.log('auth result is:'+this.service.loginWithCredentials(username, password));

      }

     

    }

    import是要将类型引入进来,而provider里面会配置这个类型的实例。

     

    方法三:服务在主模块中声明,组件只是把主模块中的实例对象引用过来。推荐这种方式!

    import { Component, OnInit, Inject } from'@angular/core';

     

    @Component({

      selector:'app-login',

      template:`

        <div>

          <input #usernameReftype="text">

          <input #passwordReftype="password">

          <button(click)="onClick(usernameRef.value,passwordRef.value)">Login</button>

        </div>

      `,

      styles: []

    })

    exportclassLoginComponentimplements OnInit {

     

      constructor(@Inject('auth') private service) {

      }

     

      ngOnInit() {

      }

     

      onClick(username,password) {

        console.log('auth result is:'+this.service.loginWithCredentials(username, password));

      }

     

    }

    展开全文
  • Angular依赖注入实例

    2018-07-01 17:23:20
    (它是对所有组件可见的,所有组件都可以注入它)先注册服务shared/product:输入命令:ng g service shared/product(生成一个服务在shared包中)product.service.ts:import { Injectable } from '@angular/core'...
  • Angular6 依赖注入

    千次阅读 2018-07-23 17:03:32
    Angular依赖注入器负责创建服务的实例,并把它们注入到你想要注入的类中。 Angular 本身没法自动判断你是打算自行创建服务类的实例,还是等注入器来创建它。如果想通过注入器来创建,必须为每个服务指定服务提供...
  • angular依赖注入的理解及实现原理

    千次阅读 2019-03-18 00:26:15
    Angular 有自己的依赖注入框架。 什么是依赖注入:它是一种编程模式,可以让类从外部源中获得它的依赖,而不必亲自创建它们。 class没有解耦前是这样的: class Person的实现需要使用到Adderss,Id里面的实例...
  • Angular 依赖注入

    2018-08-24 14:40:40
    Angular 依赖注入 Angular 依赖注入 依赖注入的好处 依赖注入 提供器的作用域 服务之间互相注入 使用工厂和值声明提供器 注入器的层级关系 依赖注入:Dependency Injection 简称DI 控制反转:Inversion...
  • Angular依赖注入模式 依赖注入: Dependency Injection 简称 DI 依赖注入模式要解决的问题 开发中,会经常遇见对象的实例化,当多个对象之间存在依赖时,手工实例化会特别麻烦。 如果一个对象A要依赖对象B,那么...
  • Angular依赖注入

    2018-10-06 22:04:16
    依赖注入内容 什么是依赖注入 依赖注入:Dependency Injection简称DI(IOC实现控制反转的手段) 控制反转:Inversion of Control简称IOC(将依赖的控制权从代码的内部转移到代码的外部) 实现了控制反转的框架叫做:...
  • angular依赖注入by Neeraj Dana 由Neeraj Dana In this article, we will see how the dependency injection of Angular works internally. Suppose we have a component named appcomponent which has a basic and...
  • 用途 有时候注入的不一定需要一个类也许只是一个值的时候可以使用这个来注入 用法 { provide: 'FactorService', useValue: { data: 'useValue show real value!'} }...
  • 浅谈angular依赖注入

    2020-02-20 16:21:25
    一、什么是依赖注入? 首先,用最通俗的语言来说,某公司财务类要给员工发放工资,它需要人事类提供一个薪资标准,财务类想要完成工资发放,就必须实例化人事类,,也就是说财务类依赖于人事类; 官方回答: 依赖...
  • angular 依赖注入原理

    2016-12-06 10:14:00
    依赖注入(Dependency Injection,简称DI)是像C#,java等典型的面向对象语言框架设计原则控制反转的一种典型的一种实现方式,angular把它引入到js中,介绍angular依赖注入的使用方式的文章很多,angular官方的文档,...
  • 1. providedIn: ‘root’ The @Injectable() decorator has the providedIn metadata option, where you can specify the provider of the decorated service class with the root injector, or with the injector ...
  • Angular依赖注入(6)

    2018-07-03 08:37:05
    1.路由重定向 2.子路由 3.辅助路由 4.路由守卫 5.小结
  • 先建好一个服务,然后写好注入的内容 然后引用到app.module,提供器,providers中 通过注入器,注入到product1Component 的构造方法中 这里没有用到,Injectable装饰器,因为@Component中继承Injectable装饰器...
  • 文件 real.service.ts ...import { Injectable } from '@angular/core'; @Injectable() export class RealService { data = { name: 'real service' } constructor() { } changeData(value: string= 'ch...
  • angular依赖注入

    2018-08-16 16:54:07
    angular提供的服务方法,组件中无需单独引入,注入器根据组件的构造函数中声明的属性类型在整个应用程序中查找这个类型的实例并注入给属性 export class service{ constructor(public use:serve) { } } 如果应用...
  • 在使用angular 依赖注入的时候,一定不要丢了。 @Injectable() 只有声明@Injectable()这个的服务,才可以注入其他服务。(建议,所以有服务都写上)。
  • angular的几种依赖注入方式

    千次阅读 2018-09-19 11:14:00
    1、useClass 提供器的一种写法是这样的 providers: [...Service...], 其完全写法为 providers: [{provide:Service,useClass:Service}], provide提供token而实际使用的服务为useClass声明的服务,下面这种 ...
  • angular依赖注入和路由

    千次阅读 2016-11-25 13:55:31
    依赖注入:DI 指的是将一个功能注入到另一个不相干的模块里面去 ,让这个功能变成这个模块的一个部分,叫做依赖注入,类似在一个功能模块里面调用另一个模块(模块化) 只是模块化是在一个调用的里面主动去引入和...
  • angular学习(六)—— 依赖注入

    千次阅读 2017-01-05 18:08:55
    转载请写明来源地址:http://blog.csdn.net/lastsweetop/article/details/53409171依赖注入依赖注入(DI)是一种处理组件...使用依赖注入Angular中DI是无处不在的,你可以用它来定义一个组件,也可以提供module实现run
  • 依赖注入(Dependency Injection,简称DI)是像C#,java等典型的面向对象语言框架设计原则控制反转的一种典型的一种实现方式,angular把它引入到js中,介绍angular依赖注入的使用方式的文章很多, angular官方的文档,...
  • $dirty 表单有填写记录 $vaild 字段内容合法的 $invaild 字段内容非法的 $pristine 表单没有填写记录 Value 是一个简单的 javascript 对象,用于向控制器传递值(配置阶段) mainApp.value("defaultInput", 5);...
  • 使用过java进行开发的人肯定知道...我们的angular框架也实现了这种机制。 思考一下,如果对象需要获得其对依赖的控制权,有哪几种方式? 1.在对象内部自行创建依赖的实例 2.将依赖定义为全局的,然后通过全局...
  • 使用angularCLI命令行创建项目 创建组件 ng g component product1 创建服务放在shared的文件夹下面 ng g service shared/product 在shared/product.component.ts内进行操作 export class ProductService { ...

空空如也

1 2 3 4 5 ... 20
收藏数 12,961
精华内容 5,184
关键字:

angular的依赖注入