Skip to content

Translate Service NullInjection problem while prerendering #257

@abdurrahmanarikan

Description

@abdurrahmanarikan

Hi
When we prerender the angular repo, we always get below error message:

NullInjectorError: R3InjectorError(AppServerModule)[ApplicationModule -> ApplicationRef -> ApplicationInitStatus -> InjectionToken Application Initializer -> [object Object] -> TranslatesService -> InjectionToken REQUEST -> InjectionToken REQUEST -> InjectionToken REQUEST]:
NullInjectorError: No provider for InjectionToken REQUEST!

those are our pretender and translate service files

prerenter.ts

import { environment } from './src/environments/environment';

// for debug
require('source-map-support').install();

const domino = require('domino');
const fs = require('fs');
const path = require('path');
const template = fs.readFileSync(path.join(__dirname, '.', 'dist', 'index.html')).toString();
const win = domino.createWindow(template);
const files = fs.readdirSync(`${process.cwd()}/dist-server`);

global['window'] = win;
Object.defineProperty(win.document.body.style, 'transform', {
  value: () => {
    return {
      enumerable: true,
      configurable: true,
    };
  },
});
global['document'] = win.document;
global['CSS'] = null;
// global['XMLHttpRequest'] = require('xmlhttprequest').XMLHttpRequest;
global['Prism'] = null;

// Load zone.js for the server.
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
import { join } from 'path';

import { enableProdMode } from '@angular/core';
// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();

// Express Engine
import { ngExpressEngine } from '@nguniversal/express-engine';
// Import module map for lazy loading
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';
import { renderModuleFactory } from '@angular/platform-server';
import { ROUTES } from './static.paths';

// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const mainFiles = files.filter((file) => file.startsWith('main'));
const hash = mainFiles[0].split('.')[1];
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require(`./dist-server/main.${hash}`);
import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens';
import { NgxRequest, NgxResponce } from '@gorniv/ngx-universal';

const BROWSER_FOLDER = join(process.cwd(), 'static');

// Load the index.html file containing referances to your application bundle.
const index = readFileSync(join('dist', 'index.html'), 'utf8');

let previousRender = Promise.resolve();

// Iterate each route path
ROUTES.forEach((route) => {
  const fullPath = join(BROWSER_FOLDER, route);

  // Make sure the directory structure is there
  if (!existsSync(fullPath)) {
    let syncpath = BROWSER_FOLDER;
    route.split('/').forEach((element) => {
      syncpath = join(syncpath, element);
      if (!existsSync(syncpath)) {
        mkdirSync(syncpath);
      }
    });
  }

  // Writes rendered HTML to index.html, replacing the file if it already exists.
  previousRender = previousRender
    .then((_) =>
      renderModuleFactory(AppServerModuleNgFactory, {
        document: index,
        url: route,
        extraProviders: [
          provideModuleMap(LAZY_MODULE_MAP),
          {
            provide: REQUEST,
            useValue: { cookie: '', headers: {} },
          },
          {
            provide: RESPONSE,
            useValue: {},
          },
          {
            provide: NgxRequest,
            useValue: { cookie: '', headers: {} },
          },
          {
            provide: NgxResponce,
            useValue: {},
          },
          {
            provide: 'ORIGIN_URL',
            useValue: environment.host,
          },
        ],
      }),
    )
    .then((html) => writeFileSync(join(fullPath, 'index.html'), html));
});

translatesservice.ts

import { Injectable, Inject, PLATFORM_ID } from '@angular/core';
import { DOCUMENT, isPlatformBrowser, isPlatformServer } from '@angular/common';
import { REQUEST } from '@nguniversal/express-engine/tokens';
import {
  TranslateService as NGXTranslateService,
  MissingTranslationHandler,
  MissingTranslationHandlerParams
} from '@ngx-translate/core';
import { MetaService } from '@ngx-meta/core';
import { Observable, of } from 'rxjs';

import { ILang } from './translates.interface';
import { UniversalStorage } from '@shared/storage/universal.storage';

const LANG_LIST: ILang[] = [
  {
    code: 'en',
    name: 'English',
    culture: 'en-US'
  },
  {
    code: 'ru',
    name: 'Русский',
    culture: 'ru-RU'
  }
];
const LANG_DEFAULT: ILang = LANG_LIST[0];
const STORAGE_LANG_NAME: string = 'langCode';

@Injectable()
export class TranslatesService {
  constructor(
    @Inject(PLATFORM_ID) private _platformId: Object,
    @Inject(DOCUMENT) private _document: any,
    @Inject(REQUEST) private _request: any,
    @Inject(NGXTranslateService) private _translate: NGXTranslateService,
    @Inject(MetaService) private _meta: MetaService,
    @Inject(REQUEST) private _req: any,
    @Inject(UniversalStorage) private _appStorage: Storage
  ) { }

  public initLanguage(): Promise<any> {
    return new Promise((resolve: Function) => {
      this._translate.addLangs(LANG_LIST.map((lang: ILang) => lang.code));
      const language: ILang = this._getLanguage();
      if (language) {
        this._translate.setDefaultLang(language.code);
      } else {
        this._translate.setDefaultLang(LANG_DEFAULT.code);
      }
      this._setLanguage(language);
      resolve();
    });
  }

  private _getLanguage(): ILang {
    // fix init cookie
    this._req.cookie = this._req.headers['cookie'];

    let language: ILang = this._getFindLang(this._appStorage.getItem(STORAGE_LANG_NAME));
    if (language) {
      return language;
    }
    if (isPlatformBrowser(this._platformId)) {
      language = this._getFindLang(this._translate.getBrowserLang());
    }
    if (isPlatformServer(this._platformId)) {
      try {
        const reqLangList: string[] = this._request.headers['accept-language'].split(';')[0].split(',');
        language = LANG_LIST.find(
          (lang: ILang) => reqLangList.indexOf(lang.code) !== -1 || reqLangList.indexOf(lang.culture) !== -1
        );
      } catch (err) {
        language = LANG_DEFAULT;
      }
    }
    language = language || LANG_DEFAULT;
    this._appStorage.setItem(STORAGE_LANG_NAME, language.code);
    return language;
  }

  private _getFindLang(code: string): ILang | null {
    return code ? LANG_LIST.find((lang: ILang) => lang.code === code) : null;
  }

  private _setLanguage(lang: ILang): void {
    this._translate.use(lang.code).subscribe(() => {
      this._meta.setTag('og:locale', lang.culture);
      this._document.documentElement.lang = lang.code;
    });
  }

  public changeLang(code: string): void {
    const lang: ILang = this._getFindLang(code);
    if (!lang || lang.code === this._translate.currentLang) {
      return;
    }
    this._appStorage.setItem(STORAGE_LANG_NAME, lang.code);
    this._setLanguage(lang);
  }

  public getLangList(): Observable<ILang[]> {
    return of(LANG_LIST);
  }

  public getCurrentLang(): string {
    return this._translate.currentLang;
  }
}

export class CommonMissingTranslationHandler implements MissingTranslationHandler {
  handle(params: MissingTranslationHandlerParams) {
    if (
      params.key.match(/\w+\.\w+/) &&
      params.translateService.translations['ru'] &&
      !params.translateService.translations['ru'][params.key]
    ) {
      console.warn(`Нехватает перевода для "${params.key}"`);
    }
    return params.key;
  }
}

Could you help ?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions