feat(platform-server): Implement PlatformLocation for platformServer() (#14405)

This gives server-side apps a current URL including hash, but doesn't implement a state stack,
so back-and-forward navigation isn't possible.

PR Close #14405
This commit is contained in:
Alex Rickabaugh
2017-02-09 14:10:00 -08:00
committed by Miško Hevery
parent db700dfc71
commit 9e28568a8f
4 changed files with 128 additions and 16 deletions

View File

@ -0,0 +1,81 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as url from 'url';
import {Injectable} from '@angular/core';
import {LocationChangeEvent, LocationChangeListener, PlatformLocation} from '@angular/common';
import {getDOM} from './private_import_platform-browser';
import {scheduleMicroTask} from './facade/lang';
import {Subject} from 'rxjs/Subject';
/**
* Server-side implementation of URL state. Implements `pathname`, `search`, and `hash`
* but not the state stack.
*/
@Injectable()
export class ServerPlatformLocation implements PlatformLocation {
private _path: string = '/';
private _search: string = '';
private _hash: string = '';
private _hashUpdate = new Subject<LocationChangeEvent>();
getBaseHrefFromDOM(): string {
return getDOM().getBaseHref();
}
onPopState(fn: LocationChangeListener): void {
// No-op: a state stack is not implemented, so
// no events will ever come.
}
onHashChange(fn: LocationChangeListener): void {
this._hashUpdate.subscribe(fn);
}
get pathname(): string { return this._path; }
get search(): string { return this._search; }
get hash(): string { return this._hash; }
get url(): string {
return `${this.pathname}${this.search}${this.hash}`;
}
private setHash(value: string, oldUrl: string) {
if (this._hash === value) {
// Don't fire events if the hash has not changed.
return;
}
this._hash = value;
const newUrl = this.url;
scheduleMicroTask(() => this._hashUpdate.next(
{type: 'hashchange', oldUrl, newUrl} as LocationChangeEvent));
}
replaceState(state: any, title: string, newUrl: string): void {
const oldUrl = this.url;
const parsedUrl = url.parse(newUrl, true);
this._path = parsedUrl.path;
this._search = parsedUrl.search;
this.setHash(parsedUrl.hash, oldUrl);
}
pushState(state: any, title: string, newUrl: string): void {
this.replaceState(state, title, newUrl);
}
forward(): void {
throw new Error('Not implemented');
}
back(): void {
throw new Error('Not implemented');
}
}

View File

@ -547,7 +547,15 @@ export class Parse5DomAdapter extends DomAdapter {
return this.defaultDoc().body;
}
}
getBaseHref(): string { throw 'not implemented'; }
getBaseHref(): string {
const base = this.querySelector(this.defaultDoc(), 'base');
let href = '';
if (base) {
href = this.getHref(base);
}
// TODO(alxhub): Need relative path logic from BrowserDomAdapter here?
return isBlank(href) ? null : href;
}
resetBaseElement(): void { throw 'not implemented'; }
getHistory(): History { throw 'not implemented'; }
getLocation(): Location { throw 'not implemented'; }

View File

@ -11,28 +11,17 @@ import {platformCoreDynamic} from '@angular/compiler';
import {Injectable, NgModule, PLATFORM_INITIALIZER, PlatformRef, Provider, RootRenderer, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {ServerPlatformLocation} from './location';
import {Parse5DomAdapter} from './parse5_adapter';
import {DebugDomRootRenderer} from './private_import_core';
import {SharedStylesHost} from './private_import_platform-browser';
import {DomAdapter, SharedStylesHost} from './private_import_platform-browser';
import {ServerRootRenderer} from './server_renderer';
function notSupported(feature: string): Error {
throw new Error(`platform-server does not support '${feature}'.`);
}
class ServerPlatformLocation extends PlatformLocation {
getBaseHrefFromDOM(): string { throw notSupported('getBaseHrefFromDOM'); };
onPopState(fn: any): void { notSupported('onPopState'); };
onHashChange(fn: any): void { notSupported('onHashChange'); };
get pathname(): string { throw notSupported('pathname'); }
get search(): string { throw notSupported('search'); }
get hash(): string { throw notSupported('hash'); }
replaceState(state: any, title: string, url: string): void { notSupported('replaceState'); };
pushState(state: any, title: string, url: string): void { notSupported('pushState'); };
forward(): void { notSupported('forward'); };
back(): void { notSupported('back'); };
}
export const INTERNAL_SERVER_PLATFORM_PROVIDERS: Array<any /*Type | Provider | any[]*/> = [
{provide: PLATFORM_INITIALIZER, useValue: initParse5Adapter, multi: true},
{provide: PlatformLocation, useClass: ServerPlatformLocation},