How to get full base URL (including server, port and protocol) in Angular Universal?

本秂侑毒 提交于 2019-11-28 12:26:54

I have bit of working code with angular 5 and angular universal

in server.ts replace this

app.engine('html', (_, options, callback) => {
    let engine = ngExpressEngine({
        bootstrap: AppServerModuleNgFactory,
        providers: [
            { provide: 'request', useFactory: () => options.req, deps: [] },
            provideModuleMap(LAZY_MODULE_MAP)
        ]
    });
    engine(_, options, callback);
});

and in Angular side you can get host with below code

export class AppComponent {
    constructor(
        private injector: Injector,
        @Inject(PLATFORM_ID) private platformId: Object
    ) {
        console.log('hi, we\'re here!');
        if (isPlatformServer(this.platformId)) {
            let req = this.injector.get('request');
            console.log("locales from crawlers: " + req.headers["accept-language"]);
            console.log("host: " + req.get('host'));
            console.log("headers: ", req.headers);
        } else {
            console.log('we\'re rendering from the browser, there is no request object.');
        }
    }
}

Now I'm using the server.ts ngExpressEngine:

import { ngExpressEngine } from '@nguniversal/express-engine';

const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('./dist/server/main.bundle');

    const {provideModuleMap} = require('@nguniversal/module-map-ngfactory-loader');

    app.engine('html', ngExpressEngine({
        bootstrap: AppServerModuleNgFactory,
        providers: [
            provideModuleMap(LAZY_MODULE_MAP)
        ]
    }));

And after that I can use in location.service.ts:

constructor(@Optional() @Inject(REQUEST) private request: any,
            @Optional() @Inject(RESPONSE) private response: any,
            @Inject(PLATFORM_ID) private platformId: Object)
{
  if (isPlatformServer(this.platformId))
  {
    console.log(this.request.get('host’)); // host on the server
  } else
  {
    console.log(document.location.hostname); // host on the browser
  }
}

You’ll find that all content coming from Http requests won’t be pre-rendered: it’s because Universal needs absolute URLs.

As your development and production server won’t have the same URL, it’s quite painful to manage it on your own.

My solution to automate this : using the new HttpClient interceptor feature of Angular 4.3, combined with the Express engine.

The interceptor catches all requests when in server context to prepend the full URL.

import { Injectable, Inject, Optional } from '@angular/core';
 import { HttpInterceptor, HttpHandler, HttpRequest } from'@angular/common/http';
 @Injectable()
 export class UniversalInterceptor implements HttpInterceptor {
  constructor(@Optional() @Inject('serverUrl') protected serverUrl: string) {}
  intercept(req: HttpRequest<any>, next: HttpHandler) {
    const serverReq = !this.serverUrl ? req : req.clone({
      url: ``${this.serverUrl}${req.url}``
    });
    return next.handle(serverReq);
  }
}

Then provide it in your AppServerModule :

import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
import { UniversalInterceptor } from './universal.interceptor';
@NgModule({
  imports: [
    AppModule,
    ServerModule
  ],
  providers: [{
    provide: HTTP_INTERCEPTORS,
    useClass: UniversalInterceptor,
    /* Multi is important or you will delete all the other interceptors */
    multi: true
  }],
  bootstrap: [AppComponent]
})
export class AppServerModule {}

Now you can use Express engine to pass the full URL to Angular, just update your server.js :

 function angularRouter(req, res) { 
  res.render('index', {
    req,
    res,
    providers: [{
      provide: 'serverUrl',
      useValue: `${req.protocol}://${req.get('host')}`
    }]
  });
}

Thanks to help from estus, I managed to hack together something that works.

It looks like most Angular Universal templates actually have the server passing a zone parameter called "originUrl", which gets used by the server rendering methods to provide a ORIGIN_URL token that can be injected in other methods. I couldn't find any documentation about this, but you can see an example here.

So if you write something like this...

export function getBaseUrl() {
    if (Zone.current.get("originUrl")) {
        return Zone.current.get('originUrl');
    } else if (location) {
        return location.origin;
    } else {
        return 'something went wrong!';
    }
}

You should be able to get the full origin URL on both the server and the client.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!