This section covers Angular services and the Dependency Injection (DI) mechanism. Below are 20 detailed questions and answers with examples and step-by-step implementation.
1. What is a service in Angular?
Answer:
A service in Angular is a class that encapsulates business logic, data operations, or reusable functionalities. Services promote code reusability and separation of concerns by sharing logic across multiple components.
Example:
@Injectable({ providedIn: 'root' })
export class DataService {
private data: string[] = [];
addData(item: string) {
this.data.push(item);
}
getData(): string[] {
return this.data;
}
}
Steps to Implement:
- Create a service using the Angular CLI:
ng generate service data - Define methods and properties in the service.
- Inject the service into components using the constructor.
2. How do you create and inject a service in Angular?
Answer:
Services can be created using the Angular CLI or manually, and they are injected into components or other services using Angular’s DI system.
Example:
// app.module.ts
providers: [DataService]
// app.component.ts
constructor(private dataService: DataService) {}
Steps to Implement:
- Generate a service using:
ng generate service my-service - Use
@Injectableto make it injectable. - Inject the service into a component or another service.
3. What is dependency injection (DI) in Angular?
Answer:
DI is a design pattern used in Angular to provide instances of services or objects to components or other services. Angular uses DI to create and manage object lifecycles.
Steps to Implement:
- Define a service with
@Injectable. - Add the service to a module or root injector.
- Inject it using the constructor.
4. What is the role of the @Injectable decorator?
Answer:
@Injectable marks a class as a service that can participate in Angular’s DI system.
Example:
@Injectable({ providedIn: 'root' })
export class LoggerService {
log(message: string) {
console.log(message);
}
}
Steps to Implement:
- Decorate the class with
@Injectable. - Specify the
providedInproperty to define the scope (e.g.,'root','any'). - Inject the service where needed using the constructor.
5. What is the difference between providedIn: 'root' and module-level providers?
Answer:
providedIn: 'root': Registers the service in the root injector, making it a singleton available throughout the application.- Module-level providers: Register the service in a specific module, creating new instances in lazy-loaded modules.
Example:
@Injectable({ providedIn: 'root' })
export class GlobalService {}
@NgModule({
providers: [ModuleSpecificService]
})
export class FeatureModule {}
6. How do you use hierarchical injectors in Angular?
Answer:
Hierarchical injectors allow different instances of a service to exist in parent and child components or modules. This is achieved by providing the service at different levels in the component or module tree.
Example:
@Component({
selector: 'parent',
providers: [DataService],
})
export class ParentComponent {}
@Component({
selector: 'child',
})
export class ChildComponent {
constructor(private dataService: DataService) {}
}
7. What are tokens in Angular dependency injection?
Answer:
Tokens are unique identifiers used by Angular’s DI system to locate and inject dependencies. They can be strings, classes, or custom tokens created with InjectionToken.
Example:
const API_URL = new InjectionToken<string>('API_URL');
@NgModule({
providers: [
{ provide: API_URL, useValue: 'https://api.example.com' }
]
})
export class AppModule {}
constructor(@Inject(API_URL) private apiUrl: string) {}
8. Explain the difference between a singleton service and a non-singleton service.
Answer:
- Singleton Service: A single instance shared across the application (e.g.,
providedIn: 'root'). - Non-Singleton Service: A new instance is created for each provider (e.g., provided at the component level).
Example:
// Singleton
@Injectable({ providedIn: 'root' })
export class SingletonService {}
// Non-Singleton
@Component({
providers: [NonSingletonService],
})
export class MyComponent {}
9. How do you inject a service into another service?
Answer:
Use the @Injectable decorator on both services and inject one service into another via the constructor.
Example:
@Injectable({ providedIn: 'root' })
export class AuthService {
constructor(private loggerService: LoggerService) {}
}
10. What are multi-providers in Angular?
Answer:
Multi-providers allow multiple values to be provided for the same token, useful for plugin-based architectures or extensibility.
Example:
@NgModule({
providers: [
{ provide: 'PLUGINS', useValue: PluginA, multi: true },
{ provide: 'PLUGINS', useValue: PluginB, multi: true }
]
})
export class AppModule {}
constructor(@Inject('PLUGINS') private plugins: any[]) {}
11. How do you handle circular dependencies in Angular services?
Answer:
Circular dependencies occur when two or more services depend on each other. Resolve them by using Injector or splitting the logic into separate services.
12. What are factory providers in Angular?
Answer:
Factory providers use a factory function to create a service instance.
Example:
@NgModule({
providers: [
{ provide: DataService, useFactory: () => new DataService('config') }
]
})
13. How do you scope services to specific modules?
Answer:
Provide the service in the providers array of the module to restrict its scope.
14. What is an Injection Token in Angular?
Answer:
An InjectionToken is used to create a custom provider token.
15. How do you create a custom provider in Angular?
Answer:
A custom provider in Angular allows you to control how a service or dependency is created. You can use useClass, useValue, useFactory, or useExisting to configure the provider.
Example (useFactory):
export class Config {
constructor(public apiUrl: string) {}
}
@NgModule({
providers: [
{
provide: Config,
useFactory: () => new Config('https://api.example.com')
}
]
})
export class AppModule {}
constructor(private config: Config) {
console.log(this.config.apiUrl); // Outputs: https://api.example.com
}
Steps to Implement:
- Create a custom provider in the
providersarray. - Use
useClass,useValue,useFactory, oruseExistingto define the provider. - Inject the dependency where needed using the constructor.
16. Explain how Angular resolves dependencies.
Answer:
Angular resolves dependencies using the Injector hierarchy. When a dependency is requested, Angular searches for it in the injector tree starting from the closest injector to the root.
Steps to Implement Dependency Resolution:
- Register the service in a module or component using the
providersarray or@InjectablewithprovidedIn. - When a dependency is injected, Angular checks the nearest injector.
- If the service is not found, Angular traverses up the injector tree until it reaches the root injector.
Example:
@Injectable({ providedIn: 'root' })
export class RootService {}
@Component({
selector: 'app-child',
providers: [ChildService],
})
export class ChildComponent {
constructor(private childService: ChildService) {}
}
@Component({
selector: 'app-root',
})
export class AppComponent {
constructor(private rootService: RootService) {}
}
In this example, ChildService is resolved in the child component’s injector, while RootService is resolved in the root injector.
17. What is the difference between useClass, useValue, useFactory, and useExisting in providers?
Answer:
Angular offers different options for configuring providers, each with specific use cases:
useClass: Provides a new instance of the class.@NgModule({ providers: [ { provide: LoggerService, useClass: LoggerService } ] }) export class AppModule {}useValue: Provides a static value.@NgModule({ providers: [ { provide: 'API_URL', useValue: 'https://api.example.com' } ] }) export class AppModule {}useFactory: Provides a value created by a factory function.@NgModule({ providers: [ { provide: ConfigService, useFactory: () => new ConfigService('custom-config') } ] }) export class AppModule {}useExisting: Aliases another provider.@NgModule({ providers: [ { provide: LoggerService, useClass: CustomLoggerService }, { provide: CustomLoggerService, useExisting: LoggerService } ] }) export class AppModule {}
Use Cases:
- Use
useClassfor default or overridden class-based services. - Use
useValuefor constants or configurations. - Use
useFactoryfor dynamic service creation. - Use
useExistingfor reusing an existing service instance.
18. How do you use DI in unit testing Angular services?
Answer:
In unit testing, Angular’s TestBed allows you to configure the dependency injection environment for testing services and components.
Steps to Implement:
- Use
TestBed.configureTestingModuleto define providers. - Inject the service using
TestBed.injector the test’s constructor. - Mock dependencies if needed.
Example:
import { TestBed } from '@angular/core/testing';
import { DataService } from './data.service';
import { HttpClientTestingModule } from '@angular/common/http/testing';
describe('DataService', () => {
let service: DataService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [DataService]
});
service = TestBed.inject(DataService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
19. What are optional dependencies in Angular?
Answer:
Optional dependencies allow you to inject a dependency if it exists and handle the absence of the dependency gracefully. This is achieved using the @Optional decorator.
Example:
@Injectable({ providedIn: 'root' })
export class OptionalService {}
@Component({
selector: 'app-root',
})
export class AppComponent {
constructor(@Optional() private optionalService?: OptionalService) {
if (optionalService) {
console.log('OptionalService is available');
} else {
console.log('OptionalService is not provided');
}
}
}
Steps to Implement:
- Use
@Optional()in the constructor to inject the dependency. - Handle cases where the dependency is
undefined.
20. How do you debug DI issues in Angular applications?
Answer:
Debugging DI issues involves identifying missing or misconfigured providers and understanding the injector hierarchy.
Steps to Debug:
- Check Console Errors: Look for errors like
NullInjectorErrororProvider not found. - Verify Provider Configuration: Ensure the service is added to the
providersarray or uses@InjectablewithprovidedIn. - Inspect Injector Hierarchy: Use Angular DevTools to check injector trees.
- Use Debugging Tools:
- Insert
console.logstatements in constructors. - Use breakpoints in the service or component constructors.
- Insert
Example:
If NullInjectorError occurs:
// Check if service is registered properly
@NgModule({
providers: [MyService]
})
export class AppModule {}