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
250
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
310
NgRx - Core Principles & New Features
markostanimirovic
0
680
NgRx Feature Creator
markostanimirovic
0
250
NgRx Action Group Creator
markostanimirovic
1
810
NgRx Tips for Future-Proof Angular Apps
markostanimirovic
0
290
NgRx Store - Tips For Better Code Hygiene
markostanimirovic
1
190
Other Decks in Programming
See All in Programming
AIエージェント時代のコードレビューを設計する
nogu66
6
2.5k
LoopHub - ローカルで動く GitHub で、AI と共同開発
jugyo
0
410
【DroidKaigi 2026】「アクセシビリティを利用するとき、 アクセシビリティもまたこちらを利用している」 〜マルウェアによる攻撃と防衛について〜
halunoyo
0
350
異なる設計思想のフレームワークを経験して得た学び
amekuhideki
2
1.5k
Pythonの実行はどこまで賢くなったのか? CPythonとPyPyから見る最適化のしくみ
curekoshimizu
4
2.8k
「つくるAI」だけではバグは見つからない ~テストに必要な「見つけるAI」を分離させる戦略~
mfunaki
0
230
XHTMLが残したもの
yosuke_furukawa
PRO
1
390
片田舎のおっさん、 Swift Buildのダイアモンド問題解決の不具合修正PRを出すが、解決方法がキャッシュをしないようにすることであり、ビルド時間が伸びると言われてマージされないので高速化もする/swiftbuild
yimajo
0
350
이 함수, 실패하면 어떻게 되나요? null부터 Rich Errors까지, Kotlin 에러 처리 15년
haeti2
0
130
Press start. Python's next generation.
willingc
PRO
3
300
go-spidermonkeyでAIエージェントのCode Modeを実装する
syumai
3
1.5k
私のClaude Code活用法 (個人開発編) - PHPerKaigi mini #4(2026/08/24)
panda_program
1
210
Featured
See All Featured
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
27k
Designing Powerful Visuals for Engaging Learning
tmiket
1
530
Navigating Algorithm Shifts & AI Overviews - #SMXNext
aleyda
1
1.6k
Building AI with AI
inesmontani
PRO
1
1.2k
How Software Deployment tools have changed in the past 20 years
geshan
1
34k
Mobile First: as difficult as doing things right
swwweet
225
10k
Design in an AI World
tapps
1
300
The AI Revolution Will Not Be Monopolized: How open-source beats economies of scale, even for LLMs
inesmontani
PRO
3
3.7k
The Impact of AI in SEO - AI Overviews June 2024 Edition
aleyda
6
1.2k
The innovator’s Mindset - Leading Through an Era of Exponential Change - McGill University 2025
jdejongh
PRO
1
320
jQuery: Nuts, Bolts and Bling
dougneiner
66
8.6k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
287
14k
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!