Dismiss Modal in Nativescript Angular Can Take Multiple Seconds to Respond

オープン
#8,419 コメント 1 件 リアクション 2 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
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 を使って問題を再現し、その後、スワイプで閉じた場合も含め、閉じても数百万回の呼び出しや数秒にわたる遅延が発生しなくなったことを確認します。

索引モデルが 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時間
マージ済み PR(30日)
35

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

NativeScript/NativeScript のほかの issue

NativeScript/NativeScript の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。