diff --git a/projects/igniteui-angular/src/lib/directives/for-of/for_of.directive.spec.ts b/projects/igniteui-angular/src/lib/directives/for-of/for_of.directive.spec.ts index d4e2e4f1d3a..c165684a93e 100644 --- a/projects/igniteui-angular/src/lib/directives/for-of/for_of.directive.spec.ts +++ b/projects/igniteui-angular/src/lib/directives/for-of/for_of.directive.spec.ts @@ -1,22 +1,5 @@ -import { AsyncPipe, NgClass, NgForOfContext } from '@angular/common'; -import { - AfterViewInit, - ChangeDetectorRef, - Component, - Directive, - Injectable, - IterableDiffers, - NgZone, - OnInit, - QueryList, - TemplateRef, - ViewChild, - ViewChildren, - ViewContainerRef, - DebugElement, - Pipe, - PipeTransform -} from '@angular/core'; +import { AsyncPipe, NgClass, NgForOfContext } from '@angular/common'; +import { AfterViewInit, ChangeDetectorRef, Component, Directive, Injectable, IterableDiffers, NgZone, OnInit, QueryList, TemplateRef, ViewChild, ViewChildren, ViewContainerRef, DebugElement, Pipe, PipeTransform, provideZonelessChangeDetection } from '@angular/core'; import { TestBed, ComponentFixture, waitForAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { BehaviorSubject, Observable } from 'rxjs'; @@ -1246,6 +1229,34 @@ describe('IgxForOf directive -', () => { }); }); + describe('zoneless', () => { + let fix: ComponentFixture; + + beforeEach(async () => { + TestBed.resetTestingModule(); + await TestBed.configureTestingModule({ + imports: [VerticalVirtualComponent], + providers: [provideZonelessChangeDetection()] + }).compileComponents(); + + fix = TestBed.createComponent(VerticalVirtualComponent); + dg.generateData(300, 5, fix.componentInstance); + fix.detectChanges(); + }); + + it('should call recalcUpdateSizes after vertical scroll', async () => { + const virtDir = fix.componentInstance.parentVirtDir; + const spy = spyOn(virtDir, 'recalcUpdateSizes').and.callThrough(); + + fix.componentInstance.scrollTop(300); + await wait(100); + fix.detectChanges(); + + expect(spy).toHaveBeenCalled(); + }); + + }); + describe('on create new instance', () => { let fix: ComponentFixture; diff --git a/projects/igniteui-angular/src/lib/directives/for-of/for_of.directive.ts b/projects/igniteui-angular/src/lib/directives/for-of/for_of.directive.ts index 6548e3a3a04..19fa95c3a0f 100644 --- a/projects/igniteui-angular/src/lib/directives/for-of/for_of.directive.ts +++ b/projects/igniteui-angular/src/lib/directives/for-of/for_of.directive.ts @@ -22,7 +22,11 @@ import { AfterViewInit, Inject, booleanAttribute, - DOCUMENT + DOCUMENT, + afterNextRender, + runInInjectionContext, + Injector, + inject } from '@angular/core'; import { DisplayContainerComponent } from './display.container'; @@ -407,6 +411,8 @@ export class IgxForOfDirective extends IgxForOfToken this.igxForOf.length; } + protected readonly _injector = inject(Injector); + constructor( private _viewContainer: ViewContainerRef, protected _template: TemplateRef>, @@ -815,6 +821,10 @@ export class IgxForOfDirective extends IgxForOfToken 5; } + protected isZonelessChangeDetection(): boolean { + return this._zone.constructor.name === 'NoopNgZone'; + } + /** * @hidden * Function that recalculates and updates cache sizes. @@ -925,10 +935,21 @@ export class IgxForOfDirective extends IgxForOfToken { + afterNextRender({ + mixedReadWrite: () => { + this.recalcUpdateSizes(); + } + }); + }); + } else { + this._zone.onStable.pipe(first()).subscribe(this.recalcUpdateSizes.bind(this)); + } this.dc.changeDetectorRef.detectChanges(); if (prevStartIndex !== this.state.startIndex) { @@ -1121,7 +1142,18 @@ export class IgxForOfDirective extends IgxForOfToken { + afterNextRender({ + mixedReadWrite: () => { + this.recalcUpdateSizes(); + } + }); + }); + } else { + this._zone.onStable.pipe(first()).subscribe(this.recalcUpdateSizes.bind(this)); + } this.dc.changeDetectorRef.detectChanges(); if (prevStartIndex !== this.state.startIndex) { @@ -1713,10 +1745,21 @@ export class IgxGridForOfDirective extends IgxForOfDirec this._bScrollInternal = false; } const scrollOffset = this.fixedUpdateAllElements(this._virtScrollPosition); + const isZoneless = this.isZonelessChangeDetection(); this.dc.instance._viewContainer.element.nativeElement.style.top = -(scrollOffset) + 'px'; - this._zone.onStable.pipe(first()).subscribe(this.recalcUpdateSizes.bind(this)); + if (isZoneless) { + runInInjectionContext(this._injector, () => { + afterNextRender({ + mixedReadWrite: () => { + this.recalcUpdateSizes(); + } + }); + }); + } else { + this._zone.onStable.pipe(first()).subscribe(this.recalcUpdateSizes.bind(this)); + } this.cdr.markForCheck(); } diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts index c9d96785aec..76036390551 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts @@ -3806,7 +3806,7 @@ export abstract class IgxGridBaseDirective implements GridType, destructor ) .subscribe(() => { - this.zone.run(() => { + const work = () => { // do not trigger reflow if element is detached. if (this.nativeElement.isConnected) { if (this.shouldResize) { @@ -3821,7 +3821,12 @@ export abstract class IgxGridBaseDirective implements GridType, } this.notifyChanges(true); } - }); + }; + if (this.isZonelessChangeDetection()) { + work(); + } else { + this.zone.run(work); + } }); this.pipeTriggerNotifier.pipe(takeUntil(this.destroy$)).subscribe(() => this.pipeTrigger++); @@ -4530,6 +4535,10 @@ export abstract class IgxGridBaseDirective implements GridType, return this.elementRef.nativeElement; } + protected isZonelessChangeDetection(): boolean { + return this.zone.constructor.name === 'NoopNgZone'; + } + /** * Gets/Sets the outlet used to attach the grid's overlays to. *