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
240
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Dependency Injection in Angular
Marko Stanimirović
October 07, 2022
More Decks by Marko Stanimirović
See All by Marko Stanimirović
[NG India] Event-Based State Management with NgRx SignalStore
markostanimirovic
1
300
NgRx - Core Principles & New Features
markostanimirovic
0
650
NgRx Feature Creator
markostanimirovic
0
240
NgRx Action Group Creator
markostanimirovic
1
800
NgRx Tips for Future-Proof Angular Apps
markostanimirovic
0
270
NgRx Store - Tips For Better Code Hygiene
markostanimirovic
1
190
Other Decks in Programming
See All in Programming
Vue × Nuxt × Oxc どこまで使える?実運用の現在地
andpad
0
270
Even G2とAWSで推しのエージェントを召喚しよう!
har1101
1
120
技術的負債解消で開発者の未来を開く- AIの力でコード刷新
kmd2kmd
0
100
PHPで使える日時の表現と、その知り方 #frontend_phpcon_do
o0h
PRO
0
250
その問い、本当に正しいですか?AI時代のエンジニアに必要な哲学と認知科学 / ai-philosophy-cognitive-science
minodriven
11
5.8k
Go1.27で導入されるジェネリクスメソッドでできること
mackee
0
140
Skillsは効率化、Agentsは"自分の拡張"——Builder時代のエージェント編成(CC Night 2026)
wemra
1
140
AI 輔助遺留系統現代化的經驗分享
jame2408
1
720
Webフレームワークの ベンチマークについて
yusukebe
0
170
Spec Driven Development | AI Summit Lisbon
danielsogl
PRO
0
200
コンテキストの使い捨てをやめる — ビジネスルール駆動開発と miko —
ioki
0
210
LLM本来の能力を解き放つサンドボックス技術とAI民主化への適用
yukukotani
3
4.3k
Featured
See All Featured
Odyssey Design
rkendrick25
PRO
2
700
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
28
3.5k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
17k
The browser strikes back
jonoalderson
0
1.3k
The #1 spot is gone: here's how to win anyway
tamaranovitovic
2
1.1k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
21
1.5k
Ecommerce SEO: The Keys for Success Now & Beyond - #SERPConf2024
aleyda
1
2k
Imperfection Machines: The Place of Print at Facebook
scottboms
270
14k
Sam Torres - BigQuery for SEOs
techseoconnect
PRO
0
290
Max Prin - Stacking Signals: How International SEO Comes Together (And Falls Apart)
techseoconnect
PRO
0
180
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
11
950
The Illustrated Children's Guide to Kubernetes
chrisshort
51
52k
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!