【问题标题】:Angular2 DI: Inject a new value on every injection?Angular2 DI:每次注入都注入一个新值?
【发布时间】:2018-03-16 08:12:07
【问题描述】:

我使用 Angular2 DI 在各种服务和组件中注入 Logger 类。每次请求Logger 时是否可以创建和注入一个新对象?我试过FactoryProvider;它实例化一个实例一次,然后在各处注入相同的实例。

为每个组件注册一个单独的Logger 提供程序可以解决这个问题,但这似乎有点过头了。

到目前为止,我没有找到更好的解决方案,但它会让我更方便。

示例

这是我超级方便的目标:

// logger.service.ts
export class Logger {
  static LoggerProvider: FactoryProvider = {
    provide: Logger,
    useFactory: () => {
      return new Logger();
    },
    /* Actually we need a LoggerService here, I omitted that. */
  };
  name: string;
  info(msg: string) { console.log('INFO', this.name, msg); }
}

// status.service.ts
@Injectable()
export class StatusService {
  constructor(private log: Logger) {
    this.log.name = 'StatusService';
    this.log.info('ctor'); // logs "INFO StatusService ctor"
  }
  test() {
    this.log.info('test'); // should log "INFO StatusService test"
  }
}

// home.component.ts
@Component({ /* ... */})
export class HomeComponent {
  constructor(
    private statusService: StatusService 
    private log: Logger,
  ) {
    // Got the same logger as the StatusService!!!
    this.log.name = 'HomeComponent';
    this.log.info('ctor'); // logs "INFO HomeComponent ctor"
    this.statusService.test(); // breaks and logs "INFO HomeComponent test"!!!
  }

}

这可以现在完成,但是,它需要更多样板:

// status.service.ts
@Injectable()
export class StatusService {
  private log: Logger;
  constructor(loggerService: LoggerService) {
    this.log = loggerService.createLogger('StatusService');
    this.log.info('ctor'); // logs "INFO StatusService ctor"
  }
  test() {
    this.log.info('test'); // definitely logs "INFO StatusService test"
  }
}

更多更新

