The point of this story is to use typescript decorators to achieve effect like this :



export class AppComponent {

@Logger()

private logger: ILogger;

}

which uses Angular DI system to inject logger instead of writing boilerplate like this:

export class AppComponent {

private logger: ILogger; constructor(private loggerService: LoggerService) {

this.logger = loggerService.createLogger('AppComponent');

} }

it gets even worse if we use inheritance passing loggers to base class

Imagine you have logger service that needs additional dependencies to create different type of loggers

class NetworkLogger {

constructor(private prefix: string, private http: HttpClient) {} public debug(...args: Array<any>): void {

this.http.post(...).subscribe();

} } @Injectable()

export class LoggerService {

constructor(private http: HttpClient) {}



public createLogger(prefix: string): ILogger {

return new NetworkLogger(prefix, this.http);

//or another logger environment-dependent like new ConsoleLogger etc ... }

}

2. Create logger decorator along with the LoggerFactory.

LoggerFactory is the main hack : a singleton instance for holding globally DI services to be accessible from decorators.

@Injectable()

export class LoggerFactory {

public static loggerService: LoggerService;



constructor(loggerService: LoggerService) {

LoggerFactory.loggerService = loggerService;

}

}



export function Logger(): any {



return function(proto: any, memberName: string): any {

Object.defineProperty(proto, memberName, {

get(): ILogger {

if (!this.__logger__) {

this.__logger__ = LoggerFactory.loggerService.createLogger(proto.constructor.name);

}

return this.__logger__;

}

});

} as any;

}

Two more hacks are being done there :

a) Object.defineProperty overrides the decorated member

b)the overrided member is accessible by getter so every time it’s used the getter is invoked — some caching is used here saving the logger instance in ‘this’ which points to component instance.

The ‘any’ probably could be replaced by ILogger but it works either way fooling typescript because the property is defined in component with ILogger either way ;)

3. Create logger module and create services instances when the module is being created

@NgModule({

declarations: [],

imports: [CommonModule],

providers: [LoggerFactory, LoggerService]

})

export class LoggerModule {

constructor(private loggerFactory: LoggerFactory, private loggerService: LoggerService) {}

}

4. Import logger module to your main module(order matters so import it as the first thing)

@NgModule({

imports: [

LoggerModule,

...modules,



],

...

})

export class CoreModule {

That’s it ;)

I was inspired by awesome ngxs library. Be sure to check it out too!