Dismiss Modal in Nativescript Angular Can Take Multiple Seconds to Respond

未关闭
#8,419 1 条评论 2 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

评估

难度
4/5
预计耗时
3-5 天
新手友好度
35/100
Issue 类型
缺陷
描述清晰度
基本清楚
活跃度
停滞
技术栈
angular, typescript
领域
mobile-dev

调研方向

从 nativescript-core/ui/core/view-base/view-base.ts 中的 _dialogClosed 开始,跟踪其 eachDescendant 遍历。在 iOS 上使用嵌套的 Angular ng-content 于非全屏 modal 中重现该问题,然后验证将其关闭不再导致数百万次调用或持续数秒的延迟,包括通过滑动关闭时也是如此。

由索引模型根据 Issue 内容生成。

描述

os: ios

Environment

  • CLI: 6.4.0
  • Cross-platform modules: 6.4.1
  • Android Runtime: 6.4.1
  • iOS Runtime: 6.4.0
  • Plugin(s):
    Nativescript Angular

Describe the bug
Apps with large numbers of nested angular components in modal views begin to take exponentially longer to close.

This seems to only affect modals presented in non-fullscreen mode. Modals presented in fullscreen seem to be unaffected.

To Reproduce
Make normal use of in Angular. The more items you nest, the larger the delay will be, exponentially.

Currently only tested for iOS.

Additional context
Recently I went back through our app and increased the number of angular components used to better clean up the code base and better make use of Angular components and templating.

I noticed that some modals took an exceptionally long time to close, and began following the stack trace to see what was going on.

I followed it to a statement that calls _dialogClosed() in view-base.ts, and notice that the stack took several seconds to return from this function.

I modified the function to see roughly how many times _dialogClose() would be called:

...
    let ENTRY = undefined;
    let TOTAL_COUNT = 0;

    ViewBase.prototype._dialogClosed = function () {
        if (!ENTRY) {
            ENTRY = this;
            console.log('Beginning _dialogClosed call.');
            TOTAL_COUNT = 0;
        }

        eachDescendant(this, function (child) {
            child._dialogClosed();
            TOTAL_COUNT += 1;
            return true;
        });

        if (ENTRY === this) {
            console.log('Finished notifying children of close. Total count: ' + TOTAL_COUNT);
            ENTRY = undefined;
        }
    };
...

I opened my modal, then closed it and got the following output:

CONSOLE LOG file:///node_modules/@nativescript/core/ui/core/view-base/view-base.js:695:0: Beginning _dialogClosed call.
CONSOLE LOG file:///node_modules/@nativescript/core/ui/core/view-base/view-base.js:706:0: Finished notifying children of close. Total count: 20611107

So in my case this function was called 20611107 times, and causes a delay of about 14 seconds before the modal responds.

Another modal with essentially only an image and label on it encounter a similar issue:

Simulator Screen Shot - iPhone 11 - 2020-03-07 at 21 12 15

CONSOLE LOG file:///node_modules/@nativescript/core/ui/core/view-base/view-base.js:695:0: Beginning _dialogClosed call.
CONSOLE LOG file:///node_modules/@nativescript/core/ui/core/view-base/view-base.js:706:0: Finished notifying children of close. Total count: 872483

This modal sees about a second's worth of delay when closing.

Expected behavior
There should not be a massive performance hit and delay when closing modals with nested Angular components. Rendering these items is essentially instant, and closing the modal should be too.

(Partial) Work Around
You can get around the issue by having Angular delete most of the components before dismissing the modal.

I updated one view to use *ngIf and destroy all content under it when about to close the modal.

Component:

<StackLayout *ngIf="!closing">
      <ng-content></ng-content>
</StackLayout>

Code:

closeModal(): void {
    if (!this.closing) {
     this.closing = true;

      this._ngZone.run(() => {
       // Call in ngZone to ensure Angular is notified of the closing change.

        setTimeout(() => {
         // Delay to allow the *ngIf to update and destroy children.
          this._dismissModal();
        }, 10);
      });
    }
}

This causes the second example modal pictured above to output the following:

CONSOLE LOG file:///node_modules/@nativescript/core/ui/core/view-base/view-base.js:695:0: Beginning _dialogClosed call.
CONSOLE LOG file:///node_modules/@nativescript/core/ui/core/view-base/view-base.js:706:0: Finished notifying children of close. Total count: 43

So by destroying all components before calling dismiss it doesn't have to traverse the chain.

The issue is though if that dismissing via a swipe down gesture doesn't hit this code, so the view will get dismissed but the app will still hang while it called _dialogClosed() recursively.

I've got a workaround for that too, but that's beyond the scope of this ticket and a bit more complex.

Sample project
I will add a sample project when I get a chance, there isn't anything special though. Essentially the second modal component's elements looks like:

<StackLayout>
   <PopupPageView>
     ... // ng-content
      <LoadingView>
         ... // ng-content
         <ExampleModalContent>
            <StylingContentComponent>
               ... // ng-content
                  <Image>
                  <Label>

Only PopupPageView, LoadingView, and StylingContentComponent has an ng-content, along with a 1-2 StackLayouts within.

The ExampleModalContent represents that pictured modal.

The first modal example _dialogClosed calls had 1-2 more nested sections within it.

主要语言
TypeScript
星标
25.7k
派生
1.7k
平均合并
1 天 5 小时
30 天内合并 PR
35

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

NativeScript/NativeScript 的其他 Issue

查看 NativeScript/NativeScript 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。