Angular 9 Universal SSR не работает на ленивом загруженном маршруте с HTTP-вызовом

У меня есть приложение Angular 9 (v9.0.6), корректно работающее с Universal (SSR), и я выполнял последний набор тестов перед переходом на PROD, когда я заметил, что приложение зависает со 100% потреблением ЦП. Я проанализировал ошибку и оказался проблемой, когда приложение Angular загружает напрямую маршрут, который представляет собой ленивый загружаемый модуль, который выполняет HTTP-вызов.

Если я загружаю angular через Home (или другой маршрут без HTTP - но даже домашний, другой ленивый загруженный модуль имеет тот же компонент, который выполняет HTTP-вызов), все работает нормально. Я могу без проблем перемещаться по всем маршрутам, а приложение работает как шарм. Если, однако, я перейду, скажем, на www.mywebsite / lazy-loaded-module прямо на новую вкладку, я считаю, что в процессе начальной загрузки есть что-то, что препятствует правильной регистрации всех регистраций или, по крайней мере, HTTPClientModule и тогда я получаю отказ.

Начнем с того, что HTTPClientModule зарегистрирован только один раз в AppModule. Я получаю следующую ошибку:

(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.

ЦП переходит на 100%, и приложение зависает. Опять же, этого не произойдет, если я перейду к www.my-website.com, а затем перейду через приложение к /my-lazy-loaded-module

Тогда я попробовал что-то еще: включая (я знаю, что не должен) HTTPClientModule внутри моего ленивого загруженного модуля, и он работал, как и проблема с процессором, исчезла, но у меня появилась другая ошибка:

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.


person Carlos Torrecillas    schedule 13.04.2020    source источник
comment
Есть ли у вас провайдеры для токена APP_INITIALIZER?   -  person David    schedule 13.04.2020
comment
Нет, у меня их нет. Я должен настроить его так, чтобы каким-то образом http стал доступен до перехода к ленивому загруженному модулю?   -  person Carlos Torrecillas    schedule 13.04.2020
comment
Нет, нет, мне просто интересно, не вызывает ли APP_INITIALIZER проблему   -  person David    schedule 13.04.2020
comment
Я только что заметил, что есть еще один модуль с почти такой же структурой, и он отлично работает. Я не уверен, что происходит, но если я не выполняю HTTP-вызов, приложение работает нормально   -  person Carlos Torrecillas    schedule 13.04.2020
comment
Можете ли вы попробовать сделать серверную часть запроса API (например, используя curl) на тот же URL-адрес, который вызывается ленивым модулем?   -  person David    schedule 13.04.2020
comment
В конце концов, проблема была не в этом. Это было связано с бесконечным циклом, запускаемым угловой анимацией - см. Мое последнее обновление   -  person Carlos Torrecillas    schedule 15.04.2020
comment
О, хорошо знать   -  person David    schedule 15.04.2020


Ответы (1)


Да, есть лучший способ добиться анимации, если вы знаете, как сообщить angular, что запрос размещается сервером SSR.

Итак, вот оно.

Вы можете увидеть server.ts

вводится токен инъекции.

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

Теперь вам нужно перейти к компоненту и ввести этот токен, как показано ниже.

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

Теперь basehref будет иметь значение null, если это не SSR, и какое-то строковое значение, если оно запрошено через SSR.

теперь устройство способ выполнять анимацию только тогда, когда basehref имеет значение null.

person Vipin Jain    schedule 15.04.2020
comment
Большое спасибо за это. Как вы думаете, более явным является использование isPlatformBrowser? В конце концов, это еще один флаг. Эта проблема была зарегистрирована как ошибка, и мое решение может даже не понадобиться в будущих выпусках, поскольку они могут исправить это в следующей версии. - person Carlos Torrecillas; 21.05.2020
comment
Я только что увидел метод и думаю, что лучше использовать этот метод. Поскольку я никогда не сталкивался с этим методом, так как мой проект все еще находится в разработке и на данный момент не использует анимацию. :) - person Vipin Jain; 22.05.2020