Angular项目基于依赖注入实现国际化时,Header组件注入TRANSLATION令牌出现NullInjectorError问题解决
Let's break down why you're seeing this error and how to fix it properly.
Root Cause
The TRANSLATION token is only provided in your lazy-loaded language modules (SiteEnModule, SiteFrModule). These modules create their own child injectors, so components inside them (like SiteComponent) can access the token. However, your HeaderComponent is rendered in AppComponent, which belongs to the root AppModule injector. Since AppModule doesn't have a provider for TRANSLATION, Angular throws the NullInjectorError when trying to inject it into HeaderComponent.
Solution: Use a Shared Translation Service
The cleanest way to make translations available to all components (including those in the root injector) is to create a singleton service that dynamically loads and exposes the current language's translations based on the active route.
Step 1: Create a TranslationService
This service will observe the router to detect the current language, load the corresponding translation file, and provide an observable for components to subscribe to:
import { Injectable } from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; import { BehaviorSubject, Observable } from 'rxjs'; import { map, distinctUntilChanged } from 'rxjs/operators'; import { WebsiteLanguage, Translation } from './path/to/translation'; import { en } from './path/to/en.translation'; import { fr } from './path/to/fr.translation'; @Injectable({ providedIn: 'root' }) export class TranslationService { private translationSubject = new BehaviorSubject<Translation>(en); // Default to English currentTranslation$: Observable<Translation> = this.translationSubject.asObservable(); constructor(private router: Router, private activatedRoute: ActivatedRoute) { this.initTranslationUpdates(); } private initTranslationUpdates(): void { this.router.events.pipe( // Extract the language from the root route map(() => { let route = this.activatedRoute.root; while (route.firstChild) route = route.firstChild; return route.snapshot.url[0]?.path || WebsiteLanguage.English; }), distinctUntilChanged() // Only update if language changes ).subscribe(lang => { const translation = this.getTranslationForLang(lang as WebsiteLanguage); this.translationSubject.next(translation); }); } private getTranslationForLang(lang: WebsiteLanguage): Translation { switch (lang) { case WebsiteLanguage.French: return fr; case WebsiteLanguage.English: default: return en; } } // Optional: Get current translation synchronously if needed getCurrentTranslation(): Translation { return this.translationSubject.value; } }
Step 2: Update HeaderComponent to Use the Service
Replace the direct TRANSLATION injection with the service:
import { Component } from '@angular/core'; import { TranslationService } from './path/to/translation.service'; import { Translation } from './path/to/translation'; @Component({ selector: 'app-header', templateUrl: './header.component.html' }) export class HeaderComponent { currentTranslation: Translation; constructor(private translationService: TranslationService) { this.translationService.currentTranslation$.subscribe(trans => { this.currentTranslation = trans; console.log('Current language:', trans.language); }); } }
Step 3: Update Other Components (Optional)
You can migrate your SiteComponent and other lazy-loaded components to use the service too, for consistency:
// In SiteComponent export class SiteComponent implements OnInit { translations = Object.entries(WebsiteLanguage); currentTranslation: Translation; constructor(private translationService: TranslationService) { this.translationService.currentTranslation$.subscribe(trans => { this.currentTranslation = trans; console.log('Current language:', trans.language); }); } ngOnInit(): void {} }
Step 4: Clean Up (Optional)
If you migrate all components to use the service, you can remove the TRANSLATION provider from your lazy-loaded language modules (SiteEnModule, SiteFrModule).
Alternative Approach: Move Header to Lazy-Loaded Context
If you prefer to keep using direct DI for TRANSLATION, you can:
- Remove
HeaderModulefromAppModuleimports - Add
HeaderComponentto the declarations ofSiteModule(or language-specific modules) - Move
<app-header>fromapp.component.htmltosite.component.html
This way, HeaderComponent will be part of the lazy-loaded module's injector and can access the TRANSLATION token. However, this means the header will only render when the user is on an /en or /fr route (which works for your setup since you redirect all paths to /en).
内容的提问来源于stack exchange,提问作者davidvera

