MST
星途 面试题库

面试题:TypeScript装饰器在Angular组件中的依赖注入原理

在Angular框架里,TypeScript装饰器对于组件的依赖注入起到关键作用。请详细解释装饰器是如何实现依赖注入机制的,以及在依赖注入过程中可能遇到的循环依赖问题,如何通过装饰器相关知识去解决。
18.3万 热度难度
前端开发TypeScript

知识考点

AI 面试

面试题答案

一键面试

装饰器实现依赖注入机制

  1. 装饰器基础:在TypeScript中,装饰器是一种特殊的声明,它可以附加到类声明、方法、访问器、属性或参数上。在Angular中,装饰器用于为类添加元数据。例如,@Component@Injectable等装饰器。
  2. @Injectable装饰器
    • 当一个类被@Injectable装饰时,它表示这个类可以被依赖注入系统创建和管理。这主要是为了让Angular知道该类可能有依赖项需要注入。
    • 例如:
    import { Injectable } from '@angular/core';
    
    @Injectable()
    class Logger {
      log(message: string) {
        console.log(message);
      }
    }
    
  3. @Component装饰器与依赖注入
    • 一个@Component装饰的组件类,通过构造函数来声明它所依赖的服务。Angular的依赖注入系统会在创建组件实例时,查找并注入这些依赖。
    • 例如:
    import { Component } from '@angular/core';
    import { Logger } from './logger.service';
    
    @Component({
      selector: 'app - my - component',
      templateUrl: './my - component.html'
    })
    class MyComponent {
      constructor(private logger: Logger) {}
    
      ngOnInit() {
        this.logger.log('Component initialized');
      }
    }
    
    • 这里MyComponent依赖Logger服务,依赖注入系统会查找Logger的提供者,并创建一个实例注入到MyComponent的构造函数中。
  4. 提供者(Providers)
    • 在Angular中,依赖的注册是通过提供者(providers)来完成的。可以在模块(@NgModuleproviders数组)、组件(@Componentproviders数组)等地方定义提供者。
    • 例如,在模块中注册Logger服务:
    import { NgModule } from '@angular/core';
    import { Logger } from './logger.service';
    
    @NgModule({
      providers: [Logger]
    })
    class AppModule {}
    
    • 当依赖注入系统查找Logger服务时,会根据这些提供者的配置来创建或获取实例。

循环依赖问题及解决

  1. 循环依赖问题描述
    • 当两个或多个类相互依赖,形成一个循环时,就会出现循环依赖问题。例如,类A依赖类B,而类B又依赖类A。
    • 例如:
    import { Injectable } from '@angular/core';
    
    @Injectable()
    class A {
      constructor(private b: B) {}
    }
    
    @Injectable()
    class B {
      constructor(private a: A) {}
    }
    
    • 在这种情况下,Angular的依赖注入系统在创建A的实例时,需要先创建B的实例,而创建B的实例又需要先创建A的实例,导致无限循环。
  2. 通过装饰器相关知识解决
    • 延迟注入(Lazy Injection)
      • 使用@Inject装饰器手动注入依赖,而不是在构造函数中直接声明。@Inject装饰器可以用于方法参数等。例如:
      import { Injectable, Inject } from '@angular/core';
      
      @Injectable()
      class A {
        constructor(@Inject('B') private bFactory: () => B) {}
      
        useB() {
          const b = this.bFactory();
          b.doSomething();
        }
      }
      
      @Injectable()
      class B {
        constructor(private a: A) {}
      
        doSomething() {
          console.log('B is doing something');
        }
      }
      
      • 这里通过将B的注入包装成一个工厂函数(() => B),使得在实际使用B时才创建B的实例,避免了循环依赖。在模块中注册时,需要使用useFactory来提供B
      import { NgModule } from '@angular/core';
      import { A, B } from './services';
      
      @NgModule({
        providers: [
          A,
          {
            provide: 'B',
            useFactory: () => new B(new A(() => new B()))
          }
        ]
      })
      class AppModule {}
      
    • 使用@Optional装饰器
      • 如果某个依赖不是必须的,可以使用@Optional装饰器。例如:
      import { Injectable, Optional } from '@angular/core';
      
      @Injectable()
      class A {
        constructor(@Optional() private b: B) {}
      }
      
      @Injectable()
      class B {
        constructor(private a: A) {}
      }
      
      • 这样在创建A实例时,如果B因为循环依赖无法创建,Ab属性会被设置为null(或undefined),从而避免了循环依赖导致的错误。但这种方式适用于依赖可以为null的场景。