Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Dependency Injection in Angular
Search
Marko Stanimirović
October 07, 2022
Programming
0
210
Dependency Injection in Angular
Marko Stanimirović
October 07, 2022
Tweet
Share
More Decks by Marko Stanimirović
See All by Marko Stanimirović
NgRx - Core Principles & New Features
markostanimirovic
0
580
NgRx Feature Creator
markostanimirovic
0
180
NgRx Action Group Creator
markostanimirovic
1
670
NgRx Tips for Future-Proof Angular Apps
markostanimirovic
0
120
NgRx Store - Tips For Better Code Hygiene
markostanimirovic
1
160
Other Decks in Programming
See All in Programming
ふかぼれ!CSSセレクターモジュール / Fukabore! CSS Selectors Module
petamoriken
0
150
ローコードSaaSのUXを向上させるためのTypeScript
taro28
1
630
Why Jakarta EE Matters to Spring - and Vice Versa
ivargrimstad
0
1.1k
「今のプロジェクトいろいろ大変なんですよ、app/services とかもあって……」/After Kaigi on Rails 2024 LT Night
junk0612
5
2.2k
Realtime API 入門
riofujimon
0
150
初めてDefinitelyTypedにPRを出した話
syumai
0
420
3 Effective Rules for Using Signals in Angular
manfredsteyer
PRO
0
100
[Do iOS '24] Ship your app on a Friday...and enjoy your weekend!
polpielladev
0
110
Outline View in SwiftUI
1024jp
1
330
ペアーズにおけるAmazon Bedrockを⽤いた障害対応⽀援 ⽣成AIツールの導⼊事例 @ 20241115配信AWSウェビナー登壇
fukubaka0825
6
2k
ヤプリ新卒SREの オンボーディング
masaki12
0
130
Streams APIとTCPフロー制御 / Web Streams API and TCP flow control
tasshi
2
350
Featured
See All Featured
Why You Should Never Use an ORM
jnunemaker
PRO
54
9.1k
The Illustrated Children's Guide to Kubernetes
chrisshort
48
48k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
280
13k
The Art of Programming - Codeland 2020
erikaheidi
52
13k
Designing on Purpose - Digital PM Summit 2013
jponch
115
7k
YesSQL, Process and Tooling at Scale
rocio
169
14k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
PRO
10
720
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
6
410
Designing Dashboards & Data Visualisations in Web Apps
destraynor
229
52k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
169
50k
Intergalactic Javascript Robots from Outer Space
tanoku
269
27k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
16
2.1k
Transcript
Dependency Injection in Angular Marko Stanimirović
Marko Stanimirović @MarkoStDev ★ Sr. Frontend Engineer at JobCloud ★
NgRx Core Team Member ★ Angular Belgrade Organizer ★ Hobby Musician
Marko Stanimirović @MarkoStDev ★ Sr. Frontend Engineer at JobCloud ★
NgRx Core Team Member ★ Angular Belgrade Organizer ★ Hobby Musician ★ Google Developer Expert in Angular
What is Dependency Injection?
“DI is a design pattern that makes an object independent
of its dependencies by decoupling its usage from its creation.”
Improves testability, scalability and maintainability Reduces tight coupling
class UsersService { private usersResource = new HttpUsersResource(); private logger
= new ServerLogger(); } Without Dependency Injection
class UsersService { private usersResource = new HttpUsersResource(); private logger
= new ServerLogger(); } Tightly coupled Without Dependency Injection
class UsersService { constructor( private usersResource: UsersResource, private logger: Logger
) {} } With Dependency Injection
class UsersService { constructor( private usersResource: UsersResource, private logger: Logger
) {} } With Dependency Injection Loosely coupled
Injection Scopes in Angular
Injection Scopes in Standalone Angular Apps
Injection Scopes in Standalone Angular Apps Root
Injection Scopes in Standalone Angular Apps Route Root
Injection Scopes in Standalone Angular Apps Element Route Root (Component
/ Directive)
Root Providers
Providing Service at the Root Level - #1 Way class
UsersService { ./ ... }
Providing Service at the Root Level - #1 Way @Injectable({
providedIn: 'root' }) class UsersService { ./ ... }
Providing Service at the Root Level - #1 Way @Injectable({
providedIn: 'root' }) class UsersService { ./ ... } @Component(** **. */) class UserDetailsComponent { constructor(private usersService: UsersService) {} }
Providing Service at the Root Level - #2 Way @Injectable()
class UsersService { ./ ... } bootstrapApplication(AppComponent, { providers: [UsersService], });
Route Providers
Providing Service at the Route Level const userRoutes: Route[] =
[ { path: '', component: UsersShellComponent, children: [ { path: '', component: UserListComponent }, { path: ':id', component: UserDetailsComponent }, ], }, ];
Providing Service at the Route Level const userRoutes: Route[] =
[ { path: '', component: UsersShellComponent, providers: [UsersService], children: [ { path: '', component: UserListComponent }, { path: ':id', component: UserDetailsComponent }, ], }, ];
Providing Service at the Route Level const userRoutes: Route[] =
[ { path: '', component: UsersShellComponent, providers: [UsersService], children: [ { path: '', component: UserListComponent }, { path: ':id', component: UserDetailsComponent }, ], }, ];
Element Providers
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) {} }
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) {} }
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) {} }
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) {} }
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) {} }
Dependency Resolution in Angular
Element Injector viewProviders @Inject(UsersService) providers UserDetailsComponent
Element Injector viewProviders @Inject(UsersService) providers UserDetailsComponent
Element Injector viewProviders @Inject(UsersService) providers UserDetailsComponent
Element Injector viewProviders @Inject(UsersService) providers Element Injector viewProviders providers AppComponent
UserDetailsComponent
Element Injector viewProviders @Inject(UsersService) providers Element Injector viewProviders providers AppComponent
UserDetailsComponent
Element Injector viewProviders @Inject(UsersService) providers Element Injector viewProviders providers AppComponent
Route Injector providers UserDetailsComponent
Element Injector viewProviders @Inject(UsersService) providers Root Injector providers Element Injector
viewProviders providers AppComponent Route Injector providers UserDetailsComponent
Element Injector viewProviders @Inject(UsersService) providers Root Injector providers Element Injector
viewProviders providers AppComponent Route Injector providers UserDetailsComponent
Resolution Modifiers
Optional Modifier
Optional Modifier @Component(** **. */) class UserDetailsComponent { constructor(@Optional() private
usersService.: UsersService) {} }
Optional Modifier @Component(** **. */) class UserDetailsComponent { constructor(@Optional() private
usersService.: UsersService) {} } Element Injector viewProviders providers UserDetailsComponent @Optional() @Inject(UsersService)
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
SkipSelf Modifier
SkipSelf Modifier @Component(** **. */) class UserDetailsComponent { constructor(@SkipSelf() private
usersService: UsersService) {} }
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
Self Modifier @Component(** **. */) class UserDetailsComponent { constructor(@Self() private
usersService: UsersService) {} }
Self Modifier @Component(** **. */) class UserDetailsComponent { constructor(@Self() private
usersService: UsersService) {} } Element Injector viewProviders providers UserDetailsComponent @Self() @Inject(UsersService)
Self Modifier @Component(** **. */) class UserDetailsComponent { constructor(@Self() private
usersService: UsersService) {} } Element Injector viewProviders providers UserDetailsComponent @Self() @Inject(UsersService)
Host Modifier @Component(** **. */) class UserDetailsComponent { constructor(@Host() private
usersService: UsersService) {} }
Host Modifier @Component(** **. */) class UserDetailsComponent { constructor(@Host() private
usersService: UsersService) {} } Element Injector viewProviders providers UserDetailsComponent @Host() @Inject(UsersService)
Host Modifier @Component(** **. */) class UserDetailsComponent { constructor(@Host() private
usersService: UsersService) {} } Element Injector viewProviders providers UserDetailsComponent @Host() @Inject(UsersService)
Host Modifier @Component(** **. */) class UserDetailsComponent { constructor(@Host() private
usersService: UsersService) {} } Element Injector viewProviders providers UserDetailsComponent @Host() @Inject(UsersService) Element Injector viewProviders providers AppComponent
Host Modifier @Component(** **. */) class UserDetailsComponent { constructor(@Host() private
usersService: UsersService) {} } Element Injector viewProviders providers UserDetailsComponent @Host() @Inject(UsersService) Element Injector viewProviders providers AppComponent
Injection Tokens
Creating Injection Token const APP_THEME = new InjectionToken<'light' | 'dark'>('Application
Theme');
Creating Injection Token const APP_THEME = new InjectionToken<'light' | 'dark'>('Application
Theme'); constant value type description
Providing Injection Token const APP_THEME = new InjectionToken<'light' | 'dark'>('Application
Theme'); bootstrapApplication(AppComponent, { providers: [ ], }); providing at the root level
Providing Injection Token const APP_THEME = new InjectionToken<'light' | 'dark'>('Application
Theme'); bootstrapApplication(AppComponent, { providers: [APP_THEME ], });
Providing Injection Token const APP_THEME = new InjectionToken<'light' | 'dark'>('Application
Theme'); bootstrapApplication(AppComponent, { providers: [{ }], });
Providing Injection Token const APP_THEME = new InjectionToken<'light' | 'dark'>('Application
Theme'); bootstrapApplication(AppComponent, { providers: [{ provide: APP_THEME }], });
Providing Injection Token const APP_THEME = new InjectionToken<'light' | 'dark'>('Application
Theme'); bootstrapApplication(AppComponent, { providers: [{ provide: APP_THEME, useValue: 'light' }], });
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'; }
Injecting Injection Token const APP_THEME = new InjectionToken<'light' | 'dark'>('Application
Theme'); bootstrapApplication(AppComponent, { providers: [{ provide: APP_THEME, useFactory: appThemeFactory }], }); @Component(** **. */) class SideMenuComponent { constructor( ) {} }
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') {} }
Providing Injection Token - Tree Shakeable Way const APP_THEME =
new InjectionToken('Application Theme', { });
Providing Injection Token - Tree Shakeable Way const APP_THEME =
new InjectionToken('Application Theme', { providedIn: 'root', factory: () .> localStorage.getItem('theme') .? 'light', });
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') {} }
inject Function
Constructor-Based DI vs inject @Component({ ** **. */ }) export
class UserDetailsComponent { constructor( private usersService: UsersService, private route: ActivatedRoute ) {} }
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); }
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
Resolution Modifiers with inject @Component({ ** **. */ }) export
class UserDetailsComponent { private usersService = inject(UsersService, { self: true, optional: true }); }
Resolution Modifiers with inject @Component({ ** **. */ }) export
class UserDetailsComponent { private usersService = inject(UsersService, { self: true, optional: true }); } config
Factories with inject
Factories with inject const ID_ROUTE_PARAM = new InjectionToken('ID Route Parameter
Observable', { factory() { }, });
Factories with inject const ID_ROUTE_PARAM = new InjectionToken('ID Route Parameter
Observable', { factory() { const route = inject(ActivatedRoute); }, });
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() ); }, });
Base Classes with inject
Base Classes with Constructor-Based DI abstract class EntityResource<T extends {
id: string }> { ./ ... }
Base Classes with Constructor-Based DI abstract class EntityResource<T extends {
id: string }> { protected constructor( protected http: HttpClient, protected apiUrl: string, protected uri: string ) {} ./ ... }
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> { }
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'); } }
Base Classes with inject abstract class EntityResource<T extends { id:
string }> { ./ ... }
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) {} ./ ... }
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'); } }
Advanced DI Techniques
Providing Logger Based on Environment
Providing Logger Based on Environment @Injectable({ providedIn: 'root', }) abstract
class Logger { abstract log(message: string): void; abstract error(message: string): void; }
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 { ./ ... }
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 { ./ ... }
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) {} }
Example from @ngrx/component package
tick-scheduler.ts @Injectable({ providedIn: 'root', }) abstract class TickScheduler { abstract
schedule(): void; }
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 {} }
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 {} }
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 {} }
Angular DI Under the Hood
Scan project files
Scan project files Find all classes with Angular-specific decorators @Injectable()
@Component() @Directive() @Pipe()
Scan project files Find all classes with Angular-specific decorators Preserve
information about constructor parameter types @Injectable() @Component() @Directive() @Pipe()
Scan project files Find all classes with Angular-specific decorators Preserve
information about constructor parameter types Transpile TypeScript to JavaScript @Injectable() @Component() @Directive() @Pipe()
users.service.ts @Injectable({ providedIn: 'root' }) class UsersService { constructor( private
usersResource: UsersResource, private logger: Logger ) {} }
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
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
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
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
Summary
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.
Marko Stanimirović @MarkoStDev Thank You!