Functional Dependency Injection with inject() in Angular

Lets get started.
What is inject()?
The inject() function is part of Angular’s dependency injection system. It has been introduced with Angular 14. We can get the dependency from outside of the class constructor. This is especially useful in functional constructs, such as Injection outside of class context.
When to Use inject()
Recomended places to use inject():
@Injectable({ providedIn: 'root' })
export class UserService {
constructor(private http: HttpClient) {}
}
We are injecting the service by the constructor.
@Injectable({ providedIn: 'root' })
export class UserService {
private http = inject(HttpClient);
getUsers() : Observable<any> {
return this.http.get('/api/users');
}
}
NOTE:
injecttion must be done before the instance created. So we can call the inject() function in a constructor, a constructor parameter and a field initializer. We can also call it from a provider's factory. But we can not call it from a method since the instance has been already created.
Using inject() in Factory Providers
Factory providers are one of the most powerful use cases for inject(). We can directly access the dependency with the inject function without the constructor requirement.
export function tokenFactory(): string {
const config = inject(AppConfigService);
return config.apiToken;
}
@NgModule({
providers: [
{
provide: 'API_TOKEN',
useFactory: tokenFactory,
}
]
})
export class AppModule {}
Here, we’re creating a string token 'API_TOKEN' using a factory function that accesses AppConfigService using inject().
Functional Injection in Standalone Components
Angular’s standalone components, are working so efficient with inject function. Here is the example of using inject with Angular Standalone component.
@Component({
selector: 'app-user',
template: `<p>{{ user?.name }}</p>`,
imports: [CommonModule],
})
export class ProfileComponent {
private readonly userService = inject(UserService);
readonly user = this.userService.getCurrentUser();
}
As you can see above, Constructor is not required and it reduces the boilerplate.
Unit Test Example Using inject()
While implementation of unit test we can define the dependency injection like below.
describe('UserService', () => {
let companyServiceSpy: jasmine.SpyObj<CompanyService>;
companyServiceSpy = jasmine.createSpyObj('CompanyService', ['details']);
beforeEach(() => {
TestBed.configureTestingModule({
providers: [UserService, HttpClient, { provide: CompanyService, useValue: companyServiceSpy }],
});
});
it('should fetch users', () => {
const userService = inject(UserService);
expect(userService).toBeTruthy();
});
});
Above we have injected the service in the test class and we are ready to use it. We can also create a test service spy and we can inject our spy to track the service methods.
Keep in Mind:
Conclusion
The inject() function offers a flexible, clean, and efficient alternative to traditional constructor-based dependency injection. It's especially beneficial in the era of standalone components and functional providers. With the inject, we can introduce cleaner and more easy-to-maintain code for our Angular project.That is all
Happy Dependency Injecting
Burak Hamdi TUFAN