UNPKG

@tarktech/ngx-context

Version:

Angular Context: Easy property binding for router outlet and nested component trees.

363 lines (353 loc) 16 kB
import * as i0 from '@angular/core'; import { Component, ChangeDetectionStrategy, ViewEncapsulation, Optional, SkipSelf, Input, Directive, Host, NgModule } from '@angular/core'; import { Observable, Subject, ReplaySubject } from 'rxjs'; import { takeUntil, startWith, filter } from 'rxjs/operators'; function isSubscribableOrPromise(obj) { return obj instanceof Observable || obj instanceof Subject || obj instanceof Promise; } function parseKeys(input) { return (Array.isArray(input) ? input : input.split(/\s+/)).filter(key => key); } class ContextProviderComponent { source; initialized = false; _contextMap = {}; _provide = ''; provided = new Map(); set contextMap(map) { this._contextMap = map || {}; } get contextMap() { return this._contextMap; } set provide(value) { this._provide = value || ''; } get provide() { return this._provide; } get component() { return this.source['_view']?.component || this.source['context']; } change$ = new ReplaySubject(1); reset$ = new Subject(); constructor(source) { this.source = source; } init() { setTimeout(() => { const THIS = this; const context = new Map(); const provided = parseKeys(this.provide).filter(key => key && key in this.component); provided.forEach(key => { if (isSubscribableOrPromise(this.component[key])) { this.change$.next(key); return; } const propertyDescriptor = Object.getOwnPropertyDescriptor(this.component, key) || Object.getOwnPropertyDescriptor(this.component.__proto__, key); this.provided.set(key, propertyDescriptor); const { value, writable, get: getter, set: setter, ...prop } = propertyDescriptor; Object.defineProperty(this.component, key, { ...prop, get: getter ? function () { return getter.call(this); } : function () { return context.get(key); }, set: setter ? function () { setter.apply(this, arguments); THIS.change$.next(key); } : function (newValue) { context.set(key, newValue); THIS.change$.next(key); }, }); this.component[key] = value || this.component[key]; }); }, 0); } reset() { this.provided.forEach((propertyDescriptor, key) => { const value = this.component[key]; Object.defineProperty(this.component, key, propertyDescriptor); this.component[key] = value; }); this.provided.clear(); this.change$.next(''); this.reset$.next(); } ngOnChanges() { if (this.initialized) { this.reset(); if (this.source) this.init(); } } ngOnInit() { this.initialized = true; this.ngOnChanges(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.7", ngImport: i0, type: ContextProviderComponent, deps: [{ token: i0.ChangeDetectorRef, optional: true, skipSelf: true }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.7", type: ContextProviderComponent, selector: "context-provider", inputs: { contextMap: "contextMap", provide: "provide" }, usesOnChanges: true, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.7", ngImport: i0, type: ContextProviderComponent, decorators: [{ type: Component, args: [{ selector: 'context-provider', template: '<ng-content></ng-content>', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, }] }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef, decorators: [{ type: Optional }, { type: SkipSelf }] }]; }, propDecorators: { contextMap: [{ type: Input }], provide: [{ type: Input }] } }); class AbstractContextConsumer { provider; target; destroy$ = new Subject(); initialized; _contextMap = {}; _consume = ''; consumed = new Map(); set contextMap(map) { this._contextMap = map || {}; } get contextMap() { return this._contextMap; } set consume(consume) { this._consume = consume || ''; } get consume() { return this._consume; } get component() { return this.target['_view']?.component || this.target['context']; } constructor(provider, target) { this.provider = provider; this.target = target; } init() { const consumed = parseKeys(this.consume); this.provider.reset$ .pipe(takeUntil(this.destroy$)) .subscribe(() => this.ngOnChanges()); if (this.provider.provide.length) this.provider.change$ .pipe(takeUntil(this.destroy$), startWith(...Array.from(this.provider.provided.keys())), filter(key => !!key)) .subscribe(providerKey => this.syncProperties(consumed, providerKey)); } reset() { this.consumed.forEach((value, key) => { this.component[key] = value; }); this.consumed.clear(); } syncProperties(consumed, providerKey) { let key = this.provider.contextMap[providerKey] || providerKey; key = this.contextMap[key] || key; if (consumed.length && consumed.indexOf(key) < 0) return; if (!this.consumed.has(key)) this.consumed.set(key, this.component[key]); this.component[key] = this.provider.component[providerKey]; this.target.markForCheck(); } ngOnChanges() { if (this.initialized) { this.destroy$.next(); this.reset(); if (this.target && this.provider) this.init(); } } ngOnDestroy() { this.destroy$.next(); } ngOnInit() { this.initialized = true; this.ngOnChanges(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.7", ngImport: i0, type: AbstractContextConsumer, deps: [{ token: ContextProviderComponent }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.7", type: AbstractContextConsumer, inputs: { contextMap: "contextMap", consume: "consume" }, usesOnChanges: true, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.7", ngImport: i0, type: AbstractContextConsumer, decorators: [{ type: Directive }], ctorParameters: function () { return [{ type: ContextProviderComponent }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { contextMap: [{ type: Input }], consume: [{ type: Input }] } }); class ContextConsumerComponent extends AbstractContextConsumer { constructor(providerComponent, parent) { super(providerComponent, parent); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.7", ngImport: i0, type: ContextConsumerComponent, deps: [{ token: ContextProviderComponent, optional: true, skipSelf: true }, { token: i0.ChangeDetectorRef, optional: true, skipSelf: true }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.7", type: ContextConsumerComponent, selector: "context-consumer", usesInheritance: true, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.7", ngImport: i0, type: ContextConsumerComponent, decorators: [{ type: Component, args: [{ selector: 'context-consumer', template: '', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, }] }], ctorParameters: function () { return [{ type: ContextProviderComponent, decorators: [{ type: Optional }, { type: SkipSelf }] }, { type: i0.ChangeDetectorRef, decorators: [{ type: Optional }, { type: SkipSelf }] }]; } }); class ContextConsumerDirective extends AbstractContextConsumer { set consume(consume) { this._consume = consume || ''; } get consume() { return this._consume; } constructor(providerComponent, host) { super(providerComponent, host); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.7", ngImport: i0, type: ContextConsumerDirective, deps: [{ token: ContextProviderComponent, optional: true, skipSelf: true }, { token: i0.ChangeDetectorRef, host: true, optional: true }], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.7", type: ContextConsumerDirective, selector: "[contextConsumer]", inputs: { consume: ["contextConsumer", "consume"] }, usesInheritance: true, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.7", ngImport: i0, type: ContextConsumerDirective, decorators: [{ type: Directive, args: [{ selector: '[contextConsumer]', }] }], ctorParameters: function () { return [{ type: ContextProviderComponent, decorators: [{ type: Optional }, { type: SkipSelf }] }, { type: i0.ChangeDetectorRef, decorators: [{ type: Optional }, { type: Host }] }]; }, propDecorators: { consume: [{ type: Input, args: ['contextConsumer'] }] } }); class Context { $implicit = {}; } class ContextDisposerDirective { tempRef; vcRef; provider; destroy$ = new Subject(); _dispose = ''; view; set dispose(dispose) { this._dispose = dispose || ''; } get dispose() { return this._dispose; } constructor(tempRef, vcRef, provider) { this.tempRef = tempRef; this.vcRef = vcRef; this.provider = provider; } init() { const disposed = parseKeys(this.dispose); this.provider.reset$ .pipe(takeUntil(this.destroy$)) .subscribe(() => this.ngOnChanges()); if (this.provider.provide.length) this.provider.change$ .pipe(takeUntil(this.destroy$), startWith(...Array.from(this.provider.provided.keys())), filter(key => !!key)) .subscribe(providerKey => this.syncProperties(disposed, providerKey)); } reset() { this.view = this.vcRef.createEmbeddedView(this.tempRef, new Context()); } syncProperties(disposed, providerKey) { const key = this.provider.contextMap[providerKey] || providerKey; if (disposed.length && disposed.indexOf(key) < 0) return; const value = this.provider.component[providerKey]; this.view.context.$implicit[key] = value; this.view.context[key] = value; this.view.markForCheck(); } ngOnChanges() { this.ngOnDestroy(); this.reset(); if (this.provider && this.tempRef && this.vcRef) this.init(); } ngOnDestroy() { this.destroy$.next(); if (this.view) this.vcRef.clear(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.7", ngImport: i0, type: ContextDisposerDirective, deps: [{ token: i0.TemplateRef, optional: true }, { token: i0.ViewContainerRef, optional: true }, { token: ContextProviderComponent, optional: true, skipSelf: true }], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.7", type: ContextDisposerDirective, selector: "[contextDisposer]", inputs: { dispose: ["contextDisposer", "dispose"] }, usesOnChanges: true, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.7", ngImport: i0, type: ContextDisposerDirective, decorators: [{ type: Directive, args: [{ selector: '[contextDisposer]', }] }], ctorParameters: function () { return [{ type: i0.TemplateRef, decorators: [{ type: Optional }] }, { type: i0.ViewContainerRef, decorators: [{ type: Optional }] }, { type: ContextProviderComponent, decorators: [{ type: Optional }, { type: SkipSelf }] }]; }, propDecorators: { dispose: [{ type: Input, args: ['contextDisposer'] }] } }); class NgxContextModule { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.7", ngImport: i0, type: NgxContextModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.2.7", ngImport: i0, type: NgxContextModule, declarations: [ContextConsumerComponent, ContextConsumerDirective, ContextDisposerDirective, ContextProviderComponent], exports: [ContextConsumerComponent, ContextConsumerDirective, ContextDisposerDirective, ContextProviderComponent] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.2.7", ngImport: i0, type: NgxContextModule }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.7", ngImport: i0, type: NgxContextModule, decorators: [{ type: NgModule, args: [{ declarations: [ ContextConsumerComponent, ContextConsumerDirective, ContextDisposerDirective, ContextProviderComponent, ], exports: [ ContextConsumerComponent, ContextConsumerDirective, ContextDisposerDirective, ContextProviderComponent, ], }] }] }); /** * Generated bundle index. Do not edit. */ export { ContextConsumerComponent, ContextConsumerDirective, ContextDisposerDirective, ContextProviderComponent, NgxContextModule }; //# sourceMappingURL=tarktech-ngx-context.mjs.map