Skip to content

Commit

Permalink
refactor(module:core): expose fromEventOutsideAngular utility (#8877)
Browse files Browse the repository at this point in the history
In this commit, we introduce a helper function, `fromEventOutsideAngular`, as a replacement for
`ngZone.runOutsideAngular + fromEvent`. Using this helper function simplifies things, as there is
no longer a need to inject the `NgZone` service everywhere.

Note that there are no functional changes—only formatting changes, as indentation has been
shifted to the left.

---------

Co-authored-by: Laffery <[email protected]>
  • Loading branch information
arturovt and Laffery authored Nov 26, 2024
1 parent 4498af0 commit cb013f0
Show file tree
Hide file tree
Showing 47 changed files with 994 additions and 981 deletions.
15 changes: 6 additions & 9 deletions components/anchor/anchor.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
EventEmitter,
inject,
Input,
NgZone,
numberAttribute,
OnChanges,
OnDestroy,
Expand All @@ -25,14 +24,14 @@ import {
ViewChild,
ViewEncapsulation
} from '@angular/core';
import { fromEvent, Subject } from 'rxjs';
import { Subject } from 'rxjs';
import { takeUntil, throttleTime } from 'rxjs/operators';

import { NzAffixModule } from 'ng-zorro-antd/affix';
import { NzConfigKey, NzConfigService, WithConfig } from 'ng-zorro-antd/core/config';
import { NzScrollService } from 'ng-zorro-antd/core/services';
import { NgStyleInterface, NzDirectionVHType } from 'ng-zorro-antd/core/types';
import { numberAttributeWithZeroFallback } from 'ng-zorro-antd/core/util';
import { fromEventOutsideAngular, numberAttributeWithZeroFallback } from 'ng-zorro-antd/core/util';

import { NzAnchorLinkComponent } from './anchor-link.component';
import { getOffsetTop } from './util';
Expand Down Expand Up @@ -129,7 +128,6 @@ export class NzAnchorComponent implements OnDestroy, AfterViewInit, OnChanges {
private scrollSrv: NzScrollService,
private cdr: ChangeDetectorRef,
private platform: Platform,
private zone: NgZone,
private renderer: Renderer2
) {}

Expand Down Expand Up @@ -160,11 +158,10 @@ export class NzAnchorComponent implements OnDestroy, AfterViewInit, OnChanges {
return;
}
this.destroy$.next(true);
this.zone.runOutsideAngular(() => {
fromEvent(this.getContainer(), 'scroll', <AddEventListenerOptions>passiveEventListenerOptions)
.pipe(throttleTime(50), takeUntil(this.destroy$))
.subscribe(() => this.handleScroll());
});

fromEventOutsideAngular(this.getContainer(), 'scroll', passiveEventListenerOptions)
.pipe(throttleTime(50), takeUntil(this.destroy$))
.subscribe(() => this.handleScroll());
// Browser would maintain the scrolling position when refreshing.
// So we have to delay calculation in avoid of getting a incorrect result.
this.handleScrollTimeoutID = setTimeout(() => this.handleScroll());
Expand Down
30 changes: 14 additions & 16 deletions components/auto-complete/autocomplete-option.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ import {
booleanAttribute,
inject
} from '@angular/core';
import { Subject, fromEvent } from 'rxjs';
import { Subject } from 'rxjs';
import { filter, takeUntil } from 'rxjs/operators';

import { NzSafeAny } from 'ng-zorro-antd/core/types';
import { scrollIntoView } from 'ng-zorro-antd/core/util';
import { fromEventOutsideAngular, scrollIntoView } from 'ng-zorro-antd/core/util';

import { NzAutocompleteOptgroupComponent } from './autocomplete-optgroup.component';

Expand Down Expand Up @@ -77,20 +77,18 @@ export class NzAutocompleteOptionComponent implements OnInit, OnDestroy {
) {}

ngOnInit(): void {
this.ngZone.runOutsideAngular(() => {
fromEvent(this.element.nativeElement, 'mouseenter')
.pipe(
filter(() => this.mouseEntered.observers.length > 0),
takeUntil(this.destroy$)
)
.subscribe(() => {
this.ngZone.run(() => this.mouseEntered.emit(this));
});

fromEvent(this.element.nativeElement, 'mousedown')
.pipe(takeUntil(this.destroy$))
.subscribe(event => event.preventDefault());
});
fromEventOutsideAngular(this.element.nativeElement, 'mouseenter')
.pipe(
filter(() => this.mouseEntered.observers.length > 0),
takeUntil(this.destroy$)
)
.subscribe(() => {
this.ngZone.run(() => this.mouseEntered.emit(this));
});

fromEventOutsideAngular(this.element.nativeElement, 'mousedown')
.pipe(takeUntil(this.destroy$))
.subscribe(event => event.preventDefault());
}

ngOnDestroy(): void {
Expand Down
33 changes: 15 additions & 18 deletions components/back-top/back-top.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { Direction, Directionality } from '@angular/cdk/bidi';
import { Platform, normalizePassiveListenerOptions } from '@angular/cdk/platform';
import { DOCUMENT, NgIf, NgTemplateOutlet } from '@angular/common';
import { DOCUMENT, NgTemplateOutlet } from '@angular/common';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Expand All @@ -25,12 +25,13 @@ import {
inject,
numberAttribute
} from '@angular/core';
import { Subject, Subscription, fromEvent } from 'rxjs';
import { Subject, Subscription } from 'rxjs';
import { debounceTime, takeUntil } from 'rxjs/operators';

import { fadeMotion } from 'ng-zorro-antd/core/animation';
import { NzConfigKey, NzConfigService, WithConfig } from 'ng-zorro-antd/core/config';
import { NzDestroyService, NzScrollService } from 'ng-zorro-antd/core/services';
import { fromEventOutsideAngular } from 'ng-zorro-antd/core/util';
import { NzIconModule } from 'ng-zorro-antd/icon';

const NZ_CONFIG_MODULE_NAME: NzConfigKey = 'backTop';
Expand All @@ -42,7 +43,7 @@ const passiveEventListenerOptions = normalizePassiveListenerOptions({ passive: t
exportAs: 'nzBackTop',
animations: [fadeMotion],
standalone: true,
imports: [NgIf, NgTemplateOutlet, NzIconModule],
imports: [NgTemplateOutlet, NzIconModule],
template: `
@if (visible) {
<div #backTop class="ant-back-top" [class.ant-back-top-rtl]="dir === 'rtl'" @fadeMotion>
Expand Down Expand Up @@ -82,16 +83,14 @@ export class NzBackTopComponent implements OnInit, OnDestroy, OnChanges {
if (backTop) {
this.backTopClickSubscription.unsubscribe();

this.backTopClickSubscription = this.zone.runOutsideAngular(() =>
fromEvent(backTop.nativeElement, 'click')
.pipe(takeUntil(this.destroy$))
.subscribe(() => {
this.scrollSrv.scrollTo(this.getTarget(), 0, { duration: this.nzDuration });
if (this.nzClick.observers.length) {
this.zone.run(() => this.nzClick.emit(true));
}
})
);
this.backTopClickSubscription = fromEventOutsideAngular(backTop.nativeElement, 'click')
.pipe(takeUntil(this.destroy$))
.subscribe(() => {
this.scrollSrv.scrollTo(this.getTarget(), 0, { duration: this.nzDuration });
if (this.nzClick.observers.length) {
this.zone.run(() => this.nzClick.emit(true));
}
});
}
}

Expand Down Expand Up @@ -139,11 +138,9 @@ export class NzBackTopComponent implements OnInit, OnDestroy, OnChanges {
}
this.scrollListenerDestroy$.next(true);
this.handleScroll();
this.zone.runOutsideAngular(() => {
fromEvent(this.getTarget(), 'scroll', <AddEventListenerOptions>passiveEventListenerOptions)
.pipe(debounceTime(50), takeUntil(this.scrollListenerDestroy$))
.subscribe(() => this.handleScroll());
});
fromEventOutsideAngular(this.getTarget(), 'scroll', <AddEventListenerOptions>passiveEventListenerOptions)
.pipe(debounceTime(50), takeUntil(this.scrollListenerDestroy$))
.subscribe(() => this.handleScroll());
}

ngOnDestroy(): void {
Expand Down
46 changes: 24 additions & 22 deletions components/back-top/back-top.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Platform } from '@angular/cdk/platform';
import { ApplicationRef, Component, DebugElement, SimpleChanges, ViewChild } from '@angular/core';
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { provideNoopAnimations } from '@angular/platform-browser/animations';
import { Subject } from 'rxjs';

import { NzScrollService } from 'ng-zorro-antd/core/services';
Expand All @@ -12,7 +12,7 @@ import { NzSafeAny } from 'ng-zorro-antd/core/types';
import { NzBackTopComponent } from './back-top.component';
import { NzBackTopModule } from './back-top.module';

describe('Component:nz-back-top', () => {
describe('nz-back-top', () => {
let scrollService: MockNzScrollService;
let fixture: ComponentFixture<TestBackTopComponent>;
let debugElement: DebugElement;
Expand All @@ -37,15 +37,14 @@ describe('Component:nz-back-top', () => {

beforeEach(() => {
TestBed.configureTestingModule({
imports: [NzBackTopModule, NoopAnimationsModule],
declarations: [TestBackTopComponent, TestBackTopTemplateComponent],
providers: [
provideNoopAnimations(),
{
provide: NzScrollService,
useClass: MockNzScrollService
}
]
}).compileComponents();
});

fixture = TestBed.createComponent(TestBackTopComponent);
component = fixture.componentInstance.nzBackTopComponent;
Expand Down Expand Up @@ -167,11 +166,11 @@ describe('Component:nz-back-top', () => {

describe('[nzTarget]', () => {
let fakeTarget: HTMLElement;
beforeEach(fakeAsync(() => {
beforeEach(() => {
fakeTarget = debugElement.query(By.css('#fakeTarget')).nativeElement;
fixture.componentInstance.setTarget(fakeTarget);
fixture.detectChanges();
}));
});

it('window scroll does not show the button', fakeAsync(() => {
componentObject.scrollTo(window, defaultVisibilityHeight + 1);
Expand All @@ -181,27 +180,27 @@ describe('Component:nz-back-top', () => {
expect(componentObject.backTopButton() === null).toBe(true);
}));

it('element scroll shows the button', fakeAsync(() => {
const time = 50;

it('element scroll shows the button', (done: () => void) => {
componentObject.scrollTo(fakeTarget, defaultVisibilityHeight + 1);
tick(time + 1);
fixture.detectChanges();

expect(componentObject.backTopButton() === null).toBe(false);
}));
setTimeout(() => {
expect(componentObject.backTopButton() === null).toBe(false);
done();
}, 50);
});

it('element (use string id) scroll shows the button', fakeAsync(() => {
it('element (use string id) scroll shows the button', (done: () => void) => {
component.nzTarget = '#fakeTarget';

const time = 50;

componentObject.scrollTo(fakeTarget, defaultVisibilityHeight + 1);
tick(time + 1);
fixture.detectChanges();

expect(componentObject.backTopButton() === null).toBe(false);
}));
setTimeout(() => {
expect(componentObject.backTopButton() === null).toBe(false);
done();
}, 50);
});
});

describe('#nzTemplate', () => {
Expand All @@ -218,6 +217,8 @@ describe('Component:nz-back-top', () => {
});

@Component({
standalone: true,
imports: [NzBackTopModule],
template: `
<nz-back-top [nzTarget]="target"></nz-back-top>
<div id="fakeTarget"></div>
Expand All @@ -227,14 +228,16 @@ class TestBackTopComponent {
@ViewChild(NzBackTopComponent, { static: true })
nzBackTopComponent!: NzBackTopComponent;

target: HTMLElement | null = null;
target: HTMLElement | undefined = undefined;

setTarget(target: HTMLElement): void {
this.target = target;
}
}

@Component({
standalone: true,
imports: [NzBackTopModule],
template: `
my comp
<nz-back-top [nzTemplate]="tpl">
Expand Down Expand Up @@ -273,7 +276,6 @@ describe('back-to-top', () => {

beforeEach(() => {
TestBed.configureTestingModule({
imports: [NzBackTopModule],
providers: [
{
provide: Directionality,
Expand All @@ -282,7 +284,7 @@ describe('back-to-top', () => {
{ provide: Platform, useValue: { isBrowser: false } },
{ provide: Document, useValue: document }
]
}).compileComponents();
});

fixture = TestBed.createComponent(NzBackTopComponent);
component = fixture.componentInstance;
Expand Down
31 changes: 14 additions & 17 deletions components/button/button.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
ContentChild,
ElementRef,
Input,
NgZone,
OnChanges,
OnInit,
Renderer2,
Expand All @@ -24,12 +23,13 @@ import {
inject,
signal
} from '@angular/core';
import { Subject, fromEvent } from 'rxjs';
import { Subject } from 'rxjs';
import { filter, startWith, takeUntil } from 'rxjs/operators';

import { NzConfigKey, NzConfigService, WithConfig } from 'ng-zorro-antd/core/config';
import { NzDestroyService } from 'ng-zorro-antd/core/services';
import { NzSizeLDSType } from 'ng-zorro-antd/core/types';
import { fromEventOutsideAngular } from 'ng-zorro-antd/core/util';
import { NzIconDirective, NzIconModule } from 'ng-zorro-antd/icon';
import { NZ_SPACE_COMPACT_ITEM_TYPE, NZ_SPACE_COMPACT_SIZE, NzSpaceCompactItemDirective } from 'ng-zorro-antd/space';

Expand Down Expand Up @@ -128,7 +128,6 @@ export class NzButtonComponent implements OnChanges, AfterViewInit, AfterContent
}

constructor(
private ngZone: NgZone,
private elementRef: ElementRef,
private cdr: ChangeDetectorRef,
private renderer: Renderer2,
Expand All @@ -153,20 +152,18 @@ export class NzButtonComponent implements OnChanges, AfterViewInit, AfterContent

this.dir = this.directionality.value;

this.ngZone.runOutsideAngular(() => {
// Caretaker note: this event listener could've been added through `host.click` or `HostListener`.
// The compiler generates the `ɵɵlistener` instruction which wraps the actual listener internally into the
// function, which runs `markDirty()` before running the actual listener (the decorated class method).
// Since we're preventing the default behavior and stopping event propagation this doesn't require Angular to run the change detection.
fromEvent<MouseEvent>(this.elementRef.nativeElement, 'click', { capture: true })
.pipe(takeUntil(this.destroy$))
.subscribe(event => {
if ((this.disabled && (event.target as HTMLElement)?.tagName === 'A') || this.nzLoading) {
event.preventDefault();
event.stopImmediatePropagation();
}
});
});
// Caretaker note: this event listener could've been added through `host.click` or `HostListener`.
// The compiler generates the `ɵɵlistener` instruction which wraps the actual listener internally into the
// function, which runs `markDirty()` before running the actual listener (the decorated class method).
// Since we're preventing the default behavior and stopping event propagation this doesn't require Angular to run the change detection.
fromEventOutsideAngular<MouseEvent>(this.elementRef.nativeElement, 'click', { capture: true })
.pipe(takeUntil(this.destroy$))
.subscribe(event => {
if ((this.disabled && (event.target as HTMLElement)?.tagName === 'A') || this.nzLoading) {
event.preventDefault();
event.stopImmediatePropagation();
}
});
}

ngOnChanges({ nzLoading, nzSize }: SimpleChanges): void {
Expand Down
Loading

0 comments on commit cb013f0

Please sign in to comment.