Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(platform-server): wait on returned BEFORE_APP_SERIALIZED promises #29120

Closed
Closed
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.
+97 −5
Diff settings

Always

Just for now

feat(platform-server): wait on returned BEFORE_APP_SERIALIZED promises

This update gives external tooling the ability for async providers to
finish resolving before the document is serialized. This is not a
breaking change since render already returns a promise. All returned
promises from `BEFORE_APP_SERIALIZED` providers will wait to be
resolved or rejected. Any rejected promises will only console.warn().
  • Loading branch information...
adamdbradley committed Mar 5, 2019
commit 317a52f8f5c9cda954e51b6b54aaf4de8e0398e9
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {ApplicationRef, NgModuleFactory, NgModuleRef, PlatformRef, StaticProvider, Type} from '@angular/core';
import {ApplicationRef, NgModuleFactory, NgModuleRef, PlatformRef, StaticProvider, Type, ɵisPromise} from '@angular/core';
importTRANSITION_ID} from '@angular/platform-browser';
import {first} from 'rxjs/operators';

@@ -45,22 +45,41 @@ the server-rendered app can be properly bootstrapped into a client app.`);
.then(() => {
const platformState = platform.injector.get(PlatformState);

const asyncPromises: Promise<any>[] = [];

// 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();
const callbackResult = callback();
ifisPromise(callbackResult)) {
asyncPromises.push(callbackResult);
}

} catch (e) {
// Ignore exceptions.
console.warn('Ignoring BEFORE_APP_SERIALIZED Exception: ', e);
}
}
}

const output = platformState.renderToString();
platform.destroy();
return output;
const complete = () => {
const output = platformState.renderToString();
platform.destroy();
return output;
};

if (asyncPromises.length === 0) {
return complete();
}

return Promise
.all(asyncPromises.map(asyncPromise => {
return asyncPromise.catch(
e => { console.warn('Ignoring BEFORE_APP_SERIALIZED Exception: ', e); });
}))
.then(complete);
});
});
}
@@ -57,6 +57,24 @@ function getMetaRenderHook(doc: any) {
};
}

function getAsyncTitleRenderHook(doc: any) {
return () => {
// Async set the title as part of the render hook.
return new Promise(resolve => {
setTimeout(() => {
doc.title = 'AsyncRenderHook';
resolve();
});
});
};
}

function asyncRejectRenderHook() {
return () => {
return new Promise((_resolve, reject) => { setTimeout(() => { reject('reject'); }); });
};
}

@NgModule({
bootstrap: [MyServerApp],
declarations: [MyServerApp],
@@ -81,6 +99,39 @@ class RenderHookModule {
class MultiRenderHookModule {
}

@NgModule({
bootstrap: [MyServerApp],
declarations: [MyServerApp],
imports: [BrowserModule.withServerTransition({appId: 'render-hook'}), ServerModule],
providers: [
{
provide: BEFORE_APP_SERIALIZED,
useFactory: getAsyncTitleRenderHook,
multi: true,
deps: [DOCUMENT]
},
]
})
class AsyncRenderHookModule {
}
@NgModule({
bootstrap: [MyServerApp],
declarations: [MyServerApp],
imports: [BrowserModule.withServerTransition({appId: 'render-hook'}), ServerModule],
providers: [
{provide: BEFORE_APP_SERIALIZED, useFactory: getMetaRenderHook, multi: true, deps: [DOCUMENT]},
{
provide: BEFORE_APP_SERIALIZED,
useFactory: getAsyncTitleRenderHook,
multi: true,
deps: [DOCUMENT]
},
{provide: BEFORE_APP_SERIALIZED, useFactory: asyncRejectRenderHook, multi: true},
]
})
class AsyncMultiRenderHookModule {
}

@Component({selector: 'app', template: `Works too!`})
class MyServerApp2 {
}
@@ -699,6 +750,28 @@ class HiddenModule {
called = true;
});
}));

it('should call async render hooks', async(() => {
renderModule(AsyncRenderHookModule, {document: doc}).then(output => {
// title should be added by the render hook.
expect(output).toBe(
'<html><head><title>AsyncRenderHook</title></head><body>' +
'<app ng-version="0.0.0-PLACEHOLDER">Works!</app></body></html>');
called = true;
});
}));

it('should call multiple async and sync render hooks', async(() => {
const consoleSpy = spyOn(console, 'warn');
renderModule(AsyncMultiRenderHookModule, {document: doc}).then(output => {
// title should be added by the render hook.
expect(output).toBe(
'<html><head><meta name="description"><title>AsyncRenderHook</title></head>' +
'<body><app ng-version="0.0.0-PLACEHOLDER">Works!</app></body></html>');
expect(consoleSpy).toHaveBeenCalled();
called = true;
});
}));
});

describe('http', () => {
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.