Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Dependency Injection in Angular

Dependency Injection in Angular

Marko Stanimirović

October 07, 2022
Tweet

More Decks by Marko Stanimirović

Other Decks in Programming

Transcript

  1. Marko Stanimirović @MarkoStDev ★ Sr. Frontend Engineer at JobCloud ★

    NgRx Core Team Member ★ Angular Belgrade Organizer ★ Hobby Musician
  2. Marko Stanimirović @MarkoStDev ★ Sr. Frontend Engineer at JobCloud ★

    NgRx Core Team Member ★ Angular Belgrade Organizer ★ Hobby Musician ★ Google Developer Expert in Angular
  3. “DI is a design pattern that makes an object independent

    of its dependencies by decoupling its usage from its creation.”
  4. class UsersService { private usersResource = new HttpUsersResource(); private logger

    = new ServerLogger(); } Tightly coupled Without Dependency Injection
  5. Providing Service at the Root Level - #1 Way @Injectable({

    providedIn: 'root' }) class UsersService { ./ ... }
  6. Providing Service at the Root Level - #1 Way @Injectable({

    providedIn: 'root' }) class UsersService { ./ ... } @Component(** **. */) class UserDetailsComponent { constructor(private usersService: UsersService) {} }
  7. Providing Service at the Root Level - #2 Way @Injectable()

    class UsersService { ./ ... } bootstrapApplication(AppComponent, { providers: [UsersService], });
  8. Providing Service at the Route Level const userRoutes: Route[] =

    [ { path: '', component: UsersShellComponent, children: [ { path: '', component: UserListComponent }, { path: ':id', component: UserDetailsComponent }, ], }, ];
  9. Providing Service at the Route Level const userRoutes: Route[] =

    [ { path: '', component: UsersShellComponent, providers: [UsersService], children: [ { path: '', component: UserListComponent }, { path: ':id', component: UserDetailsComponent }, ], }, ];
  10. Providing Service at the Route Level const userRoutes: Route[] =

    [ { path: '', component: UsersShellComponent, providers: [UsersService], children: [ { path: '', component: UserListComponent }, { path: ':id', component: UserDetailsComponent }, ], }, ];
  11. Providing Service at the Component Level @Component({ selector: 'app-user-details', standalone:

    true, template: ` <app-user-form [user]="usersService.activeUser"> ./app-user-form> <ng-content>./ng-content> `, imports: [UserFormComponent], }) class UserDetailsComponent { constructor(readonly usersService: UsersService) {} }
  12. Providing Service at the Component Level @Component({ selector: 'app-user-details', standalone:

    true, template: ` <app-user-form [user]="usersService.activeUser"> ./app-user-form> <ng-content>./ng-content> `, providers: [UsersService], imports: [UserFormComponent], }) class UserDetailsComponent { constructor(readonly usersService: UsersService) {} }
  13. Providing Service at the Component Level @Component({ selector: 'app-user-details', standalone:

    true, template: ` <app-user-form [user]="usersService.activeUser"> ./app-user-form> <ng-content>./ng-content> `, providers: [UsersService], imports: [UserFormComponent], }) class UserDetailsComponent { constructor(readonly usersService: UsersService) {} }
  14. Providing Service at the Component Level @Component({ selector: 'app-user-details', standalone:

    true, template: ` <app-user-form [user]="usersService.activeUser"> ./app-user-form> <ng-content>./ng-content> `, viewProviders: [UsersService], imports: [UserFormComponent], }) class UserDetailsComponent { constructor(readonly usersService: UsersService) {} }
  15. Providing Service at the Component Level @Component({ selector: 'app-user-details', standalone:

    true, template: ` <app-user-form [user]="usersService.activeUser"> ./app-user-form> <ng-content>./ng-content> `, viewProviders: [UsersService], imports: [UserFormComponent], }) class UserDetailsComponent { constructor(readonly usersService: UsersService) {} }
  16. Element Injector viewProviders @Inject(UsersService) providers Root Injector providers Element Injector

    viewProviders providers AppComponent Route Injector providers UserDetailsComponent
  17. Element Injector viewProviders @Inject(UsersService) providers Root Injector providers Element Injector

    viewProviders providers AppComponent Route Injector providers UserDetailsComponent
  18. Optional Modifier @Component(** **. */) class UserDetailsComponent { constructor(@Optional() private

    usersService.: UsersService) {} } Element Injector viewProviders providers UserDetailsComponent @Optional() @Inject(UsersService)
  19. Optional Modifier @Component(** **. */) class UserDetailsComponent { constructor(@Optional() private

    usersService.: UsersService) {} } Element Injector viewProviders providers UserDetailsComponent Element Injector viewProviders providers AppComponent Route Injector providers Root Injector providers @Optional() @Inject(UsersService) null
  20. SkipSelf Modifier @Component(** **. */) class UserDetailsComponent { constructor(@SkipSelf() private

    usersService: UsersService) {} } Element Injector viewProviders providers UserDetailsComponent Element Injector viewProviders providers AppComponent Route Injector providers Root Injector providers @SkipSelf() @Inject(UsersService) search starts here
  21. Self Modifier @Component(** **. */) class UserDetailsComponent { constructor(@Self() private

    usersService: UsersService) {} } Element Injector viewProviders providers UserDetailsComponent @Self() @Inject(UsersService)
  22. Self Modifier @Component(** **. */) class UserDetailsComponent { constructor(@Self() private

    usersService: UsersService) {} } Element Injector viewProviders providers UserDetailsComponent @Self() @Inject(UsersService)
  23. Host Modifier @Component(** **. */) class UserDetailsComponent { constructor(@Host() private

    usersService: UsersService) {} } Element Injector viewProviders providers UserDetailsComponent @Host() @Inject(UsersService)
  24. Host Modifier @Component(** **. */) class UserDetailsComponent { constructor(@Host() private

    usersService: UsersService) {} } Element Injector viewProviders providers UserDetailsComponent @Host() @Inject(UsersService)
  25. Host Modifier @Component(** **. */) class UserDetailsComponent { constructor(@Host() private

    usersService: UsersService) {} } Element Injector viewProviders providers UserDetailsComponent @Host() @Inject(UsersService) Element Injector viewProviders providers AppComponent
  26. Host Modifier @Component(** **. */) class UserDetailsComponent { constructor(@Host() private

    usersService: UsersService) {} } Element Injector viewProviders providers UserDetailsComponent @Host() @Inject(UsersService) Element Injector viewProviders providers AppComponent
  27. Providing Injection Token const APP_THEME = new InjectionToken<'light' | 'dark'>('Application

    Theme'); bootstrapApplication(AppComponent, { providers: [ ], }); providing at the root level
  28. Providing Injection Token const APP_THEME = new InjectionToken<'light' | 'dark'>('Application

    Theme'); bootstrapApplication(AppComponent, { providers: [APP_THEME ], });
  29. Providing Injection Token const APP_THEME = new InjectionToken<'light' | 'dark'>('Application

    Theme'); bootstrapApplication(AppComponent, { providers: [{ }], });
  30. Providing Injection Token const APP_THEME = new InjectionToken<'light' | 'dark'>('Application

    Theme'); bootstrapApplication(AppComponent, { providers: [{ provide: APP_THEME }], });
  31. Providing Injection Token const APP_THEME = new InjectionToken<'light' | 'dark'>('Application

    Theme'); bootstrapApplication(AppComponent, { providers: [{ provide: APP_THEME, useValue: 'light' }], });
  32. Providing Injection Token const APP_THEME = new InjectionToken<'light' | 'dark'>('Application

    Theme'); bootstrapApplication(AppComponent, { providers: [{ provide: APP_THEME, useFactory: appThemeFactory }], }); function appThemeFactory() { return localStorage.getItem('theme') .? 'light'; }
  33. Injecting Injection Token const APP_THEME = new InjectionToken<'light' | 'dark'>('Application

    Theme'); bootstrapApplication(AppComponent, { providers: [{ provide: APP_THEME, useFactory: appThemeFactory }], }); @Component(** **. */) class SideMenuComponent { constructor( ) {} }
  34. Injecting Injection Token const APP_THEME = new InjectionToken<'light' | 'dark'>('Application

    Theme'); bootstrapApplication(AppComponent, { providers: [{ provide: APP_THEME, useFactory: appThemeFactory }], }); @Component(** **. */) class SideMenuComponent { constructor(@Inject(APP_THEME) theme: 'light' | 'dark') {} }
  35. Providing Injection Token - Tree Shakeable Way const APP_THEME =

    new InjectionToken('Application Theme', { });
  36. Providing Injection Token - Tree Shakeable Way const APP_THEME =

    new InjectionToken('Application Theme', { providedIn: 'root', factory: () .> localStorage.getItem('theme') .? 'light', });
  37. Providing Injection Token - Tree Shakeable Way const APP_THEME =

    new InjectionToken('Application Theme', { providedIn: 'root', factory: () .> localStorage.getItem('theme') .? 'light', }); @Component(** **. */) class SideMenuComponent { constructor(@Inject(APP_THEME) theme: 'light' | 'dark') {} }
  38. Constructor-Based DI vs inject @Component({ ** **. */ }) export

    class UserDetailsComponent { constructor( private usersService: UsersService, private route: ActivatedRoute ) {} }
  39. Constructor-Based DI vs inject @Component({ ** **. */ }) export

    class UserDetailsComponent { constructor( private usersService: UsersService, private route: ActivatedRoute ) {} } @Component({ ** **. */ }) export class UserDetailsComponent { private usersService = inject(UsersService); private route = inject(ActivatedRoute); }
  40. Constructor-Based DI vs inject @Component({ ** **. */ }) export

    class UserDetailsComponent { constructor( private usersService: UsersService, private route: ActivatedRoute ) {} } @Component({ ** **. */ }) export class UserDetailsComponent { private usersService = inject(UsersService); private route = inject(ActivatedRoute); } token
  41. Resolution Modifiers with inject @Component({ ** **. */ }) export

    class UserDetailsComponent { private usersService = inject(UsersService, { self: true, optional: true }); }
  42. Resolution Modifiers with inject @Component({ ** **. */ }) export

    class UserDetailsComponent { private usersService = inject(UsersService, { self: true, optional: true }); } config
  43. Factories with inject const ID_ROUTE_PARAM = new InjectionToken('ID Route Parameter

    Observable', { factory() { const route = inject(ActivatedRoute); }, });
  44. Factories with inject const ID_ROUTE_PARAM = new InjectionToken('ID Route Parameter

    Observable', { factory() { const route = inject(ActivatedRoute); return route.params.pipe( map((params) .> params['id']), filter(Boolean), distinctUntilChanged() ); }, });
  45. Base Classes with Constructor-Based DI abstract class EntityResource<T extends {

    id: string }> { protected constructor( protected http: HttpClient, protected apiUrl: string, protected uri: string ) {} ./ ... }
  46. Base Classes with Constructor-Based DI abstract class EntityResource<T extends {

    id: string }> { protected constructor( protected http: HttpClient, protected apiUrl: string, protected uri: string ) {} ./ ... } @Injectable({ providedIn: 'root' }) class UsersResource extends EntityResource<User> { }
  47. Base Classes with Constructor-Based DI abstract class EntityResource<T extends {

    id: string }> { protected constructor( protected http: HttpClient, protected apiUrl: string, protected uri: string ) {} ./ ... } @Injectable({ providedIn: 'root' }) class UsersResource extends EntityResource<User> { constructor( http: HttpClient, @Inject(API_URL) apiUrl: string ) { super(http, apiUrl, 'users'); } }
  48. Base Classes with inject abstract class EntityResource<T extends { id:

    string }> { protected http = inject(HttpClient); protected apiUrl = inject(API_URL); protected constructor(protected uri: string) {} ./ ... }
  49. Base Classes with inject abstract class EntityResource<T extends { id:

    string }> { protected http = inject(HttpClient); protected apiUrl = inject(API_URL); protected constructor(protected uri: string) {} ./ ... } @Injectable({ providedIn: 'root' }) class UsersResource extends EntityResource<User> { constructor() { super('users'); } }
  50. Providing Logger Based on Environment @Injectable({ providedIn: 'root', }) abstract

    class Logger { abstract log(message: string): void; abstract error(message: string): void; }
  51. Providing Logger Based on Environment @Injectable({ providedIn: 'root', }) abstract

    class Logger { abstract log(message: string): void; abstract error(message: string): void; } @Injectable({ providedIn: 'root' }) class ServerLogger extends Logger { ./ ... } @Injectable({ providedIn: 'root' }) class ConsoleLogger extends Logger { ./ ... }
  52. Providing Logger Based on Environment @Injectable({ providedIn: 'root', useFactory: ()

    .> environment.production ? inject(ServerLogger) : inject(ConsoleLogger), }) abstract class Logger { abstract log(message: string): void; abstract error(message: string): void; } @Injectable({ providedIn: 'root' }) class ServerLogger extends Logger { ./ ... } @Injectable({ providedIn: 'root' }) class ConsoleLogger extends Logger { ./ ... }
  53. Providing Logger Based on Environment @Injectable({ providedIn: 'root', useFactory: ()

    .> environment.production ? inject(ServerLogger) : inject(ConsoleLogger), }) abstract class Logger { abstract log(message: string): void; abstract error(message: string): void; } @Injectable({ providedIn: 'root' }) class ServerLogger extends Logger { ./ ... } @Injectable({ providedIn: 'root' }) class ConsoleLogger extends Logger { ./ ... } @Injectable({ providedIn: 'root' }) class UsersService { constructor(private readonly logger: Logger) {} }
  54. tick-scheduler.ts @Injectable({ providedIn: 'root', }) abstract class TickScheduler { abstract

    schedule(): void; } @Injectable({ providedIn: 'root' }) class AnimationFrameTickScheduler extends TickScheduler { ./ ... } class NoopTickScheduler extends TickScheduler { schedule(): void {} }
  55. tick-scheduler.ts @Injectable({ providedIn: 'root', useFactory: () .> { const zone

    = inject(NgZone); return isNgZone(zone) ? new NoopTickScheduler() : inject(AnimationFrameTickScheduler); }, }) abstract class TickScheduler { abstract schedule(): void; } @Injectable({ providedIn: 'root' }) class AnimationFrameTickScheduler extends TickScheduler { ./ ... } class NoopTickScheduler extends TickScheduler { schedule(): void {} }
  56. tick-scheduler.ts @Injectable({ providedIn: 'root', useFactory: () .> { const zone

    = inject(NgZone); return isNgZone(zone) ? new NoopTickScheduler() : inject(AnimationFrameTickScheduler); }, }) abstract class TickScheduler { abstract schedule(): void; } @Injectable({ providedIn: 'root' }) class AnimationFrameTickScheduler extends TickScheduler { ./ ... } class NoopTickScheduler extends TickScheduler { schedule(): void {} }
  57. Scan project files Find all classes with Angular-specific decorators Preserve

    information about constructor parameter types @Injectable() @Component() @Directive() @Pipe()
  58. Scan project files Find all classes with Angular-specific decorators Preserve

    information about constructor parameter types Transpile TypeScript to JavaScript @Injectable() @Component() @Directive() @Pipe()
  59. users.service.ts @Injectable({ providedIn: 'root' }) class UsersService { constructor( private

    usersResource: UsersResource, private logger: Logger ) {} } class UsersService { constructor(usersResource, logger) { this.usersResource = usersResource; this.logger = logger; } } UsersService.ɵfac = function UsersService_Factory() { return new UsersService( ɵɵinject(UsersResource), ɵɵinject(Logger) ); }; UsersService.ɵprov = ɵɵdefineInjectable({ token: UsersService, factory: UsersService.ɵfac, providedIn: 'root' }); Compiled Output
  60. users.service.ts @Injectable({ providedIn: 'root' }) class UsersService { constructor( private

    usersResource: UsersResource, private logger: Logger ) {} } class UsersService { constructor(usersResource, logger) { this.usersResource = usersResource; this.logger = logger; } } UsersService.ɵfac = function UsersService_Factory() { return new UsersService( ɵɵinject(UsersResource), ɵɵinject(Logger) ); }; UsersService.ɵprov = ɵɵdefineInjectable({ token: UsersService, factory: UsersService.ɵfac, providedIn: 'root' }); Compiled Output
  61. users.service.ts @Injectable({ providedIn: 'root' }) class UsersService { constructor( private

    usersResource: UsersResource, private logger: Logger ) {} } class UsersService { constructor(usersResource, logger) { this.usersResource = usersResource; this.logger = logger; } } UsersService.ɵfac = function UsersService_Factory() { return new UsersService( ɵɵinject(UsersResource), ɵɵinject(Logger) ); }; UsersService.ɵprov = ɵɵdefineInjectable({ token: UsersService, factory: UsersService.ɵfac, providedIn: 'root' }); Compiled Output
  62. users.service.ts @Injectable({ providedIn: 'root' }) class UsersService { constructor( private

    usersResource: UsersResource, private logger: Logger ) {} } class UsersService { constructor(usersResource, logger) { this.usersResource = usersResource; this.logger = logger; } } UsersService.ɵfac = function UsersService_Factory() { return new UsersService( ɵɵinject(UsersResource), ɵɵinject(Logger) ); }; UsersService.ɵprov = ɵɵdefineInjectable({ token: UsersService, factory: UsersService.ɵfac, providedIn: 'root' }); Compiled Output
  63. DI mechanism is one of the most powerful features of

    the Angular Framework! DI gives the ability to write loosely coupled and reusable code that is easier to test, scale, and maintain.