Lock in $30 Savings on PRO—Offer Ends Soon! ⏳
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
230
Dependency Injection in Angular
Marko Stanimirović
October 07, 2022
Tweet
Share
More Decks by Marko Stanimirović
See All by Marko Stanimirović
[NG India] Event-Based State Management with NgRx SignalStore
markostanimirovic
1
260
NgRx - Core Principles & New Features
markostanimirovic
0
630
NgRx Feature Creator
markostanimirovic
0
230
NgRx Action Group Creator
markostanimirovic
1
740
NgRx Tips for Future-Proof Angular Apps
markostanimirovic
0
210
NgRx Store - Tips For Better Code Hygiene
markostanimirovic
1
170
Other Decks in Programming
See All in Programming
大体よく分かるscala.collection.immutable.HashMap ~ Compressed Hash-Array Mapped Prefix-tree (CHAMP) ~
matsu_chara
1
210
Navigation 3: 적응형 UI를 위한 앱 탐색
fornewid
1
220
UIデザインに役立つ 2025年の最新CSS / The Latest CSS for UI Design 2025
clockmaker
18
7.3k
Level up your Gemini CLI - D&D Style!
palladius
1
180
SwiftUIで本格音ゲー実装してみた
hypebeans
0
110
CSC509 Lecture 14
javiergs
PRO
0
220
Cap'n Webについて
yusukebe
0
130
Cell-Based Architecture
larchanjo
0
110
宅宅自以為的浪漫:跟 AI 一起為自己辦的研討會寫一個售票系統
eddie
0
490
著者と進める!『AIと個人開発したくなったらまずCursorで要件定義だ!』
yasunacoffee
0
120
まだ間に合う!Claude Code元年をふりかえる
nogu66
5
640
DSPy Meetup Tokyo #1 - はじめてのDSPy
masahiro_nishimi
1
160
Featured
See All Featured
A Tale of Four Properties
chriscoyier
162
23k
Intergalactic Javascript Robots from Outer Space
tanoku
273
27k
GitHub's CSS Performance
jonrohan
1032
470k
The Cult of Friendly URLs
andyhume
79
6.7k
Building an army of robots
kneath
306
46k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.5k
Product Roadmaps are Hard
iamctodd
PRO
55
12k
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
27k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
9
1.1k
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
How to Ace a Technical Interview
jacobian
280
24k
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
34
2.5k
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!