feat(platform-server): provide a way to hook into renderModule* (#19023)
A multi RENDER_MODULE_HOOK provider can provide function that will be called with the current document just before the document is rendered to string. This hook can for example be used for the state transfer module to serialize any server state that needs to be transported to the client, just before the current platform state is rendered to string. PR Close #19023
This commit is contained in:
parent
15945c8791
commit
8dfc3c386a
|
@ -8,7 +8,7 @@
|
|||
|
||||
export {PlatformState} from './platform_state';
|
||||
export {ServerModule, platformDynamicServer, platformServer} from './server';
|
||||
export {INITIAL_CONFIG, PlatformConfig} from './tokens';
|
||||
export {BEFORE_APP_SERIALIZED, INITIAL_CONFIG, PlatformConfig} from './tokens';
|
||||
export {renderModule, renderModuleFactory} from './utils';
|
||||
|
||||
export * from './private_export';
|
||||
|
|
|
@ -24,3 +24,12 @@ export interface PlatformConfig {
|
|||
* @experimental
|
||||
*/
|
||||
export const INITIAL_CONFIG = new InjectionToken<PlatformConfig>('Server.INITIAL_CONFIG');
|
||||
|
||||
/**
|
||||
* A function that will be executed when calling `renderModuleFactory` or `renderModule` just
|
||||
* before current platform state is rendered to string.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export const BEFORE_APP_SERIALIZED =
|
||||
new InjectionToken<Array<() => void>>('Server.RENDER_MODULE_HOOK');
|
||||
|
|
|
@ -14,7 +14,7 @@ import {toPromise} from 'rxjs/operator/toPromise';
|
|||
|
||||
import {PlatformState} from './platform_state';
|
||||
import {platformDynamicServer, platformServer} from './server';
|
||||
import {INITIAL_CONFIG} from './tokens';
|
||||
import {BEFORE_APP_SERIALIZED, INITIAL_CONFIG} from './tokens';
|
||||
|
||||
interface PlatformOptions {
|
||||
document?: string;
|
||||
|
@ -45,7 +45,22 @@ the server-rendered app can be properly bootstrapped into a client app.`);
|
|||
return toPromise
|
||||
.call(first.call(filter.call(applicationRef.isStable, (isStable: boolean) => isStable)))
|
||||
.then(() => {
|
||||
const output = platform.injector.get(PlatformState).renderToString();
|
||||
const platformState = platform.injector.get(PlatformState);
|
||||
|
||||
// Run any BEFORE_APP_SERIALIZED callbacks just before rendering to string.
|
||||
const callbacks = moduleRef.injector.get(BEFORE_APP_SERIALIZED, null);
|
||||
if (callbacks) {
|
||||
for (const callback of callbacks) {
|
||||
try {
|
||||
callback();
|
||||
} catch (e) {
|
||||
// Ignore exceptions.
|
||||
console.warn('Ignoring BEFORE_APP_SERIALIZED Exception: ', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const output = platformState.renderToString();
|
||||
platform.destroy();
|
||||
return output;
|
||||
});
|
||||
|
|
|
@ -16,7 +16,7 @@ import {Http, HttpModule, Response, ResponseOptions, XHRBackend} from '@angular/
|
|||
import {MockBackend, MockConnection} from '@angular/http/testing';
|
||||
import {BrowserModule, DOCUMENT, Title} from '@angular/platform-browser';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {INITIAL_CONFIG, PlatformState, ServerModule, platformDynamicServer, renderModule, renderModuleFactory} from '@angular/platform-server';
|
||||
import {BEFORE_APP_SERIALIZED, INITIAL_CONFIG, PlatformState, ServerModule, platformDynamicServer, renderModule, renderModuleFactory} from '@angular/platform-server';
|
||||
import {Subscription} from 'rxjs/Subscription';
|
||||
import {filter} from 'rxjs/operator/filter';
|
||||
import {first} from 'rxjs/operator/first';
|
||||
|
@ -38,6 +38,50 @@ class MyServerApp {
|
|||
class ExampleModule {
|
||||
}
|
||||
|
||||
function getTitleRenderHook(doc: any) {
|
||||
return () => {
|
||||
// Set the title as part of the render hook.
|
||||
doc.title = 'RenderHook';
|
||||
};
|
||||
}
|
||||
|
||||
function exceptionRenderHook() {
|
||||
throw new Error('error');
|
||||
}
|
||||
|
||||
function getMetaRenderHook(doc: any) {
|
||||
return () => {
|
||||
// Add a meta tag before rendering the document.
|
||||
const metaElement = doc.createElement('meta');
|
||||
metaElement.setAttribute('name', 'description');
|
||||
doc.head.appendChild(metaElement);
|
||||
};
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
bootstrap: [MyServerApp],
|
||||
declarations: [MyServerApp],
|
||||
imports: [BrowserModule.withServerTransition({appId: 'render-hook'}), ServerModule],
|
||||
providers: [
|
||||
{provide: BEFORE_APP_SERIALIZED, useFactory: getTitleRenderHook, multi: true, deps: [DOCUMENT]},
|
||||
]
|
||||
})
|
||||
class RenderHookModule {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
bootstrap: [MyServerApp],
|
||||
declarations: [MyServerApp],
|
||||
imports: [BrowserModule.withServerTransition({appId: 'render-hook'}), ServerModule],
|
||||
providers: [
|
||||
{provide: BEFORE_APP_SERIALIZED, useFactory: getTitleRenderHook, multi: true, deps: [DOCUMENT]},
|
||||
{provide: BEFORE_APP_SERIALIZED, useValue: exceptionRenderHook, multi: true},
|
||||
{provide: BEFORE_APP_SERIALIZED, useFactory: getMetaRenderHook, multi: true, deps: [DOCUMENT]},
|
||||
]
|
||||
})
|
||||
class MultiRenderHookModule {
|
||||
}
|
||||
|
||||
@Component({selector: 'app', template: `Works too!`})
|
||||
class MyServerApp2 {
|
||||
}
|
||||
|
@ -469,6 +513,28 @@ export function main() {
|
|||
called = true;
|
||||
});
|
||||
}));
|
||||
|
||||
it('should call render hook', async(() => {
|
||||
renderModule(RenderHookModule, {document: doc}).then(output => {
|
||||
// title should be added by the render hook.
|
||||
expect(output).toBe(
|
||||
'<html><head><title>RenderHook</title></head><body>' +
|
||||
'<app ng-version="0.0.0-PLACEHOLDER">Works!</app></body></html>');
|
||||
called = true;
|
||||
});
|
||||
}));
|
||||
|
||||
it('should call mutliple render hooks', async(() => {
|
||||
const consoleSpy = spyOn(console, 'warn');
|
||||
renderModule(MultiRenderHookModule, {document: doc}).then(output => {
|
||||
// title should be added by the render hook.
|
||||
expect(output).toBe(
|
||||
'<html><head><title>RenderHook</title><meta name="description"></head>' +
|
||||
'<body><app ng-version="0.0.0-PLACEHOLDER">Works!</app></body></html>');
|
||||
expect(consoleSpy).toHaveBeenCalled();
|
||||
called = true;
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('http', () => {
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
/** @experimental */
|
||||
export declare const BEFORE_APP_SERIALIZED: InjectionToken<(() => void)[]>;
|
||||
|
||||
/** @experimental */
|
||||
export declare const INITIAL_CONFIG: InjectionToken<PlatformConfig>;
|
||||
|
||||
|
|
Loading…
Reference in New Issue