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
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
180
Other Decks in Programming
See All in Programming
re:Invent 2025 トレンドからみる製品開発への AI Agent 活用
yoskoh
0
670
0→1 フロントエンド開発 Tips🚀 #レバテックMeetup
bengo4com
0
500
AIエージェントの設計で注意するべきポイント6選
har1101
6
3.2k
2025 Reflections on Working with Natural Language
inouehi
0
100
メルカリのリーダビリティチームが取り組む、AI時代のスケーラブルな品質文化
cloverrose
2
490
AtCoder Conference 2025
shindannin
0
960
ゆくKotlin くるRust
exoego
1
210
余白を設計しフロントエンド開発を 加速させる
tsukuha
6
1.7k
実はマルチモーダルだった。ブラウザの組み込みAI🧠でWebの未来を感じてみよう #jsfes #gemini
n0bisuke2
3
1.5k
コマンドとリード間の連携に対する脅威分析フレームワーク
pandayumi
1
390
2年のAppleウォレットパス開発の振り返り
muno92
PRO
0
180
從冷知識到漏洞,你不懂的 Web,駭客懂 - Huli @ WebConf Taiwan 2025
aszx87410
2
3.4k
Featured
See All Featured
Java REST API Framework Comparison - PWX 2021
mraible
34
9.1k
RailsConf 2023
tenderlove
30
1.3k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
254
22k
New Earth Scene 8
popppiees
1
1.4k
Digital Ethics as a Driver of Design Innovation
axbom
PRO
0
140
Navigating Weather and Climate Data
rabernat
0
72
B2B Lead Gen: Tactics, Traps & Triumph
marketingsoph
0
43
A Tale of Four Properties
chriscoyier
162
24k
Claude Code のすすめ
schroneko
67
210k
Reflections from 52 weeks, 52 projects
jeffersonlam
355
21k
Writing Fast Ruby
sferik
630
62k
Designing for Performance
lara
610
70k
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!