Server does not wait till http call completes before rendering - angular 4 server side rendering
- Chan15
- 2017-11-07 07:02
- 4
I have gone ahead and implemented angular universal and able to render static part of html via server side rendering. Issue that I face is, API calls are being made and server is rendering the html without waiting for the http call to complete. So, part where my template is dependent on data obtained from api call is not being rendered on server.
Further Info:
I use authentication in node server which serve the index html only if the user is authenticated and sets the cookie on response.
Whenever I make an API call from angular, I also send the cookie as header as the dependent services also validates user with the token. For server side rendering, as the cookie will not be available at server level, I have successfully injected request and pick the cookie for the API call. So, API calls are successful but server is not waiting to render till the promise resolves.
Steps that I have tried with no success:
I have changed my zone version as suggested in this comment https://github.com/angular/universal-starter/issues/181#issuecomment-250177280
Please let me know if any further info is required.
Directing me to a angular universal boilerplate which has http calls involved would help me.
4 Answers
Finally, solution was to schedule external API async calls as macro tasks. The explanation in this issue helped. Implementing ZoneMacroTaskWrapper
like helper wrapper class for external API async calls did the rendering process wait on external promises.
Currently, ZoneMacroTaskWrapper
is not exposed to public API. But there is a promise in issue to provide documentation.
For illustration purposes monkey typing example:
export class MyAsyncTaskProcessor extends ZoneMacroTaskWrapper<MyRequest, MyResult> { constructor() { super(); } // your public task invocation method signature doTask(request: MyRequest): Observable<MyResult> { // call via ZoneMacroTaskWrapper return this.wrap(request); } // delegated raw implementation that will be called by ZoneMacroTaskWrapper protected delegate(request: MyRequest): Observable<MyResult> { return new Observable<MyResult>((observer: Observer<MyResult>) => { // calling observer.next / complete / error new Promise((resolve, error) => { // do something async }).then(result => { observer.next(result); observer.complete(); }).catch(error => observer.error(error)); }); } }
client app angular.io-example not found., Thanks for contributing an answer to Stack Overflow! Please be sure to answer the question.Provide details and share your research! But avoid …. Asking for help, clarification, or responding to other answers. I tried to setup the universal example provided on the angular.io guide page: build:ssr: `npm run build:client-and-server-bundles && npm run webpack:server` npm ERR! Exit status 1 app/app.server.module'; Not found contents for creating `main.server.ts` and configuring `.angular-cli.json` #21516.
muradm
2018-10-03 12:24
I 've created a service for doing the async API calls using muradm code.
import { Injectable } from '@angular/core'; import { Observable, Observer, Subscription } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class AsyncApiCallHelperService { taskProcessor: MyAsyncTaskProcessor; constructor() { this.taskProcessor = new MyAsyncTaskProcessor(); } doTask<T>(promise: Promise<T>) { return <Observable<T>> this.taskProcessor.doTask(promise); } } declare const Zone: any; export abstract class ZoneMacroTaskWrapper<S, R> { wrap(request: S): Observable<R> { return new Observable((observer: Observer<R>) => { let task; let scheduled = false; let sub: Subscription|null = null; let savedResult: any = null; let savedError: any = null; // tslint:disable-next-line:no-shadowed-variable const scheduleTask = (_task: any) => { task = _task; scheduled = true; const delegate = this.delegate(request); sub = delegate.subscribe( res => savedResult = res, err => { if (!scheduled) { throw new Error( 'An http observable was completed twice. This shouldn\'t happen, please file a bug.'); } savedError = err; scheduled = false; task.invoke(); }, () => { if (!scheduled) { throw new Error( 'An http observable was completed twice. This shouldn\'t happen, please file a bug.'); } scheduled = false; task.invoke(); }); }; // tslint:disable-next-line:no-shadowed-variable const cancelTask = (_task: any) => { if (!scheduled) { return; } scheduled = false; if (sub) { sub.unsubscribe(); sub = null; } }; const onComplete = () => { if (savedError !== null) { observer.error(savedError); } else { observer.next(savedResult); observer.complete(); } }; // MockBackend for Http is synchronous, which means that if scheduleTask is by // scheduleMacroTask, the request will hit MockBackend and the response will be // sent, causing task.invoke() to be called. const _task = Zone.current.scheduleMacroTask( 'ZoneMacroTaskWrapper.subscribe', onComplete, {}, () => null, cancelTask); scheduleTask(_task); return () => { if (scheduled && task) { task.zone.cancelTask(task); scheduled = false; } if (sub) { sub.unsubscribe(); sub = null; } }; }); } protected abstract delegate(request: S): Observable<R>; } export class MyAsyncTaskProcessor extends ZoneMacroTaskWrapper<Promise<any>, any> { constructor() { super(); } // your public task invocation method signature doTask(request: Promise<any>): Observable<any> { // call via ZoneMacroTaskWrapper return this.wrap(request); } // delegated raw implementation that will be called by ZoneMacroTaskWrapper protected delegate(request: Promise<any>): Observable<any> { return new Observable<any>((observer: Observer<any>) => { // calling observer.next / complete / error request .then(result => { observer.next(result); observer.complete(); }).catch(error => observer.error(error)); }); } }
I hope this helps someone.
angular universal deployment, Angular Universal executes on the server, generating static application pages that later get bootstrapped on the client. This means that the application generally renders more quickly, giving users a chance to view the application layout before it becomes fully interactive. Angular Universal executes on the server, generating static application pages that later get bootstrapped on the client. This means that the application generally
Alvaro
2018-12-22 21:26
i've created a solution which fits my needs. Maybe it is helping both of us:
const obs = new Observable<Item<any>>(subscriber => { this.thirdPartyService.getItem(itemId).then((item) => { subscriber.next(item); subscriber.complete(); return item; }); }); return obs.map(item => item.data);
angular 9 universal, Angular Universal "new" & improved Last updated: February 21, 2020. Angular Universal is just a nickname for Angular SSR (server-side rendering). With Angular Universal (SSR) we are able to render our applications as HTML on the server (ie: Node.js), and return it to the browser! Announcing Angular Universal 9.0 RC!! - A pre-render builder in Angular Universal! - Guesses static routes using guess-parser by @mgechev (
dcballs
2018-01-15 11:51
I just used Zone directly:
Declare the Zone variable in your component:
Create a macro task.
Do your http async call. In the response callback / promise let the macro task know its done:
The above is the simplest form of the solution. You would obviously need to handle errors and timeouts.
Server does not wait till http call completes before rendering, For server side rendering, as the cookie will not be available at server level, I have Currently, ZoneMacroTaskWrapper is not exposed to public API. import { Injectable } from '@angular/core'; import { Observable, Observer, observer.complete(); } }; // MockBackend for Http is synchronous, which means For server side rendering, as the cookie will not be available at server level, I have successfully injected request and pick the cookie for the API call. So, API calls
Khurram Ali
2018-04-27 15:34