【问题标题】:Angular 2 dynamic dependency injection based on @Input()基于@Input()的Angular 2动态依赖注入
【发布时间】:2017-05-12 23:25:59
【问题描述】:

假设我有一个 Angular 2 组件指令,我希望组件使用的注入依赖项由 @Input() 确定。

我想写<trendy-directive use="'serviceA'"> 之类的东西,让TrendyDirective 的实例使用serviceA,或者如果我指定的话,让它使用serviceB。 (这是我实际尝试做的过于简化的版本)

(如果您认为这是一个糟糕的想法,我愿意接受这种反馈,但请解释原因。)

这是一个如何实现我所想的示例。在此示例中,假设 ServiceA 和 ServiceB 是可注入的,它们都通过具有“superCoolFunction”来实现 iService。

@Component({
    selector: 'trendy-directive',
    ...
})
export class TrendyDirective implements OnInit {
    constructor(
        private serviceA: ServiceA,
        private serviceB: ServiceB){}

    private service: iService;
    @Input() use: string;

    ngOnInit() {
        switch (this.use){
            case: 'serviceA': this.service = this.serviceA; break;
            case: 'serviceB': this.service = this.serviceB; break;
            default: throw "There's no such thing as a " + this.use + '!';
        }
        this.service.superCoolFunction();
    }
}

我认为这在技术上可行,但必须有更好的方法来进行动态依赖注入。

【问题讨论】:

    标签: angular typescript dependency-injection


    【解决方案1】:

    是的

    // can be a service also for overriding and testing
    export const trendyServiceMap = {
      serviceA: ServiceA,
      serviceB: ServiceB
    }
    
    constructor(private injector: Injector) {}    
    ...
    ngOnInit() {
        if (trendyServiceMap.hasOwnProperty(this.use)) {
            this.service = this.injector.get<any>(trendyServiceMap[this.use]);
        } else {
            throw new Error(`There's no such thing as '${this.use}'`);
        }
    }
    

    【讨论】:

    • 自 4.0 get 已弃用。
    • 用类型调用泛型方法总是更好。我不确定这个签名是否会被删除。它在那里让用户知道使用泛型更好。无论如何,我更新了答案,感谢您的关注。通常我不会费心输入解决方案,而是专注于提供它的要点。
    • 谢谢你,你的回答对我帮助很大!
    • 对我来说,问题是对所有实现的依赖。我在bottom 的回答没有这个限制,但实现起来有点复杂。
    【解决方案2】:

    一般来说,Angular2 文档中描述了相同的方法:InjectorComponent

    @Component({
        providers: [Car, Engine, Tires, heroServiceProvider, Logger]
    })
    export class InjectorComponent {
         car: Car = this.injector.get(Car);
         heroService: HeroService = this.injector.get(HeroService);
         hero: Hero = this.heroService.getHeroes()[0];
    
         constructor(private injector: Injector) { }
    }
    

    您必须在构造函数中注入Injector,并在@Component 注释的providers 属性中列出所有服务。然后您可以injector.get(type),其中type 将从您的@Input 解析。根据文档,Service 在您提出要求之前不会被实际注入 (.get())。

    【讨论】:

    • 我试过这个,但得到“没有找到 ComponentA 的提供者”,而我确实在提供者列表中提到了 ComponentA。有什么想法吗?
    【解决方案3】:

    @angular/core 模块中有一个名为 Inject 的服务。使用@Inject,您可以实现另一种注入方式。但这只能在构造函数中完成。

    因此,您需要将组件的输入放入 @component 装饰器的输入数组中(不要在类中使用 @Input 装饰器),然后在构造函数中注入该输入变量。

    【讨论】:

    • 我也会投票支持注入:您是否多次在其父组件中使用带有不同服务的新潮指令或动态切换它?如果没有,父组件可以简单地提供正确的服务,您不需要实现复杂的逻辑。 @Component({ selector: 'i-use-trendy-directive', providers: [ {provide: MyServiceInterfase, useClass: MyServiceImpl } ] } 然后constructor(private myService : MyServiceInterface) {}
    • 就我而言,我会在多个不同的地方使用该指令并提供不同的服务。不过还是谢谢。
    • @Vineet 'DEVIN' Dev 这很有趣。装饰器中有关输入数组的文档似乎很少。听起来@Component 装饰器中的输入在组件生命周期中比@Input 装饰器指定的输入更早得到解决。你知道什么时候?
    • 我刚刚测试过,不幸的是,来自 @Component 装饰器的输入尚未在构造函数中填充(但它们在我们到达 ngOnInit 时已填充,就像使用 @Input 声明的一样)。
    • 您是否尝试过直接使用服务而不是尝试先注入?就像,只需使用@input 从父组件获取服务变量的引用,然后直接使用它而不进行注入?我也尝试了我回答的方法,但似乎父组件在子组件之后初始化。
    【解决方案4】:

    我想进一步回答Estus Flask 并创建一个导入服务的逻辑,而不必将名称声明为数组对象。

    基本上,我们只需要传入服务的pathname 就可以了,其余的几乎一样。

    public _dynamicService: any;
    
    dynamicDI(service_path, service_name){
        import(service_path).then(s => {
    
          this._dynamicService = this.injector.get<any>(s['service_name']);
    
        })
    }
    

    现在,您可以访问dynamicService 中的函数,如下所示:

    (假设我们需要的服务中有一个 http observable fn)

    this._dynamicService['your_function_name']().subscribe(res=> { console.log(res) } );
    

    【讨论】:

      【解决方案5】:

      这里有一个方法,有点复杂,但工作起来就像一个魅力!

      在共享模块和多个自定义实现中设置默认搜索服务。 并且无需明确引用所有可能的实现。

      接口和默认实现

      export interface ISearch {
          searchByTerm(textInput: string);
      }
      
      export class DefaultSearch implements ISearch {
          searchByTerm(textInput: string) { console.log("default search by term"); }
      }
      

      使用 InjectionToken 创建服务实现列表

       // Keep list of token, provider will give a implementation for each of them
       export const SearchServiceTokens: Map<string, InjectionToken<ISearch>> = new Map();
       // Add File service implementation token
       SearchServiceTokens.set('default', new InjectionToken<ISearch>('default'));
      

      默认服务实现的提供者

         providers: [
            ...
            // Default implementation service
            {
               provide: SearchServiceTokens.get('default'),
               useClass: DefaultSearch
            }
         ]
      

      自定义实现(可能在另一个模块上)

      export class Component1Search implements ISearch {
          searchByTerm(textInput: string) { console.log("component1 search by term"); }
      }
      

      为自定义实现添加令牌

      SearchServiceTokens.set('component1', new InjectionToken<ISearch>('component1'));
      

      添加提供者

         providers: [
            ...
            // Other implementation service
            {
               provide: SearchServiceTokens.get('component1'),
               useClass: Component1Search
            }
         ]
      

      最后,在你的组件中

          @Input() useService;
          searchService: ISearch;
      
          constructor(private injector: Injector) {
             // Use default if none provided
             let serviceToUse = 'default';
             if (null !== this.useService) { serviceToUse = this.useService; }
             this.searchService = this.injector.get(SearchServiceTokens.get(serviceToUse));
          }
      

      【讨论】:

        猜你喜欢
        • 2016-02-15
        • 1970-01-01
        • 2020-12-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-08-19
        • 1970-01-01
        相关资源
        最近更新 更多