简体   繁体   English

Bootstrap 手风琴导致无限重新渲染

[英]Bootstrap accordion causes infinite re-renders

I have just upgrade an Angular project from version 11 to 12, including an update to @ng-bootstrap/ng-bootstrap (from v6 to v10).我刚刚将一个 Angular 项目从版本 11 升级到 12,包括对@ng-bootstrap/ng-bootstrap (从 v6 到 v10)的更新。

The upgrade went great without any difficult changes, however I am now facing a weird issue with the Bootstrap accordion.升级非常顺利,没有任何困难的变化,但是我现在面临 Bootstrap 手风琴的一个奇怪问题。 I have noticed that it continuously re-renders on the page, causing a maximum call stack error.我注意到它不断在页面上重新渲染,导致最大调用堆栈错误。


Update: I have determine the exact version that caused this issue.更新:我已经确定了导致此问题的确切版本。 It is version 8.0.3 that only includes bug fixes for animations.它是8.0.3版,仅包含动画错误修复

animations: don't reflow in 'ngbRunTransition' (#3962) (f699999), closes #3954 #3952
animations: make sure 'ngbRunTransition' runs inside the zone (#3957) (a006a62), closes #3950
carousel: respect [animation]="false" (#3964) (9afae34), closes #3961

The below error occurs as soon as I load the page.一旦我加载页面,就会发生以下错误。 What is strange is after it occurs I can interact with it.奇怪的是,它发生后我可以与之交互。

zone.js:182 Uncaught RangeError: Maximum call stack size exceeded
    at SafeSubscriber.__tryOrUnsub (Subscriber.js:191)
    at SafeSubscriber.next (Subscriber.js:122)
    at Subscriber._next (Subscriber.js:72)
    at Subscriber.next (Subscriber.js:49)
    at EventEmitter_.next (Subject.js:39)
    at EventEmitter_.emit (core.js:25935)
    at checkStable (core.js:28594)
    at onLeave (core.js:28722)
    at Object.onInvoke (core.js:28678)
    at ZoneDelegate.invoke (zone.js:371)

There is some very strange behaviour about how it errors dependant on how many items are in the accordion.关于它如何错误取决于手风琴中有多少项目有一些非常奇怪的行为。

1-3 items 1-3 项

After it has errored, you can interact with the accordion as if there was no errors.发生错误后,您可以与手风琴交互,就好像没有错误一样。

4 items 4 项

After it has errored, it renders the first two panels as expect, renders a blank 3 panel and doesn't render then 4th.出错后,它按预期呈现前两个面板,呈现空白的 3 面板,然后不呈现 4th。 When you click on any panel it then renders the 4th and you can interact with the accordion as if there was no errors.当您单击任何面板时,它将呈现第 4 个面板,您可以与手风琴交互,就好像没有错误一样。 The 3rd panel remains blank and unclickable.第三个面板保持空白且无法点击。

在此处输入图片说明

5 items 5 项

After it has errored, you can interact with the accordion as if there was no errors.发生错误后,您可以与手风琴交互,就好像没有错误一样。

6 items 6 项

it doesn't render at all but leaves 2 tabs (the panels) in the html它根本不呈现,但在 html 中留下 2 个选项卡(面板)

<ngb-accordion _ngcontent-serverapp-c144="" role="tablist" activeids="acc-event_description" class="accordion" ng-reflect-active-ids="acc-event_description" aria-multiselectable="true"><!--container--><div><div role="tab"><!--container--></div><!--container--></div><div><div role="tab"><!--container--></div><!--container--></div><!--bindings={
  "ng-reflect-ng-for-of": "[object Object],[object Object"
}--></ngb-accordion>

Another strange behaviour I have noticed is that when another element on the page updates, it causes the accordion to update several times.我注意到的另一个奇怪的行为是,当页面上的另一个元素更新时,它会导致手风琴更新多次。 This behaviour persisted from before the update.此行为在更新之前一直存在。

在此处输入图片说明


Update: After some thought, I think this error is a symptom of another problem in the project.更新:经过一番思考,我认为这个错误是项目中另一个问题的征兆。 Is there common patterns/code/implementations that would cause this bizarre behaviour?是否有常见的模式/代码/实现会导致这种奇怪的行为? The continuous re-rendering does appear even in the old version of the accordion, however it just doesn't happen enough to cause a max call stack error.即使在旧版本的手风琴中也确实出现了连续重新渲染,但是它并没有发生到导致最大调用堆栈错误的程度。


Troubleshooting故障排除

  • I have verified this error persists in the exact same way when I do a dev/prod build and also when using angular universal (SSR).我已经验证,当我进行开发/生产构建以及使用 angular Universal (SSR) 时,此错误仍然以完全相同的方式存在。
  • I can avoid the error by not setting an id on the panels however when I click on a panel, the component re-renders in very quick succession incrementing the id it assigned automatically (it updates so quickly that after a sec it is already incremented the ids to over 100 ).我可以通过不在面板上设置id来避免错误,但是当我单击面板时,组件会非常快速地连续重新渲染并自动增加它分配的id (它更新得很快,一秒钟后它已经增加了ids 超过100 )。 When I don't set an id the accordion does not work, clicking on a panel does nothing but update its id .当我不设置id ,手风琴不起作用,单击面板只会更新其id
  • I have tried to set [animation]="false" but it doesn't have any impact我试图设置[animation]="false"但它没有任何影响
  • I have tried to import NgbModule and nothing else, and the other approach to import the specific NgbAccordionModule however it doesn't make a difference我试图导入NgbModule而没有其他任何东西,以及导入特定NgbAccordionModule的另一种方法,但是它没有什么区别
  • I have tried loading just the accordion component as its own module (so there is no influence from any other code) and it still has the same behaviour.我已经尝试将手风琴组件作为它自己的模块加载(因此没有任何其他代码的影响)并且它仍然具有相同的行为。

I have tried to use the Angular devbug tools however since they can't run as soon as the page loads (automatically) I can't run the profiler in time.我曾尝试使用 Angular devbug 工具,但是因为它们无法在页面加载(自动)后立即运行,所以我无法及时运行分析器。

I have set up an example project with what the component looks like and it works fine, so there is something occurring in my project that is now causing this but I can't seem to work it out.我已经使用组件的外观设置了一个示例项目并且它工作正常,所以我的项目中发生了一些事情,现在导致了这个问题,但我似乎无法解决。

This is what the component looks like - https://stackblitz.com/edit/angular-tp9ldq这是组件的样子 - https://stackblitz.com/edit/angular-tp9ldq

The only way I can avoid it, is to hard code the panels into the HTML.我可以避免它的唯一方法是将面板硬编码到 HTML 中。 Which I can not do as they are dynamically fetch.这是我不能做的,因为它们是动态获取的。 I have tried hard coded them into the component (just like the stack blitz link) and it still errors.我已经尝试将它们硬编码到组件中(就像堆栈闪电战链接一样),但它仍然出错。

Here is the dependencies I am using, is there anything obvious here that needs to be upgraded?这是我正在使用的依赖项,这里有什么明显需要升级的地方吗? When I run ng update it says everything seems to be in order.当我运行ng update它说一切似乎都井井有条。

"dependencies": {
  "@angular/animations": "~12.2.5",
  "@angular/common": "~12.2.5",
  "@angular/compiler": "~12.2.5",
  "@angular/core": "~12.2.5",
  "@angular/forms": "~12.2.5",
  "@angular/localize": "~12.2.5",
  "@angular/platform-browser": "~12.2.5",
  "@angular/platform-browser-dynamic": "~12.2.5",
  "@angular/platform-server": "~12.2.5",
  "@angular/router": "~12.2.5",
  "@auth0/angular-jwt": "^5.0.2",
  "@bugsnag/js": "^7.4.0",
  "@bugsnag/plugin-angular": "^7.4.0",
  "@ng-bootstrap/ng-bootstrap": "^10.0.0",
  "@nguniversal/express-engine": "^12.1.0",
  "@techiediaries/ngx-qrcode": "^9.1.0",
  "@types/url-parse": "^1.4.3",
  "bootstrap": "^4.5.0",
  "core-js": "^3.17.3",
  "express": "^4.17.1",
  "hammerjs": "^2.0.8",
  "jquery": "^3.3.1",
  "less": "4.1.1",
  "lodash": "^4.17.21",
  "logrocket": "^1.0.3",
  "moment": "^2.24.0",
  "ngx-barcode": "^0.3.0",
  "ngx-connection-service": "^7.0.3",
  "ngx-cookie": "^5.0.2",
  "ngx-cookie-backend": "^5.0.2",
  "popper.js": "^1.14.7",
  "rxjs": "~6.6.7",
  "sanitize-html": "^2.5.0",
  "tslib": "^2.3.1",
  "url-parse": "^1.4.7",
  "zone.js": "~0.11.4"
},
"devDependencies": {
  "@angular-devkit/build-angular": "~12.2.5",
  "@angular-eslint/builder": "12.4.1",
  "@angular-eslint/eslint-plugin": "12.4.1",
  "@angular-eslint/eslint-plugin-template": "12.4.1",
  "@angular-eslint/schematics": "12.4.1",
  "@angular-eslint/template-parser": "12.4.1",
  "@angular/cli": "~12.2.5",
  "@angular/compiler-cli": "~12.2.5",
  "@angular/language-service": "~12.2.5",
  "@nguniversal/builders": "12.1.0",
  "@types/core-js": "^2.5.5",
  "@types/express": "^4.17.13",
  "@types/jasmine": "~2.8.8",
  "@types/jasminewd2": "~2.0.3",
  "@types/lodash": "^4.14.172",
  "@types/node": "^16.9.0",
  "@types/sanitize-html": "^2.3.2",
  "@types/stripe-checkout": "^1.0.4",
  "@types/stripe-v3": "^3.1.25",
  "@typescript-eslint/eslint-plugin": "4.28.2",
  "@typescript-eslint/parser": "4.28.2",
  "codelyzer": "^6.0.2",
  "eslint": "^7.26.0",
  "jasmine-core": "~2.99.1",
  "jasmine-spec-reporter": "~4.2.1",
  "karma": "^5.1.1",
  "karma-chrome-launcher": "~2.2.0",
  "karma-coverage-istanbul-reporter": "~2.0.1",
  "karma-firefox-launcher": "^2.1.0",
  "karma-jasmine": "~1.1.2",
  "karma-jasmine-html-reporter": "^0.2.2",
  "karma-junit-reporter": "^1.2.0",
  "karma-spec-reporter": "0.0.32",
  "npm-run-all": "^4.1.5",
  "protractor": "^7.0.0",
  "rimraf": "^3.0.2",
  "ts-node": "^10.2.1",
  "tslint": "^6.1.3",
  "typescript": "~4.3.5"
},

And here is the build section of my angular project这是我的角度项目的构建部分

"build": {
  "builder": "@angular-devkit/build-angular:browser",
  "options": {
    "outputPath": "dist/frontend/browser",
    "index": "src/index.html",
    "main": "src/main.ts",
    "polyfills": "src/polyfills.ts",
    "tsConfig": "src/tsconfig.app.json",
    "assets": ["src/assets", "src/.well-known"],
    "allowedCommonJsDependencies": ["lodash", "@bugsnag/js", "ngx-barcode", "url-parse", "sanitize-html"],
    "styles": ["node_modules/bootstrap/dist/css/bootstrap.min.css", "src/styles.less"],
    "scripts": [
      "./node_modules/jquery/dist/jquery.slim.min.js",
      "./node_modules/popper.js/dist/umd/popper.min.js"
    ]
  },
  "configurations": {
    "production": {
      "fileReplacements": [
        {
          "replace": "src/environments/environment.ts",
          "with": "src/environments/environment.prod.ts"
        }
      ],

      "outputHashing": "all",
      "budgets": [
        {
          "type": "initial",
          "maximumWarning": "2mb",
          "maximumError": "5mb"
        },
        {
          "type": "anyComponentStyle",
          "maximumWarning": "6kb"
        }
      ]
    },
    "development": {
      "buildOptimizer": false,
      "optimization": false,
      "vendorChunk": true,
      "extractLicenses": false,
      "sourceMap": true,
      "namedChunks": true
    }
  },
  "defaultConfiguration": "production"
},

Faced the same problem, a temporal solution could be creating an empty array with the same length as te original one, and use it for the *ngFor loop.面对同样的问题,临时解决方案可能是创建一个与原始数组长度相同的空数组,并将其用于 *ngFor 循环。 Then inside access the elements of the original array using the index position.然后在内部使用索引位置访问原始数组的元素。

Component ts:组件 ts:

accordionSamples = [
   ...
]
accordionSamplesLengthArray = [].constructor(accordionSamples.length);

Component template:组件模板:

<ngb-panel id="pos-{{i}}" *ngFor="obj of accordionSamplesLengthArray; let i = index;">
     <ng-template ngbPanelHeader>
         {{ accordionSamples[i].title }}
     </ng-template>
</ngb-panel>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM