【问题标题】:Angular 9 Universal SSR fails on Lazy Loaded route with HTTP callAngular 9 Universal SSR 在带有 HTTP 调用的延迟加载路由上失败
【发布时间】:2020-07-26 00:34:12
【问题描述】:

我有一个 Angular 9 (v9.0.6) 应用程序可以与 Universal (SSR) 一起正常工作,当我注意到应用程序以 100% 的 CPU 消耗挂起时,我正在执行最后一组测试,然后再转移到 PROD。我分析了这个错误,当 Angular 应用程序直接加载一个执行 HTTP 调用的延迟加载模块的路由时,结果证明是一个问题。

如果我通过 Home(或另一个没有 HTTP 的路由 - 但即使是 home,另一个延迟加载的模块具有相同的组件来进行 HTTP 调用)加载角度,一切都可以正常工作。我可以毫无问题地导航到所有路线,并且 APP 就像一个魅力。但是,如果我去,让 www.mywebsite/lazy-loaded-module 直接在一个新选项卡中说,我相信引导过程中有一些东西阻止所有 tregistrations 或至少 HTTPClientModule 正确注册并且然后我就失败了。

首先,我在AppModule 中只注册了一次HTTPClientModule。我得到的错误是:

(node:17624) [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead.

CPU 达到 100%,应用程序挂起。同样,如果我转到 www.my-website.com 则不会发生这种情况,然后我通过应用程序导航到 /my-lazy-loaded-module

然后我尝试了其他方法:在我的延迟加载模块中包括(我知道我不应该)HTTPClientModule,它在 CPU 问题中的工作方式消失了,但是我得到了一个不同的错误:

ReferenceError: XMLHttpRequest is not defined
    at BrowserXhr.build

这会对我使用 SSR 检索并呈现的内容产生影响,因为没有。作为一种可以做到的解决方法,但我想知道应该如何解决这个问题。我执行的node 版本是:v10.16.2

更新

为了更清楚地了解我正在进行的 HTTP 调用,我只是一个标准调用:

public getNext = (page: number, pageSize: number): Promise<IEventsPaged> => {
        return this.http.get<IEventsPaged>(`${environment.apiBaseUrl}/events?page=${page}&pageSize=${pageSize})
                        .toPromise()
                        .then(r => r)
                        .catch((error: Response | any) => {
                            return Promise.reject(error);
                        });
    }

更新 2

我已更新到 Angular 9.1.1,它修复了缓冲区警告。应用程序仍然挂起,并且只发生在一个模块上。其他模块也在进行 http 调用(相同的标准 get,但使用不同的服务)而没有问题。

** 更新 3 **

我找到了问题的根本原因。它最终与 HTTP 无关。发生的事情是,在我所指的页面中,我有一个非常简单的角度动画。这是一个通过标准角度动画应用无限动画的三角形,它会变大或变小。为了实现无限效果,我连接到animation.done 事件以将状态更改为大或小。好吧,如果你在放置动画的那条路线上硬刷新页面,你最终会陷入这样的无限循环:

{ element:
   HTMLDivElement {
     parentNode:
      HTMLUnknownElement {
        parentNode: [HTMLDivElement],
        _previousSibling: [HTMLDivElement],
        _nextSibling: [HTMLImageElement],
        _index: undefined,
        _childNodes: null,
        _firstChild: [Circular],
        nodeType: 1,
        ownerDocument: [Object],
        localName: 'app-bottom-angle',
        namespaceURI: 'http://www.w3.org/1999/xhtml',
        prefix: null,
        _tagName: undefined,
        _attrsByQName: [Object],
        _attrsByLName: [Object],
        _attrKeys: [Array],
        __ngContext__: [LComponentView_CalendarIntroductionComponent],
        _classList: [DOMTokenList],
        _nid: 79 },
     _previousSibling: [Circular],
     _nextSibling: [Circular],
     _index: undefined,
     _childNodes: null,
     _firstChild: null,
     nodeType: 1,
     ownerDocument:
      { parentNode: null,
        _previousSibling: [Circular],
        _nextSibling: [Circular],
        _index: undefined,
        _childNodes: null,
        _firstChild: [Object],
        nodeType: 9,
        isHTML: true,
        _address: 'http://localhost:54818/en/calendar',
        readyState: 'loading',
        implementation: [Object],
        ownerDocument: null,
        _contentType: 'text/html',
        doctype: [Object],
        documentElement: [HTMLHtmlElement],
        _templateDocCache: null,
        _nodeIterators: null,
        _nid: 1,
        _nextnid: 152,
        _nodes: [Array],
        byId: [Object],
        modclock: 23,
        _scripting_enabled: true,
        defaultView: [Object],
        _lastModTime: 1 },
     localName: 'div',
     namespaceURI: 'http://www.w3.org/1999/xhtml',
     prefix: null,
     _tagName: undefined,
     _attrsByQName:
      [Object: null prototype] { '_ngcontent-sc33': [Object], class: [Object], style: [Object] },
     _attrsByLName:
      [Object: null prototype] {
        '|_ngcontent-sc33': [Object],
        '|class': [Object],
        '|style': [Object] },
     _attrKeys: [ '|_ngcontent-sc33', '|class', '|style' ],
     _classList:
      DOMTokenList {
        '0': 'position-absolute',
        '1': 'z-index-plus-1',
        '2': 'bottom-0',
        '3': 'right-0',
        '4': 'left-0',
        '5': 'mb-4',
        '6': 'ng-tns-c33-1',
        '7': 'ng-trigger',
        '8': 'ng-trigger-scale',
        '9': undefined,
        '10': undefined,
        _getString: [Function],
        _setString: [Function],
        _length: 9 },
     __ngContext__:
      LComponentView_BottomAngleComponent [
        [HTMLUnknownElement],
        [TView],
        211,
        [LComponentView_CalendarIntroductionComponent],
        null,
        null,
        [TNode$1],
        [LCleanup],
        [BottomAngleComponent],
        [Object],
        [AnimationRendererFactory],
        [AnimationRenderer],
        null,
        null,
        null,
        [LComponentView_CalendarIntroductionComponent],
        [Circular],
        null,
        0,
        [Circular],
        'big' ],
     _nid: 80,
     _style:
      { _element: [Circular],
        _parsedStyles: [Object],
        _lastParsedText: 'transform: scale(1); transform-style: preserve-3d;',
        _names: [Array] } },
  triggerName: 'scale',
  fromState: 'small',
  toState: 'big',
  phaseName: 'done',
  totalTime: 1200,
  disabled: false,
  _data: 1006 }
{ element:
   HTMLDivElement {
     parentNode:
      HTMLUnknownElement {
        parentNode: [HTMLDivElement],
        _previousSibling: [HTMLDivElement],
        _nextSibling: [HTMLImageElement],
        _index: undefined,
        _childNodes: null,
        _firstChild: [Circular],
        nodeType: 1,
        ownerDocument: [Object],
        localName: 'app-bottom-angle',
        namespaceURI: 'http://www.w3.org/1999/xhtml',
        prefix: null,
        _tagName: undefined,
        _attrsByQName: [Object],
        _attrsByLName: [Object],
        _attrKeys: [Array],
        __ngContext__: [LComponentView_CalendarIntroductionComponent],
        _classList: [DOMTokenList],
        _nid: 79 },
     _previousSibling: [Circular],
     _nextSibling: [Circular],
     _index: undefined,
     _childNodes: null,
     _firstChild: null,
     nodeType: 1,
     ownerDocument:
      { parentNode: null,
        _previousSibling: [Circular],
        _nextSibling: [Circular],
        _index: undefined,
        _childNodes: null,
        _firstChild: [Object],
        nodeType: 9,
        isHTML: true,
        _address: 'http://localhost:54818/en/calendar',
        readyState: 'loading',
        implementation: [Object],
        ownerDocument: null,
        _contentType: 'text/html',
        doctype: [Object],
        documentElement: [HTMLHtmlElement],
        _templateDocCache: null,
        _nodeIterators: null,
        _nid: 1,
        _nextnid: 152,
        _nodes: [Array],
        byId: [Object],
        modclock: 23,
        _scripting_enabled: true,
        defaultView: [Object],
        _lastModTime: 1 },
     localName: 'div',
     namespaceURI: 'http://www.w3.org/1999/xhtml',
     prefix: null,
     _tagName: undefined,
     _attrsByQName:
      [Object: null prototype] { '_ngcontent-sc33': [Object], class: [Object], style: [Object] },
     _attrsByLName:
      [Object: null prototype] {
        '|_ngcontent-sc33': [Object],
        '|class': [Object],
        '|style': [Object] },
     _attrKeys: [ '|_ngcontent-sc33', '|class', '|style' ],
     _classList:
      DOMTokenList {
        '0': 'position-absolute',
        '1': 'z-index-plus-1',
        '2': 'bottom-0',
        '3': 'right-0',
        '4': 'left-0',
        '5': 'mb-4',
        '6': 'ng-tns-c33-1',
        '7': 'ng-trigger',
        '8': 'ng-trigger-scale',
        '9': undefined,
        '10': undefined,
        _getString: [Function],
        _setString: [Function],
        _length: 9 },
     __ngContext__:
      LComponentView_BottomAngleComponent [
        [HTMLUnknownElement],
        [TView],
        211,
        [LComponentView_CalendarIntroductionComponent],
        null,
        null,
        [TNode$1],
        [LCleanup],
        [BottomAngleComponent],
        [Object],
        [AnimationRendererFactory],
        [AnimationRenderer],
        null,
        null,
        null,
        [LComponentView_CalendarIntroductionComponent],
        [Circular],
        null,
        0,
        [Circular],
        'small' ],
     _nid: 80,
     _style:
      { _element: [Circular],
        _parsedStyles: [Object],
        _lastParsedText: 'transform: scale(1.2); transform-style: preserve-3d;',
        _names: [Array] } },
  triggerName: 'scale',
  fromState: 'big',
  toState: 'small',
  phaseName: 'done',
  totalTime: 1200,
  disabled: false,
  _data: 1007 }

然后您的应用程序挂起。我不知道是否有更好的方法来使用 Angular 动画实现无限动画,以及是否应该由开发人员“知道”SSR 会受到它们的攻击。我想如果你认为它们处理 HTML 元素,你可能会争辩说它们都应该在其中使用 isPlatformBrowser

【问题讨论】:

  • 你有APP_INITIALIZER令牌的提供者吗?
  • 不,我没有。是不是我必须对其进行配置,以便在导航到延迟加载的模块之前以某种方式使 http 可用?
  • 不,我只是想知道 APP_INITIALIZER 是否引起了问题
  • 我刚刚注意到有另一个模块具有几乎相同的结构并且工作正常。我不确定发生了什么,但如果我不执行 HTTP 调用,应用程序可以正常工作
  • 你能尝试向惰性模块调用的同一个 url 发出 API 请求服务器端(例如使用 curl)吗?

标签: node.js angular angular-universal angular-animations


【解决方案1】:

是的,如果您知道如何让 Angular 知道请求是由 SSR 服务器发出的,那么有更好的方法来实现动画。

就这样吧。

可以看到server.ts

有一个注入令牌被注入。

providers: [
    { provide: APP_BASE_HREF, useValue: req.baseUrl },

现在您需要做的是转到组件并注入此令牌,如下所示。

constructor(
@Optional() @Inject(APP_BASE_HREF) private basehref: string,

现在basehref 如果不是 SSR,则为 null,如果通过 SSR 请求,则为一些字符串值。

现在设备只有在basehref 为空时才能执行动画。

【讨论】:

  • 非常感谢。您认为 isPlatformBrowser 的用法更明确吗?归根结底,这是另一面旗帜。此问题已作为错误提交,在未来的版本中甚至可能不需要我的解决方法,因为他们可能会在即将发布的版本中修复它
  • 我刚刚看到了这个方法,我认为最好使用这个方法。由于我从未遇到过这种方法,因为我的项目仍在开发中,到目前为止还没有使用任何动画。 :)
猜你喜欢
  • 1970-01-01
  • 2019-01-13
  • 1970-01-01
  • 1970-01-01
  • 2020-10-30
  • 2020-10-01
  • 1970-01-01
  • 2017-05-17
  • 1970-01-01
相关资源
最近更新 更多