Angular2 DI:每次注射都注入一个新值?

时间:2017-10-04 20:01:29

标签: angular typescript dependency-injection

我使用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 - 这是不受支持的。)

3 个答案:

答案 0 :(得分:2)

如果服务需要多次实例化而不是每次都在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();
}
...

答案 1 :(得分: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-one)。请注意,实际上某些根容器中不需要路由器插座。您只需要使用自定义注入器创建一个组件,并使其他所有组件成为该组件的子项。

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

缺点是不会调用记录器上的ngOnDestroy(如果已实现)。

希望有所帮助。

答案 2 :(得分:1)

惊奇这个问题的观点很少。您可能希望将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
}

这样,如果您的班级发生变化,如果您记录的内容不再存在,它将导致编译失败。