diff --git a/modules/benchmarks/src/change_detection/change_detection.e2e-spec.ts b/modules/benchmarks/src/change_detection/change_detection.e2e-spec.ts index 33ba44bce5..0407b31aaf 100644 --- a/modules/benchmarks/src/change_detection/change_detection.e2e-spec.ts +++ b/modules/benchmarks/src/change_detection/change_detection.e2e-spec.ts @@ -23,8 +23,14 @@ describe('change detection benchmark', () => { expect(await $('#root').getText()).toEqual(''); await $('#createDom').click(); expect($('#root').getText()).toContain('1'); + + await $('#markInsertionComponentForCheck').click(); await $('#detectChanges').click(); - expect($('#root').getText()).toContain('2'); + // Ivy currently refreshes at *both* declaration and insertion while VE only refreshes at + // insertion. Simply assert that the view was updated at least once since the first update. + expect(Number(await $('#root').getText())).toBeGreaterThan(1); + + // The button click causes change detection to trigger at the root await $('#destroyDom').click(); expect(await $('#root').getText()).toEqual(''); }); diff --git a/modules/benchmarks/src/change_detection/change_detection.perf-spec.ts b/modules/benchmarks/src/change_detection/change_detection.perf-spec.ts index f450dc5bc8..6abf5d03ba 100644 --- a/modules/benchmarks/src/change_detection/change_detection.perf-spec.ts +++ b/modules/benchmarks/src/change_detection/change_detection.perf-spec.ts @@ -15,8 +15,9 @@ interface Worker { work(): void; } -const UpdateWorker: Worker = { - id: 'createOnly', +// Used to benchmark performance when insertion tree is not dirty. +const InsertionNotDirtyWorker: Worker = { + id: 'insertionNotDirty', prepare: () => { $('#destroyDom').click(); $('#createDom').click(); @@ -24,6 +25,17 @@ const UpdateWorker: Worker = { work: () => $('#detectChanges').click() }; +// Used to benchmark performance when both declaration and insertion trees are dirty. +const AllComponentsDirtyWorker: Worker = { + id: 'allComponentsDirty', + prepare: () => { + $('#destroyDom').click(); + $('#createDom').click(); + $('#markInsertionComponentForCheck').click(); + }, + work: () => $('#detectChanges').click() +}; + // In order to make sure that we don't change the ids of the benchmarks, we need to // determine the current test package name from the Bazel target. This is necessary @@ -36,7 +48,7 @@ const testPackageName = process.env['BAZEL_TARGET']!.split(':')[0].split('/').po describe('change detection benchmark perf', () => { afterEach(verifyNoBrowserErrors); - [UpdateWorker].forEach((worker) => { + [InsertionNotDirtyWorker, AllComponentsDirtyWorker].forEach((worker) => { describe(worker.id, () => { it(`should run benchmark for ${testPackageName}`, async () => { await runChangeDetectionBenchmark({ diff --git a/modules/benchmarks/src/change_detection/transplanted_views/index.html b/modules/benchmarks/src/change_detection/transplanted_views/index.html index f75c7fc447..5e103e6e3c 100644 --- a/modules/benchmarks/src/change_detection/transplanted_views/index.html +++ b/modules/benchmarks/src/change_detection/transplanted_views/index.html @@ -18,6 +18,7 @@

+

diff --git a/modules/benchmarks/src/change_detection/transplanted_views/init.ts b/modules/benchmarks/src/change_detection/transplanted_views/init.ts index 53b6ef336a..9300bbcec4 100644 --- a/modules/benchmarks/src/change_detection/transplanted_views/init.ts +++ b/modules/benchmarks/src/change_detection/transplanted_views/init.ts @@ -19,6 +19,7 @@ export function init(moduleRef: NgModuleRef) { bindAction('#destroyDom', destroyDom); bindAction('#createDom', createDom); + bindAction('#markInsertionComponentForCheck', markInsertionComponentForCheck); bindAction('#detectChanges', detectChanges); bindAction('#detectChangesProfile', profile(detectChanges, noop, 'detectChanges')); @@ -35,6 +36,10 @@ export function init(moduleRef: NgModuleRef) { appRef.tick(); } + function markInsertionComponentForCheck() { + declaration.insertionComponent.changeDetector.markForCheck(); + } + function detectChanges() { appRef.tick(); } diff --git a/modules/benchmarks/src/change_detection/transplanted_views/transplanted_views.ts b/modules/benchmarks/src/change_detection/transplanted_views/transplanted_views.ts index da7746640c..22360dc401 100644 --- a/modules/benchmarks/src/change_detection/transplanted_views/transplanted_views.ts +++ b/modules/benchmarks/src/change_detection/transplanted_views/transplanted_views.ts @@ -6,29 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectionStrategy, Component, Input, NgModule, TemplateRef} from '@angular/core'; +import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, NgModule, TemplateRef, ViewChild} from '@angular/core'; import {BrowserModule} from '@angular/platform-browser'; import {newArray} from '../util'; -@Component({ - selector: 'declaration-component', - template: ` - {{trackTemplateRefresh()}} - - `, -}) -export class DeclarationComponent { - @Input() viewCount = 1; - // Tracks number of times the template was executed to ensure it was updated during CD. - templateRefreshCount = 0; - - trackTemplateRefresh() { - this.templateRefreshCount++; - return this.templateRefreshCount; - } -} - @Component({ selector: 'insertion-component', template: ` @@ -44,12 +26,33 @@ export class InsertionComponent { this.views = n > 0 ? newArray(n) : []; } + constructor(readonly changeDetector: ChangeDetectorRef) {} + // use trackBy to ensure profile isn't affected by the cost to refresh ngFor. trackByIndex(index: number, item: any) { return index; } } +@Component({ + selector: 'declaration-component', + template: ` + {{trackTemplateRefresh()}} + + `, +}) +export class DeclarationComponent { + @Input() viewCount = 1; + @ViewChild(InsertionComponent) insertionComponent!: InsertionComponent; + // Tracks number of times the template was executed to ensure it was updated during CD. + templateRefreshCount = 0; + + trackTemplateRefresh() { + this.templateRefreshCount++; + return this.templateRefreshCount; + } +} + @NgModule({ declarations: [DeclarationComponent, InsertionComponent], bootstrap: [DeclarationComponent],