I am implementing ngx-translate-router in an Angular Universal app that is already working in SSR but once I added this module SSR does not run anymore.
But works correctly with ng serve
so it means that the integration of this module without SSR is working fine.
But when running in SSR mode : npm run serve:ssr
i get this error :
NetworkError
at XMLHttpRequest.send (F:\GitaLab\vyv-angular\dist\server\main.js:1:819512)
at Observable_Observable._subscribe (F:\GitaLab\vyv-angular\dist\server\main.js:1:3285565)
at Observable_Observable._trySubscribe (F:\GitaLab\vyv-angular\dist\server\main.js:1:576303)
at Observable_Observable.subscribe (F:\GitaLab\vyv-angular\dist\server\main.js:1:576085)
at CatchOperator.call (F:\GitaLab\vyv-angular\dist\server\main.js:1:3994238)
at Observable_Observable.subscribe (F:\GitaLab\vyv-angular\dist\server\main.js:1:575939)
at DoOperator.call (F:\GitaLab\vyv-angular\dist\server\main.js:1:3343772)
at Observable_Observable.subscribe (F:\GitaLab\vyv-angular\dist\server\main.js:1:575939)
at F:\GitaLab\vyv-angular\dist\server\main.js:1:3315893
at Observable_Observable._subscribe (F:\GitaLab\vyv-angular\dist\server\main.js:1:3316238)
I implemented the SSR part based on the instructions of ngx-translate-router so what i did additional for the SSR part are the following :
1 - implemented an interceptor in the app.server.module.ts to be able to access the translations within the server part. Here is the interceptor :
import { REQUEST } from '@nguniversal/express-engine/tokens';
import * as express from 'express';
import {Inject, Injectable} from '@angular/core';
import {HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
@Injectable()
export class TranslateInterceptor implements HttpInterceptor {
private readonly DEFAULT_PORT = 4200;
private readonly PORT = process.env.PORT || this.DEFAULT_PORT;
constructor(@Inject(REQUEST) private request: express.Request) {}
getBaseUrl(req: express.Request) {
const { protocol, hostname } = req;
return this.PORT ?
`${protocol}://${hostname}:${this.PORT}` :
`${protocol}://${hostname}`;
}
intercept(request: HttpRequest<any>, next: HttpHandler) {
if (request.url.startsWith('./assets')) {
const baseUrl = this.getBaseUrl(this.request);
request = request.clone({
url: `${baseUrl}/${request.url.replace('./assets', 'assets')}`
});
}
return next.handle(request);
}
}
2 - I modified the server.ts to access the different locales and added the routes for them, but I think the issues lies there. I think I incorrectly added the routes listening in the server.ts but i did not find help about this anywhere...
here is the server.ts
// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
const server = express();
const distFolder = join(process.cwd(), 'dist/browser');
const fs = require('fs');
const data: any = JSON.parse(fs.readFileSync(`src/assets/locales.json`, 'utf8'));
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
server.use(cookieParser());
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
server.engine('html', ngExpressEngine({
bootstrap: AppServerModule,
}));
server.set('view engine', 'html');
server.set('views', distFolder);
server.get('/', (req, res) => {
const defaultLang = 'en';
const lang = req.acceptsLanguages('en', 'de', 'fr', 'es', 'pt');
let cookieLang = req.cookies.lang;
if (!cookieLang) {
cookieLang = req.cookies.LOCALIZE_DEFAULT_LANGUAGE; // This is the default name of cookie
}
const definedLang = cookieLang || lang || defaultLang;
console.log('domain requested without language');
res.redirect(301, `/${definedLang}/`);
});
// Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { });
// Serve static files from /browser
server.get('*.*', express.static(distFolder, {
maxAge: '1y'
}));
console.log('routes for the locales:');
console.log(data);
data.locales.forEach(route => {
server.get(`/${route}`, (req: express.Request, res: express.Response) => {
console.log('domain requested with language' + req.originalUrl);
res.render(indexHtml, {
req, providers: [
{ provide: REQUEST, useValue: req }
]
});
});
server.get(`/${route}/*`, (req: express.Request, res: express.Response) => {
console.log('page requested with language ' + req.originalUrl);
res.render(indexHtml, {
req, providers: [
{ provide: REQUEST, useValue: req }
]
});
});
});
return server;
}
function run(): void {
const port = process.env.PORT || 4000;
// Start up the Node server
const server = app();
server.use(compression());
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = mainModule && mainModule.filename || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
run();
}
export * from './src/main.server';
when I start the SSR server and then request the page http://localhost
I can see the redirect working to the default language and the console logs the "domain requested with language /en/"
before the error reported above.
I think the issue is that the server.ts does not manage to map the requested url to something within the routes declared in the app-routing.module.ts but i don't know how to do that. In the GitHub repository of ngx-translate-router they say :
// let node server knows about the new routes:
let fs = require('fs');
let data: any = JSON.parse(fs.readFileSync(`src/assets/locales.json`, 'utf8'));
app.get('/', ngApp);
data.locales.forEach(route => {
app.get(`/${route}`, ngApp);
app.get(`/${route}/*`, ngApp);
});
but they don't describe what "ngApp" is so i just extrapolated it base on how the server.ts was before integrating this plugin:
// All regular routes use the Universal engine
server.get('*', (req: express.Request, res: express.Response) => {
res.render(indexHtml, {
req, providers: [
{ provide: REQUEST, useValue: req }
]
});
});
So my question is double. Do you think I am right to keep searching on the direction that I dentified ? (server.ts implementation is wrong). If yes do you have an idea how to correct it ? If no, any other direction to look for ?
The error was not in the server.ts, but the interceptor. I originally designed this interceptor and I thought it could be shared just like that to retrieve the data from ./assets/locales.json as it was working fine in SSR for retrieving the ./assets/i18n/en.json, but no luck. ngx-translate and ngx-translate-router cannot share the same interceptor, I don't know why but that's it. So I had to create a second interceptor (code below) and this solved my issue.