2

I have an Angular 9 (v9.0.6) app working correctly with Universal (SSR) in it and I was performing the last set of tests prior moving to PROD when I noticed the application hanging with 100% CPU consumption. I analyzed the error and turned out to be a problem when the Angular app loads directly a route which is a lazy loaded module which performs a HTTP call.

If I load the angular via Home (or another route with no HTTP - but even the home, another lazy loaded module has the same component which makes the HTTP call) eveything works fine. I can navigate to all routes without any issue and the APP works like a charm. If, however I go, to, lets say www.mywebsite/lazy-loaded-module straight in a new tab, I believe there is something in the bootstrap process which is preventing for all tregistrations or at least the HTTPClientModule to be registered correctly and then I get the failure.

To start with, I have the HTTPClientModule registered only once in the AppModule. The error I get is:

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

The CPU goes to 100% and the application hangs. Again, that does not happen if I go to www.my-website.com then I navigate through the app to /my-lazy-loaded-module

I tried something else then: Including (I know I shouldn't) the HTTPClientModule inside my lazy loaded module and it worked as in the CPU issue went away however I got a different error:

ReferenceError: XMLHttpRequest is not defined
    at BrowserXhr.build

Which has an impact in the content I would retrieve using SSR and rendering it because there is none. As a workaround that could do it but I'd like to know how this should be addressed. The node version I have executing is: v10.16.2

UPDATE

To give more visibility around the HTTP call I'm making is just a standard call:

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);
                        });
    }

UPDATE 2

I have updated to Angular 9.1.1 which fixes the buffer warning. The application still hangs, and it's only happening on one module. Other modules are also making a http call (same standard get, but with a different service) without issues.

** UPDATE 3 **

I have found the root cause of the issue. It had nothing to do with HTTP eventually. What was happening was that in the page I'm referring I have an angular animation very simple. It's a triangle which gets bigger or smaller applying an infinite animation via standard angular animation. To achieve the infinite effect I was hooking into the animation.done event to change the state to either big or small. Well, if you hard refresh the page on that route where that animation is placed, you end up on an infinite loop like this:

{ 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 }

And your application hangs. I don't know if there is a better way of achieving infinite animations using Angular animations and whether it should be up to the developer to "know" that SSR will be vulnerable with them. I guess if you think that they deal with HTML elements you could argue that they all should be using isPlatformBrowser within them.

Carlos Torrecillas
  • 4,965
  • 7
  • 38
  • 69

1 Answers1

1

Yes there is a better way of achieving the animations if you know how to let angular know that the request is being placed by SSR server.

So here it goes.

You can see the server.ts

there is a injection token that is injected.

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

now what you need to do is go to component and inject this token like below.

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

Now basehref will be null if it is not SSR and some string value if requested through SSR.

now device a way to perform the animation only when basehref is null.

Vipin Jain
  • 1,382
  • 1
  • 10
  • 19
  • 1
    Many thanks for this. Do you think is more explicit the usage of isPlatformBrowser though? At the end of the day, it’s another flag. This issue was filed as a bug and my workaround may not even be needed in future releases since they may fix it in an upcoming version – Carlos Torrecillas May 20 '20 at 21:25
  • i just saw the method and i think it is better to use this method. Since i have never encountered this method as my project is still in development and is not using any animations as of now. :) – Vipin Jain May 22 '20 at 14:12