UNPKG

@rx-angular/template

Version:

**Fully** Reactive Component Template Rendering in Angular. @rx-angular/template aims to be a reflection of Angular's built in renderings just reactive.

137 lines (133 loc) 5.57 kB
import * as i0 from '@angular/core'; import { Directive, Input } from '@angular/core'; import { getZoneUnPatchedApi } from '@rx-angular/cdk/internals/core'; import { focusEvents, mouseEvents, wheelEvents, inputEvents, keyboardEvents, touchEvents } from '@rx-angular/cdk/zone-configurations'; import { Subscription, BehaviorSubject } from 'rxjs'; const zonePatchedEvents = [ ...focusEvents, ...mouseEvents, ...wheelEvents, ...inputEvents, ...keyboardEvents, ...touchEvents, ]; /** * * @description * * This function takes an elem and event and re-applies the listeners from the passed event to the * passed element with the zone un-patched version of it. * * @param elem {HTMLElement} - The elem to re-apply the listeners to. * @param event {string} - The name of the event from which to re-apply the listeners. * * @returns void */ function unpatchEventListener(element, event) { // `EventTarget` is patched only in the browser environment, thus // running this code on the server-side will throw an exception: // `TypeError: element.eventListeners is not a function`. if (typeof element.eventListeners !== 'function') { return; } const eventListeners = element.eventListeners(event); // Return if no event listeners are present if (!Array.isArray(eventListeners) || eventListeners.length === 0) { return; } const addEventListener = getZoneUnPatchedApi(element, 'addEventListener').bind(element); const listeners = []; eventListeners.forEach((listener) => { // Remove and reapply listeners with patched API element.removeEventListener(event, listener); // Reapply listeners with un-patched API addEventListener(event, listener); listeners.push(listener); }); return listeners; } /** * @Directive RxUnpatch * * @description * * The `unpatch` directive helps in partially migrating to zone-less apps as well as getting rid * of unnecessary renderings through zones `addEventListener` patches. * It can be used on any element you apply event bindings. * * The current way of binding events to the DOM is to use output bindings: * ```html * <button (click)="doStuff($event)">click me</button> * ``` * * The problem is that every event registered over `()` syntax, e.g. `(click)` * marks the component and all its ancestors as dirty and re-renders the whole component tree. * This is because zone.js patches the native browser API and whenever one of the patched APIs is used it re-renders. * * So even if your button is not related to a change that needs a re-render the app will re-render completely. * This leads to bad performance. This is especially helpful if you work with frequently fired events like 'mousemove' * * `unpatch` directive solves that problem. * * Included Features: * - by default un-patch all registered listeners of the host it is applied on * - un-patch only a specified set of registered event listeners * - works zone independent (it directly checks the widow for patched APIs and un-patches them without the use of `runOutsideZone` which brings more performance) * - Not interfering with any logic executed by the registered callback * * @usageNotes * * The `unpatch` directive can be used like shown here: * ```html * <button [unpatch] (click)="triggerSomeMethod($event)">click me</button> * <button [unpatch]="['mousemove']" (mousemove)="doStuff2($event)" (click)="doStuff($event)">click me</button> * ``` * * @publicApi */ class RxUnpatch { constructor(host) { this.host = host; this.subscription = new Subscription(); this.events$ = new BehaviorSubject(zonePatchedEvents); this.listeners = new Map(); } ngOnChanges({ events }) { if (events && Array.isArray(this.events)) { this.events$.next(this.events); } } ngAfterViewInit() { this.subscription = this.events$.subscribe((events) => { this.reapplyUnPatchedEventListeners(events); }); } ngOnDestroy() { this.subscription.unsubscribe(); for (const [event, listeners = []] of this.listeners) { listeners.forEach((listener) => { this.host.nativeElement.removeEventListener(event, listener); }); } } reapplyUnPatchedEventListeners(events) { for (const event of events) { const listeners = unpatchEventListener(this.host.nativeElement, event); this.listeners.set(event, listeners); } } /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: RxUnpatch, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); } /** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.0.0", type: RxUnpatch, isStandalone: true, selector: "[unpatch]", inputs: { events: ["unpatch", "events"] }, usesOnChanges: true, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: RxUnpatch, decorators: [{ type: Directive, args: [{ selector: '[unpatch]', standalone: true }] }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { events: [{ type: Input, args: ['unpatch'] }] } }); /** * Generated bundle index. Do not edit. */ export { RxUnpatch }; //# sourceMappingURL=template-unpatch.mjs.map