美文网首页
使用Angular的依赖注入

使用Angular的依赖注入

作者: 飞凡的陀螺 | 来源:发表于2019-06-14 23:02 被阅读0次

    首先介绍 Angular 中依赖注入的相关概念:

    Service 服务

    Service 的表现形式是一个class,可以用来在组件中复用
    比如 Http 请求获取数据,日志处理,验证用户输入等都写成Service,供组件使用。

    import { Injectable } from '@angular/core';
    // 在 Angular 中,要把一个类定义为服务,就要用 `@Injectable` 装饰器来提供元数据
    
    @Injectable({
      // we declare that this service should be created
      // by the root application injector.
      providedIn: 'root',
    })
    export class LoggerService {
      warn(msg) { 
        return console.warn(msg); 
      }
    }
    
    

    Injector 注入器

    一般不用自己手动注入,Angular 会在启动过程中为你创建全应用级注入器以及所需的其它注入器。

    Provider 提供商

    是一个对象,告诉 Injector 应该如何获取或创建依赖。

    打开Angular看下面的代码片段 app.module.ts

    @NgModule({
      declarations: [
        ....
      ],
      imports: [
        ....
      ],
      // providers 告诉 Angular 应用哪些对象需要依赖注入
      // providers 是个数组,每一项都是provider
      providers: [
        //  简写,等价 {provider: LoggerService, useClass: LoggerService}
        LoggerService,
        { 
           provide: RequestCache, 
           useClass: RequestCacheWithLocalStorage 
        },
        {
          provide: HTTP_INTERCEPTORS,
          useClass: UrlInterceptor,
          multi: true
        },
      ],
      bootstrap: [AppComponent]
    })
    

    DI token(令牌)

    provide 属性提供了provider 的token,也叫令牌,表示在构造函数中指定的类型。
    也就是说,当constructor(private productService: ProductService){...} 指定了ProductService,就会去找token是productService的provider。

    image.png
    Provider 的几种写法
    1. useClass
      providers: [{provide: ProductService, useClass: ProductService} ]
      的简写是
      providers: [ ProductService ]
      useClass属性指定实例化方式,表示是 new 一个 ProductService,如果userClass" AnotherProductService 真正实例化的就是 AnotherProductService。
    2. userFactory
      除了useClass写法,还可以使用 userFactory 工厂方法,这个方法返回的实例作为构造函数中productService参数的内容。
      providers: [{provide: ProductService, userFactory: () => {}} ]
      这样可以根据条件具体实例化某对象,更加灵活
    providers: [{
      provide: ProductService, 
      userFactory: () => {
        let logger = new LoggerService();
        let dev = Math.random() > 0.5;
        if (dev) {
          return new ProductService(logger);
        } else {
          return new AnotherProductService(logger);
       }
      }
    }, LoggerService ]
    

    上面的写法有个弊端LoggerService和ProductService耦合太强
    进一步优化,利用deps参数,指工厂声明所依赖的参数。

    providers: [{
        provide: ProductService, 
        userFactory: (logger: LoggerService) => {
          let dev = Math.random() > 0.5;
          if (dev) {
            return new ProductService(logger);
          } else {
            return new AnotherProductService(logger);
         }
        },
        deps: [ LoggerService ]
      }, 
      LoggerService
    ]
    

    再次优化,定义第三个提供器,token是IS_DEV_ENV,值是具体的false

    providers: [{
        provide: ProductService, 
        // 注入的 顺序和deps对应
        userFactory: (logger: LoggerService, isDev) => {
          if (isDev) {
            return new ProductService(logger);
          } else {
            return new AnotherProductService(logger);
         }
        },
        deps: [ LoggerService, 'IS_DEV_ENV' ]
      }, 
      LoggerService,
      {provide: 'IS_DEV_ENV', useValue: false}
    ]
    

    一般来说可以创建一个类型为对象的提供器供注入

    providers: [{
        provide: ProductService, 
        // 注入的 顺序和deps对应
        userFactory: (logger: LoggerService, appConfig) => {
          if (appConfig.isDev) {
            return new ProductService(logger);
          } else {
            return new AnotherProductService(logger);
         }
        },
        deps: [ LoggerService, 'APP_CONFIG' ]
      }, 
      LoggerService,
      { provide: 'APP_CONFIG', useValue: {isDev: false }}
    ]
    

    提供器的作用域

    image.png

    provide声明在App模块中,则对所有模块可见

    provide声明在某组件中,只对该组件及其子组件可见。其他组件不可以注入。
    当声明在组件和模块中的提供器具有相同的token时,声明在组件中的提供器会覆盖模块中的那个提供器。

    @Injectable 装饰器

    表示FooService可以通过构造函数注入其他服务
    举个例子,如果注释掉

    // @Injectable({
    //   providedIn: 'root'
    // })
    

    就会报错

    image.png
    image.png

    为什么在组件中没有写@Injectable也能直接注入service?
    我们知道定义组件要写@Component装饰器,定义管道要写@Pipe装饰器,他们都是Injectable的子类。
    同时Component又是Directive的子类,所以所有的组件都是指令。

    手动注入

    import { Component, OnInit, Injector } from '@angular/core';
    import { LoggerService } from '../_service/logger.service';
    
    @Component({
      selector: 'app-di',
      templateUrl: './di.component.html',
      styleUrls: ['./di.component.styl']
    })
    export class DIComponent implements OnInit {
      logger: LoggerService;
      // 手动注入
      constructor(
        private injector: Injector
      ) {
        this.logger = injector.get(LoggerService);
      }
    
      ngOnInit() {
        this.logger.log()
      }
    }
    
    

    相关文章

      网友评论

          本文标题:使用Angular的依赖注入

          本文链接:https://www.haomeiwen.com/subject/srtasqtx.html