Hone logo
Hone
Problems

Angular Platform Injector Service

Develop a reusable Angular service that acts as a "platform injector." This service should allow different parts of an Angular application to conditionally provide specific implementations or configurations based on the environment (e.g., browser vs. server-side rendering). This is crucial for applications that need to behave differently in the browser (where DOM access is available) versus on the server (where it's not).

Problem Description

The goal is to create an Angular service, PlatformInjectorService, that provides a mechanism for injecting platform-specific dependencies or values. This service will abstract away the logic of determining the current platform and provide a consistent API for accessing platform-dependent data or services.

Key Requirements:

  1. Platform Detection: The service must reliably detect whether the current execution environment is the browser or the server (e.g., Node.js for SSR).
  2. Conditional Injection: The service should allow for the registration of different implementations or values for a given token based on the detected platform.
  3. Value Retrieval: A method should be provided to retrieve the appropriate value or service instance for a given token, based on the current platform.
  4. Angular Integration: The service should be designed as an Angular injectable service, making it easy to use within components, other services, and directives.

Expected Behavior:

  • When the PlatformInjectorService is initialized, it should determine the execution platform.
  • Developers should be able to "register" different providers for a specific token. For instance, a DOCUMENT token might have a browser-specific implementation and a null or placeholder implementation for the server.
  • When a consumer requests a value for a token, the service should return the implementation registered for the current platform. If no specific implementation is found for the current platform, a default or fallback should be provided.

Edge Cases to Consider:

  • What happens if no provider is registered for a token for the current platform?
  • How should the service handle singleton instances vs. factory-based providers? (For this challenge, focus on simple value injection).
  • Ensuring correct type safety for injected values.

Examples

Example 1: Injecting the DOCUMENT

Imagine you need access to the browser's document object. This is only available in the browser, not on the server.

// In your app.module.ts or a dedicated platform module
import { NgModule } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { PlatformInjectorService } from './platform-injector.service'; // Assume this exists

@NgModule({
  providers: [
    PlatformInjectorService,
    // Register browser-specific DOCUMENT
    {
      provide: 'DOCUMENT_BROWSER',
      useValue: DOCUMENT
    },
    // Register a placeholder for server-side
    {
      provide: 'DOCUMENT_SERVER',
      useValue: null // Or a specific server-side mock if needed
    },
    // Configure the platform injector to use these
    {
      provide: PlatformInjectorService,
      useFactory: (injector: Injector) => {
        const platformInjector = new PlatformInjectorService();
        platformInjector.registerProvider('DOCUMENT', {
          browser: injector.get('DOCUMENT_BROWSER'),
          server: injector.get('DOCUMENT_SERVER')
        });
        return platformInjector;
      },
      deps: [Injector]
    }
  ]
})
export class AppModule {}

// In a component that needs DOCUMENT
import { Component, Injector } from '@angular/core';
import { PlatformInjectorService } from './platform-injector.service';

@Component({...})
export class MyComponent {
  document: Document | null;

  constructor(private platformInjector: PlatformInjectorService) {
    this.document = this.platformInjector.get('DOCUMENT');
    if (this.document) {
      console.log('Running in browser, document:', this.document);
    } else {
      console.log('Running on server, document is null.');
    }
  }
}

Output (in browser):

Running in browser, document: #document

Output (on server):

Running on server, document is null.

Explanation: The PlatformInjectorService determines the platform. In the browser, it provides the actual DOCUMENT. On the server, it provides null.

Example 2: Injecting a platform-specific configuration object.

interface AppConfig {
  apiEndpoint: string;
  featureFlags: { [key: string]: boolean };
}

// In app.module.ts
@NgModule({
  providers: [
    PlatformInjectorService,
    {
      provide: 'APP_CONFIG_BROWSER',
      useValue: {
        apiEndpoint: 'http://localhost:4200/api',
        featureFlags: { enableAnalytics: true }
      } as AppConfig
    },
    {
      provide: 'APP_CONFIG_SERVER',
      useValue: {
        apiEndpoint: 'http://my-ssr-server.com/api',
        featureFlags: { enableAnalytics: false }
      } as AppConfig
    },
    {
      provide: PlatformInjectorService,
      useFactory: (injector: Injector) => {
        const platformInjector = new PlatformInjectorService();
        platformInjector.registerProvider('APP_CONFIG', {
          browser: injector.get('APP_CONFIG_BROWSER'),
          server: injector.get('APP_CONFIG_SERVER')
        });
        return platformInjector;
      },
      deps: [Injector]
    }
  ]
})
export class AppModule {}

// In a service
import { Injectable } from '@angular/core';
import { PlatformInjectorService } from './platform-injector.service';

@Injectable()
export class ConfigService {
  private config: AppConfig;

  constructor(private platformInjector: PlatformInjectorService) {
    this.config = this.platformInjector.get('APP_CONFIG');
  }

  getApiEndpoint(): string {
    return this.config.apiEndpoint;
  }

  isFeatureEnabled(featureName: string): boolean {
    return this.config.featureFlags[featureName] || false;
  }
}

Output (in browser): ConfigService.getApiEndpoint() returns 'http://localhost:4200/api' ConfigService.isFeatureEnabled('enableAnalytics') returns true

Output (on server): ConfigService.getApiEndpoint() returns 'http://my-ssr-server.com/api' ConfigService.isFeatureEnabled('enableAnalytics') returns false

Explanation: The ConfigService injects the APP_CONFIG token via PlatformInjectorService. The service provides different AppConfig objects depending on whether the application is running in the browser or on the server, allowing for environment-specific API endpoints and feature flag settings.

Constraints

  • The solution must be implemented in TypeScript.
  • The PlatformInjectorService should be an Angular Injectable service.
  • Platform detection should ideally leverage Angular's isPlatformBrowser or a similar mechanism, or a custom implementation if necessary.
  • The registerProvider method should accept a generic type for the value being injected.
  • The get method should also handle generics for type safety.
  • The primary goal is to abstract the platform-specific value retrieval, not to implement a full DI system replacement.

Notes

  • Consider how to handle the initial setup of the PlatformInjectorService. It's often best configured at the application's root module.
  • You might need to import Injector from @angular/core to allow the factory to access other services needed for provider registration.
  • Think about the interface for registering providers. A simple object with browser and server properties is a good starting point.
  • The solution should be robust enough to handle cases where a platform might not be explicitly defined.
Loading editor...
typescript