我使用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 - 这是不受支持的。)
答案 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
}
这样,如果您的班级发生变化,如果您记录的内容不再存在,它将导致编译失败。