angular - Angular2 DI: Injicera ett nytt värde vid varje injektion?
Jag använder Angular2 DI för att injicera en Logger-klass i olika tjänster och komponenter. Är det möjligt att skapa och injicera ett nytt objekt varje gång en Logger efterfrågas? Jag provade FactoryProvider; det instansierar en enda instans en gång och injicerar sedan samma instans överallt.
Att registrera en separat Logger-leverantör för varje komponent skulle lösa detta, men det verkar vara överdrivet.
Hittills har jag inte hittat någon bättre lösning, men det skulle göra saker mycket mer bekvämt för mig.
EXEMPEL
Detta är mitt superbekväma mål:
// 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"!!!
}
}
Detta skulle kunna göras nu, men det behöver lite mer standardkod:
// 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"
}
}
MER UPPDATERING
Tja, jag grävde i Angular2:s ärendespårare och hittade den här personen (#18015) där de diskuterar en DI-förlängning som löser detta problem och hur det skulle påverka hela DI-mekanismen. Det verkar som att detta skulle vara den verkliga lösningen men det finns ingen slutsats än. (@bryan60 verkar ha rätt: för närvarande -- 5.0.0-rc.1 -- stöds detta INTE.)
Svar #1
Det är faktiskt möjligt att skapa en Logger varje gång den efterfrågas. Tänk bara på ActivatedRoute, du kommer att få olika instanser för olika komponenter som är under olika rutter.
Idén är att skapa en anpassad injektor.
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);
}
}
Använd då bara some-root-container istället för router-outlet (behöver bara ersätta den rot som finns). Observera att router-outlet egentligen inte krävs i some-root-container. Du behöver bara skapa en komponent med en egen injector och göra allt annat till barn av komponenten.
Sedan, injicera bara Logger som vanligt (ge det inte någonstans). Du kommer att få en ny Logger-instans varje gång.
Bristen är att ngOnDestroy på Logger (om det är implementerat) inte kommer att anropas.
Hoppas det hjälper.
Svar #2
Om en tjänst behöver instansieras flera gånger utan att specificera den i providers varje gång, bör den instansieras manuellt:
...
{ provide: Logger, useValue: Logger }
...
...
constructor(@Inject(Logger) Logger: typeof Logger) {
this.logger = new Logger();
}
...
Om Logger-klassen accepterar andra beroenden som argument, bör de tillhandahållas manuellt i detta fall.
För att injicera dem automatiskt bör ytterligare tjänst tillhandahållas, som 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();
}
...
