The inject() function offers a more flexible way to access dependencies. While it can be used in component properties as an alternative to the constructor, its real power shines when used outside of the traditional class-based DI context.

Why Use inject() in Advanced Scenarios?

An advanced use case is creating reusable, higher-order functions (HoFs) that are DI-aware. These are functions that can be exported and used across your application in a functional style, while still having access to Angular’s services. This is impossible with constructor injection, which is tied to a class instance.

Example: DI-Aware Route Guard with inject()

Traditionally, route guards are classes that implement an interface (CanActivate). With inject(), you can define a guard as a simple, reusable function, making your code more concise and functional.

Let’s create a feature flag guard function. It will use an injected FeatureFlagsService to check if a feature is enabled before allowing access to a route.

Feature Flag Service

// in feature-flags.service.ts
@Injectable({ providedIn: 'root' })
export class FeatureFlagsService { private featureFlags = new Map<string, boolean>([	['newDashboard', true],	['adminPanel', false] ]); isFeatureEnabled(featureName: string): boolean {	return this.featureFlags.get(featureName) ?? false; }
}
// in auth.guards.ts (our DI-aware function)
import { inject } from '@angular/core';
import { Router } from '@angular/router';
import { FeatureFlagsService } from './feature-flags.service';
// This is just a function, not a class!
export const featureFlagGuard = (featureName: string) => { return () => { // The actual guard function returned	const featureFlagsService = inject(FeatureFlagsService);	const router = inject(Router);	if (featureFlagsService.isFeatureEnabled(featureName)) {	return true;	}	// Redirect to a 'feature disabled' page or the home page	return router.parseUrl('/feature-unavailable'); };
};
// in app.routes.ts
import { Routes } from '@angular/router';
import { featureFlagGuard } from './auth.guards';
export const routes: Routes = [ {	path: 'new-dashboard',	component: NewDashboardComponent,	canActivate: [featureFlagGuard('newDashboard')] // -> Will allow access }, {	path: 'admin',	component: AdminPanelComponent,	canActivate: [featureFlagGuard('adminPanel')] // -> Will redirect }
];

This pattern is incredibly powerful. The featureFlagGuard is a factory for route guards. It’s clean, reusable, testable, and leverages DI without the boilerplate of a class, which is a perfect example of modern, functional Angular code.

Conclusion

Using inject() in functions lets you break free from class-only dependency injection. It enables cleaner, more flexible patterns like DI-aware route guards, without the boilerplate of classes. This is especially useful as Angular evolves toward a more functional, signal-driven approach. To take full advantage of these patterns, it’s smart to hire Angular developers who understand how to build with inject() beyond the basics. The result is code that’s easier to test, reuse, and scale.