@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
JavaScript
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