Hone logo
Hone
Problems

Angular Injection Token Creation Challenge

This challenge focuses on understanding and implementing Angular's dependency injection system by creating custom injection tokens. This is a fundamental technique for providing configuration values, services, or any other data to different parts of your Angular application without tightly coupling components or services.

Problem Description

Your task is to create a reusable injection token in Angular. This token will be used to provide a specific configuration value (e.g., an API base URL or a feature flag) to a service. You will then inject this value into the service and demonstrate its usage.

Key Requirements:

  1. Create an Injection Token: Define a constant using InjectionToken to represent a unique identifier for your configuration value.
  2. Provide the Value: Configure the Angular injector to provide a specific value for your custom token. This can be done at the module level or component level.
  3. Inject the Value: Create a service that injects the value associated with your custom token.
  4. Demonstrate Usage: Create a component that injects the service and displays the configuration value it received.

Expected Behavior:

When the application runs, the component should successfully display the configuration value that was provided via the injection token.

Edge Cases to Consider:

  • What happens if the token is not provided? (Angular's default behavior is to throw an error, which is acceptable for this challenge).
  • How would you provide different values for the same token in different parts of the application (e.g., for different environments)? (While not explicitly required to implement for this challenge, consider how your solution would accommodate this).

Examples

Example 1: Providing a simple string value

Scenario: Provide an API base URL.

Code Snippet (Conceptual - Not the full solution):

  • app.config.ts (or module provider):

    import { InjectionToken } from '@angular/core';
    
    export const API_BASE_URL = new InjectionToken<string>('API_BASE_URL');
    
    // ... in bootstrapApplication or NgModule providers
    providers: [
      { provide: API_BASE_URL, useValue: 'https://api.example.com/v1' }
    ]
    
  • my.service.ts:

    import { Inject, Injectable } from '@angular/core';
    import { API_BASE_URL } from './app.config'; // or wherever defined
    
    @Injectable({ providedIn: 'root' })
    export class MyService {
      constructor(@Inject(API_BASE_URL) private baseUrl: string) {}
    
      getApiEndpoint(): string {
        return `${this.baseUrl}/users`;
      }
    }
    
  • my.component.ts:

    import { Component } from '@angular/core';
    import { MyService } from './my.service';
    
    @Component({
      selector: 'app-my',
      template: `API Endpoint: {{ endpoint }}`
    })
    export class MyComponent {
      endpoint: string;
      constructor(private myService: MyService) {
        this.endpoint = this.myService.getApiEndpoint();
      }
    }
    

Output:

API Endpoint: https://api.example.com/v1/users

Explanation:

The API_BASE_URL token is defined and provided with a string value. The MyService injects this value and uses it to construct an API endpoint. The MyComponent injects MyService and displays the resulting endpoint.

Example 2: Providing an object configuration

Scenario: Provide a feature flag object.

Code Snippet (Conceptual):

  • feature.config.ts:

    import { InjectionToken } from '@angular/core';
    
    export interface FeatureFlags {
      enableNewDashboard: boolean;
      showExperimentalFeature: boolean;
    }
    
    export const FEATURE_FLAGS = new InjectionToken<FeatureFlags>('FEATURE_FLAGS');
    
    // ... in bootstrapApplication or NgModule providers
    providers: [
      {
        provide: FEATURE_FLAGS,
        useValue: { enableNewDashboard: true, showExperimentalFeature: false }
      }
    ]
    
  • feature.service.ts:

    import { Inject, Injectable } from '@angular/core';
    import { FEATURE_FLAGS, FeatureFlags } from './feature.config';
    
    @Injectable({ providedIn: 'root' })
    export class FeatureService {
      constructor(@Inject(FEATURE_FLAGS) private flags: FeatureFlags) {}
    
      isNewDashboardEnabled(): boolean {
        return this.flags.enableNewDashboard;
      }
    }
    

Output (for a component injecting FeatureService and calling isNewDashboardEnabled()):

true

Explanation:

An object conforming to the FeatureFlags interface is provided via the FEATURE_FLAGS token. The FeatureService injects this object and exposes methods to check specific flag values.

Constraints

  • The injection token must be created using Angular's InjectionToken class.
  • The value provided to the token should be a primitive type (like string, number, boolean) or a simple object/interface.
  • The service must use the @Inject() decorator to explicitly inject the token's value.
  • The solution should be implemented in TypeScript.
  • The primary focus is on demonstrating the creation and usage of the injection token, not complex application structure.

Notes

  • Consider the generic type parameter for InjectionToken (e.g., new InjectionToken<string>('TOKEN_NAME')). This improves type safety.
  • You can provide values using useValue, useFactory, useClass, or useExisting. For this challenge, useValue is sufficient and recommended for simplicity.
  • While providedIn: 'root' is common for services, you can also provide tokens at the module or component level using the providers array. For this challenge, providing at the root or module level is acceptable.
  • Think about how naming your injection tokens clearly can improve the readability of your code.
Loading editor...
typescript