在渲染之前,服务器不会等到http调用完成 - 角度4服务器端渲染

Cha*_*n15 6 typescript server-side-rendering angular-universal angular

我已经开始实现角度通用,并能够通过服务器端渲染呈现html的静态部分.我面临的问题是,正在进行API调用,服务器正在呈现html而不等待http调用完成.因此,我的模板依赖于从api调用获得的数据的部分不会在服务器上呈现.

更多信息:

我在节点服务器中使用身份验证服务,只有在用户通过身份验证时才会为索引html提供身份验证,并在响应时设置cookie.

每当我从angular进行API调用时,我也会将cookie作为标头发送,因为依赖服务也会使用令牌验证用户.对于服务器端呈现,由于cookie在服务器级别不可用,我已成功注入请求并为API调用选择cookie.因此,API调用是成功的,但服务器不等待呈现,直到promise解析.

我试过的步骤没有成功:

我已根据此评论中的建议更改了我的区域版本https://github.com/angular/universal-starter/issues/181#issuecomment-250177280

如果需要进一步的信息,请告诉我.

将我引导到一个有角度的通用样板,它可以帮助我.

小智 5

我创建了一个使用muradm代码进行异步 API 调用的服务。

要点链接。

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));
    });
  }
}
Run Code Online (Sandbox Code Playgroud)

我希望这可以帮助别人。


mur*_*adm 3

最后,解决方案是将外部 API 异步调用安排为宏任务。这个问题的解释很有帮助。为外部 API 异步调用实现ZoneMacroTaskWrapper类似的帮助器包装类,渲染进程会等待外部承诺。

目前,ZoneMacroTaskWrapper尚未公开 API。但有一个承诺提供文件。

为了便于说明,猴子输入示例:

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));
    });
  }
}
Run Code Online (Sandbox Code Playgroud)