好吧,我在 Angular2 问题跟踪器中进行挖掘,发现 this guy (#18015) 在那里他们争论解决这个问题的 DI 扩展以及它会影响整个 DI 机制的方式。似乎这将是真正的解决方案,但还没有结论。 (@bryan60 似乎是对的:目前 -- 5.0.0-rc.1 -- 不支持。)

【问题讨论】:

  • 请展示各种服务和组件中的Logger类的实际代码
  • 我相信您所描述的正是单独的provider 的用途。虽然在应用程序级别拥有一个提供程序会创建一个 Logger 实例,用于注入 Logger 的任何位置,但使用每个组件的提供程序同样会创建一个 Logger 每个组件的实例
  • 你想要的是每次注入一个新的记录器,通过 DI 做到这一点的唯一方法是在注入的任何地方提供它。这是 DI 的预期行为。您应该审查您的设计决策,以确定您真正需要它的原因,并看看您是否不能想出更好的东西。
  • 看看这是否有帮助:stackoverflow.com/a/38473200/2015408

标签: angular typescript dependency-injection


【解决方案1】:

如果一个服务需要多次实例化,而没有在providers每次都指定,则应该手动实例化:

...
{ provide: Logger, useValue: Logger }
...

...
constructor(@Inject(Logger) Logger: typeof Logger) {
  this.logger = new Logger();
}
...

如果Logger 类接受其他依赖项作为参数,则在这种情况下应手动提供。

为了自动注入它们,应该提供额外的服务,比如loggerFactory:

export function loggerFactory(foo: Foo, bar: Bar): Logger {
  return () => new Logger(foo, bar);
}

...
{ provide: loggerFactory, useFactory: loggerFactory, deps: [Foo, Bar] }
...

...
constructor(@Inject(loggerFactory) loggerFactory: () => Logger) {
  this.logger = loggerFactory();
}
...

【讨论】:

  • constructor(@Inject(Logger) Logger: typeof Logger) { this.logger = new Logger(); } 与仅constructor() { this.logger = new Logger() } 有何不同?
  • 这个支持DI(实际没有显示)。是的,这可以使用,但它不是那么超级方便。它非常接近于注入 LoggerService 并通过它进行实例化。
  • @yurzui 这是 DI。我们可以随时切换 Logger 类,这在测试中非常有用,或者如果代码不是第一方但应该作为第三方库发布。
  • 有一种方法instantiateResolved 可以使用 ReflectiveInjector 获取新实例,但在 StaticInjector 中没有等效方法。我创建了an issue 请求该功能。
  • @Simon_Weaver 任何类都可以用作接口。使用抽象类作为标记是一种常见的做法,请参阅stackoverflow.com/a/42422835/3731501。在这里有不同的Logger(抽象类)和LoggerImplementation(实际类)是没有意义的,因为它是唯一的实现,注入的Logger实际上是Logger类。我不确定为什么 OP 希望用 DI 来完成。可能有利于可扩展性或可测试性。
【解决方案2】:

实际上可以在每次请求时创建一个 Logger。想想ActivatedRoute,你会得到不同路由下不同组件的不同实例。

这个想法是创建一个自定义注入器。

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

@Component({
  selector: 'app-container-outlet',
  template: '<router-outlet></router-outlet>',
})
export class AppContainerOutletComponent { }


@Directive({
  selector: 'some-root-container',
})
export class AppContainerDirective implements OnInit {
  private content: ComponentRef<any> | null;
  constructor(
    private vcr: ViewContainerRef,
    private cdr: ChangeDetectorRef,
    private resolver: ComponentFactoryResolver,
    private router: Router
  ) { }

  ngOnInit() {
    const factory = this.resolver.resolveComponentFactory(AppContainerOutletComponent);
    const injector = new ContainerInjector(this.router, this.vcr.injector);
    this.content = this.vcr.createComponent(factory, this.vcr.length, injector);
    this.cdr.markForCheck();
  }
}

class ContainerInjector implements Injector {
  constructor(
    private router: Router,
    private parent: Injector
  ) {}

  get(token: any, notFoundValue?: any): any {
    if (token === Logger) {
      return new Logger(/* You can also pass some parameters to Logger */);
    }

    return this.parent.get(token, notFoundValue);
  }
}

那么就用some-root-container代替router-outlet(只需要替换root一个)。请注意,在 some-root-container 中实际上不需要 router-outlet。您只需要使用自定义注入器创建一个组件,并将其他所有内容都设为该组件的子组件。

然后,像往常一样注入 Logger(不要在任何地方提供)。您每次都会获得一个新的 Logger 实例。

缺点是不会调用Logger上的ngOnDestroy(如果实现的话)。

希望对您有所帮助。

【讨论】:

    【解决方案3】:

    惊讶于这个问题的浏览量如此之少。你可能想让 Logger 成为一个泛型类型,然后你可以用它做一些超级聪明的事情。

    例如。 Typescript 具有非常强大的功能(其中一些可能需要 2.9)

    您可以让您的记录器工厂返回一个泛型类型

    export function loggerFactory<LFT>(): (t) => Logger<LFT> 
    {
        return (t: LFT) => new Logger(t);
    }
    

    记录器本身可以使用非常强大的过滤功能,因此您可以创建成员(注意这都是在编译时)。

    type ObservablePropertyNames<T> = { [K in keyof T]: T[K] extends Observable<any> ? never : K }[keyof T];
    
    export class Logger<T>
    {
        constructor(public t: T){
            debugger;
        }
    
        logMember(member: keyof T)
        {
            console.log('value of member' + member, member);
        }
    
        // TODO: Unsubscribe afterwards!
        watch(member: keyof Pick<T, ObservablePropertyNames<T>>)
        {
           // member is an Observable<T>
           member.subscribe((v) => {
              console.log('member changed to ' + v);
           });
        }
    }
    

    要创建记录器,您必须使用包含组件名称作为通用参数类型的稍微笨拙的语法。

    @Inject(loggerFactory) loggerFactory:(t) => Logger<FeatureComponent>) {
    
        const logger = loggerFactory(this);
        logger.logMember('getPortal');  // only compiles if 'getPortal' exists
        logger.watch('layout');  // only compiles if 'layout' is Observable
    }
    

    这样,如果您的类发生更改,如果您正在记录不再存在的内容,它将无法编译。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-06-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-06
      • 2012-09-06
      相关资源
      最近更新 更多