首页 > 解决方案 > 错误 NullInjectorError: R3InjectorError(AppModule) - 使用 DI 进行角度内容翻译

问题描述

我有一个角度项目,它具有以下架构(我删除了 hml 和 css):

|_ app.component.ts
|_ app.module.ts
|_ app-routing-module.ts
|_ components
   |_ site
        |_ site.component.ts
        |_ site.module.ts
        |_ site.en.module.ts
        |_ site.fr.module.ts
        |_ site-routing-module.ts
           |_ home
               |_ home.component.ts
               |_ home.module.ts
               |_ home.en.module.ts
               |_ home.fr.module.ts
               |_ home-routing-module.ts
                   |_ pdf-viewer-form
                       |_ pdf-viewer-form.component.ts
                   |_ pdf-viewer-from-server
                       |_ pdf-viewer-from-server.component.ts
           |_ info
               |_ info.component.ts
               |_ info.module.ts
               |_ info.en.module.ts
               |_ info.fr.module.ts
               |_ info-routing-module.ts
   |_ theme
           |_ header
               |_ header.component.ts
               |_ header.module.ts
               |_ header.en.module.ts
               |_ header.fr.module.ts
               |_ header-routing-module.ts

对于这个项目,我尝试使用依赖注入和延迟加载来实现 i18n。为此,我实现了一个包含以下内容的打字稿文件:

import { InjectionToken } from '@angular/core';
import { en } from './en.translation';

export enum WebsiteLanguage {
  English = 'en',
  French = 'fr',
}
export type Translation = typeof en;
export const TRANSLATION = new InjectionToken<Translation>('TRANSLATION');

还有一个翻译文件:

export const fr = {
  language: 'Français',
  home: {
    title: 'La page d\'accueil marche',
  },
  upload: {
    title: 'La page Upload marche!',
  },

  langsSupported: (n: number) => `Cette démon inclut ${n} langues ${n === 1 ? '' : 's'}.`,
};

我的 app.component.html 是:

<app-header></app-header>
<router-outlet></router-outlet>

关于 app-routing.module.ts 包含允许延迟加载的路由:

const routes: Routes = [
  {
    path: WebsiteLanguage.English,
    loadChildren: () => import ("./components/site/site.en.module").then(m => m.SiteEnModule)
  }, // lazy loading the English site module
  {
    path: WebsiteLanguage.French,
    loadChildren: () => import ("./components/site/site.fr.module").then(m => m.SiteFrModule)
  },   // lazy loading the Czech site module

  {path: '**', redirectTo: WebsiteLanguage.English},  // redirecting to default route in case of any other prefix
];

如果我们看一下 site.fr.module :

@NgModule({
  declarations: [],
  imports: [
    // CommonModule,
    SiteModule,
  ],
  providers: [
    // providing the value of english translation data
    {provide: TRANSLATION, useValue: fr},
  ],
})
export class SiteFrModule { }

最后我的 SiteModule 很简单:

@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    SiteRoutingModule,
    // RouterModule
  ],
  exports: [
    CommonModule,
  ],
})
export class SiteModule { }

在我的 site.component html 中:

<ol>
  <li *ngFor="let translation of translations">
    <a [routerLink]="['/', translation[1]]">{{translation[0]}} ({{translation[1]}})</a>
  </li>
</ol>

<!-- simple navigation links to switch between two "translated" views -->
<a routerLink="home">home</a>
<a routerLink="info">info</a>

我的 SiteComponent 正在注入翻译:

export class SiteComponent implements OnInit {

  // this will extract tuple of translation key and name from enum
  translations = Object.entries(WebsiteLanguage);

  constructor(@Inject(TRANSLATION) public readonly lang: Translation) {
    // just a simple log to demonstrate usage in component class
    console.log('current language is', lang.language);
  }

  ngOnInit(): void {
  }
}

我可以轻松导航并更改 url 中的语言,并且我有正确的翻译。现在,我的问题是我在 app.component.html 中有一个菜单。我本可以在 angular 中使用内置的 i18n 工具,但我想使用 json 对象来设置不同的翻译静态内容。

我试图创建一个标头模块和标头路由。我复制了用于 SiteComponent 的代码。

例如我的路线有这个定义:

const headerRoutes: Routes = [
  // loadChildren: './home/home.module#HomeModule'
  // loadChildren: './info/info.module#InfoModule'
  {
    path: '',
    component: HeaderComponent,
    children: [
      {
        path: 'home',
        loadChildren: () => import ("../../site/home/home.module").then(m => m.HomeModule)
      },
      {
        path: 'info',
        loadChildren: () => import ("../../site/info/info.module").then(m => m.InfoModule)
      },
      {
        path: '',
        pathMatch: 'full',
        redirectTo: 'home'
      },
    ],
  },
];

@NgModule({
  imports: [RouterModule.forChild(headerRoutes)],
  exports: [RouterModule],
})
export class HeaderRoutingModule {}

我的 HTML:

<a routerLink="home">From file</a> |
<a routerLink="info">From server</a>

我不幸收到此错误消息:

core.js:6479 ERROR NullInjectorError: R3InjectorError(AppModule)[InjectionToken TRANSLATION -> InjectionToken TRANSLATION -> InjectionToken TRANSLATION]: 
  NullInjectorError: No provider for InjectionToken TRANSLATION!
    at NullInjector.get (core.js:11101)
    at R3Injector.get (core.js:11268)
    at R3Injector.get (core.js:11268)
    at R3Injector.get (core.js:11268)
    at NgModuleRef$1.get (core.js:25332)
    at Object.get (core.js:25046)
    at lookupTokenUsingModuleInjector (core.js:3342)
    at getOrCreateInjectable (core.js:3454)
    at Module.ɵɵdirectiveInject (core.js:14737)

通过在我的 header.component.ts 文件中注释以下行:

// constructor(@Inject(TRANSLATION) public readonly lang: Translation) {
//   // just a simple log to demonstrate usage in component class
//   console.log('current language is', lang.language);
// }

页面显示时,站点组件正在根据翻译文件翻译内容。

我有两个问题:

标签: angulardependency-injectioninternationalization

解决方案


我在发布问题后解决了这个问题。我的 app.component.html 已修改:

<router-outlet></router-outlet>

我在站点组件中添加了标题:

<app-header></app-header>
<ol>
  <li *ngFor="let translation of translations">
    <a [routerLink]="['/', translation[1]]">{{translation[0]}} ({{translation[1]}})</a>
  </li>
</ol>

<!-- simple navigation links to switch between two "translated" views -->
<a routerLink="home">home</a>
<a routerLink="info">info</a>

最后我从头组件中取消注释构造函数:

constructor(@Inject(TRANSLATION) public readonly lang: Translation) {
    // just a simple log to demonstrate usage in component class
    console.log('current language is', lang.language);
}

我还删除了以下脚本:

  1. header.module.ts
  2. header.en.module.ts
  3. header.fr.module.ts
  4. 标头路由模块.ts

美满结局...


推荐阅读