Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2+ Universal is it possible to pass parameters from expressjs (without using url query string)?

I'm using that example to setup Angular 4 Universal with nodejs & expressjs : https://github.com/FrozenPandaz/ng-universal-demo

I've been trying to pass data from the first full page load to the Angular 4 final app.

The use case would be to transfer the language detected from the headers to absorb it from the Angular 4 server renderer so it can use the correct language to translate things.

The only way I've found so far is to do the language detection using HTTP headers from expressjs at the "/" route and redirect with a HTTP 302 to the "/fr" for example if the user language is French.

main.server.ts

app.param('language', function(req: any, res, next, language) {
  if(language.length == 2) 
  {
    req.language = language;
    next();
  } else {
    next(new Error('Invalid language found'));
  }
});

// Redirect incoming trafic without language in url (302 used so the browser do not seal that redirection if the user decides to change language and reload)
app.get('/', function(req, res) {   
  res.set('location', baseUrl + '/' + getLanguage(req);
  res.status(302).send();
});

routes.ts

export const ROUTES: string[] = [
  '/error',
  '/:language',
  '/:language/download'
];

It works, because I'm able to configure the Angular 4 router and retrieve the current route, but it's kind of dirty.

app.module.ts

RouterModule.forRoot([
      { path: '', component: HomeView, pathMatch: 'full'},
      { path: ':language/download', loadChildren: './+lazy/lazy.module#LazyModule'},
      { path: ':language/', redirectTo: ''},
      // Catch all if expressjs allowed this url
      { path: '**', component: HomeView }
    ])

Is there a better way than URL params to pass data from ExpressJS to the Angular 4 app using Angular Universal?

How could I transfer more data (information about the user session for example) from a full load of Angular Universal ?

like image 556
Micaël Félix Avatar asked Jun 01 '17 15:06

Micaël Félix


1 Answers

I had the same problem and after several hours of searching I found a solution that is working for me here.

In your 'main.server.ts' file, you have to add 'extraProviders' to the app.engine() function that look like so:

...
let template = readFileSync(join(__dirname, '..', 'dist', 'index.html')).toString();

app.engine('html', (_, options, callback) => {
  const opts = { 
     document: template
    ,url: options.req.url
    ,extraProviders: [
                         { provide: 'host',  useFactory: () => options.req.get('host') }
                        ,{ provide: 'PLACEHOLDER',  useFactory: () => 'SOME PLACEHOLDER TEXT' }
                     ]
  };

  renderModuleFactory(AppServerModuleNgFactory, opts)
    .then(html => callback(null, html));
});

app.set('view engine', 'html');
...

You can then access the providers that you have defined in the 'main.server.ts' file in your Angular4-component or -service code like so:

import { Injectable, Injector, PLATFORM_ID }  from '@angular/core';
import { isPlatformServer } from '@angular/common';


@Injectable()
export class I18nService {

    constructor(
          private injector: Injector
         ,@Inject(PLATFORM_ID) private platformId: Object
        ){
        let host = null;
        if (isPlatformServer(this.platformId)) {
            // -> server rendered
            host = this.injector.get('host');
            //do something with host here
        } else {
            // -> browser rendered
            host = document.location...
        }
        console.log(host);
        //do something clever with 'host' here
    }
...

Please note that once your page has loaded in the client-side-browser the 'host'-provider will no longer be available. Instead you have to get the host from the JS api.

Here is my complete 'main.server.ts' file:

import 'reflect-metadata';
import 'zone.js/dist/zone-node';
import { platformServer, renderModuleFactory } from '@angular/platform-server'
import { enableProdMode } from '@angular/core'
import { AppServerModuleNgFactory } from '../dist/ngfactory/src/app/app.server.module.ngfactory'
import * as express from 'express';
import { readFileSync } from 'fs';
import { join } from 'path';
import * as compression from 'compression';

const PORT = 4200;

enableProdMode();

const app = express();

let template = readFileSync(join(__dirname, '..', 'dist', 'index.html')).toString();

app.engine('html', (_, options, callback) => {
  const opts = { 
     document: template
    ,url: options.req.url
    ,extraProviders: [
                         { provide: 'request', useFactory: () => options.req }
                        ,{ provide: 'host',  useFactory: () => options.req.get('host') }
                     ]
  };

  renderModuleFactory(AppServerModuleNgFactory, opts)
    .then(html => callback(null, html));
});

app.set('view engine', 'html');
app.set('views', 'src');
app.use(compression());

app.get('*.*', express.static(join(__dirname, '..', 'dist')));

app.get('*', (req, res) => {
  res.render('index', { req });
});

app.listen(PORT, () => {
  console.log(`listening on http://localhost:${PORT}!`);
});
like image 133
Humppakäräjät Avatar answered Nov 14 '22 14:11

Humppakäräjät