UNPKG

@igo2/common

Version:
376 lines (368 loc) 14.1 kB
import { Observable } from 'rxjs'; import * as i0 from '@angular/core'; import { Injectable, ViewContainerRef, ViewChild, Input, ChangeDetectionStrategy, Component, NgModule } from '@angular/core'; import { ObjectUtils } from '@igo2/utils'; /** * This class is used in the DynamicComponentOutlet component. It holds * a reference to a component factory and can render that component * in a target element on demand. It's also possible to set inputs * and to subscribe to outputs. */ class DynamicComponent { componentFactory; /** * Component reference */ componentRef; /** * Subscriptions to the component's outputs. Those need * to be unsubscribed when the component is destroyed. */ subscriptions = []; /** * Component target element */ target; /** * Component inputs */ inputs = {}; /** * Subscriptions to the component's async inputs */ inputs$$ = {}; /** * Subscribers to the component's outputs */ subscribers = {}; constructor(componentFactory) { this.componentFactory = componentFactory; } /** * Render the component to a target element. * Set it's inputs and subscribe to it's outputs. * @param target Target element */ setTarget(target) { this.target = target; this.componentRef = target.createComponent(this.componentFactory); this.updateInputs(this.inputs); this.updateSubscribers(this.subscribers); } /** * Destroy this component. That means, removing from it's target * element and unsubscribing to it's outputs. */ destroy() { if (this.target !== undefined) { this.target.clear(); } if (this.componentRef !== undefined) { this.componentRef.destroy(); this.componentRef = undefined; } this.unobserveAllInputs(); this.unsubscribeAll(); } /** * Update the component inputs. This is an update so any * key not defined won't be overwritten. */ updateInputs(inputs) { this.inputs = inputs; if (this.componentRef === undefined) { return; } const instance = this.componentRef.instance; const allowedInputs = this.componentFactory.inputs; allowedInputs.forEach((value) => { const key = value.propName; this.unobserveInput(key); const inputValue = inputs[key]; if (Object.prototype.hasOwnProperty.call(inputs, key)) { if (inputValue instanceof Observable) { this.observeInput(key, inputValue); } else { this.setInputValue(instance, key, inputValue); } } }); if (typeof instance.onUpdateInputs === 'function') { instance.onUpdateInputs(); } } /** * Set an instance's input value * @param instance Component instance * @param key Input key * @param value Input value */ setInputValue(instance, key, value) { const currentValue = instance[key]; if (value === currentValue) { return; } const prototype = Object.getPrototypeOf(instance); const descriptor = Object.getOwnPropertyDescriptor(prototype, key); if (descriptor !== undefined && descriptor.set !== undefined) { descriptor.set.call(instance, value); } else { instance[key] = value; } } /** * Update the component subscribers. This is an update so any * key not defined won't be overwritten. */ updateSubscribers(subscribers) { this.subscribers = subscribers; if (this.componentRef === undefined) { return; } const instance = this.componentRef.instance; const allowedSubscribers = this.componentFactory.outputs; allowedSubscribers.forEach((value) => { const key = value.propName; if (Object.prototype.hasOwnProperty.call(subscribers, key)) { const emitter = instance[key]; const subscriber = subscribers[key]; if (Array.isArray(subscriber)) { subscriber.forEach((_subscriber) => { this.subscriptions.push(emitter.subscribe(_subscriber)); }); } else { this.subscriptions.push(emitter.subscribe(subscriber)); } } }); } /** * Subscribe to an observable input and update the component's input value * accordingly * @param key Input key * @param observable Observable */ observeInput(key, observable) { this.inputs$$[key] = observable.subscribe((value) => { const instance = this.componentRef.instance; this.setInputValue(instance, key, value); if (typeof instance.onUpdateInputs === 'function') { instance.onUpdateInputs(); } }); } /** * Unsubscribe to an observable input * @param key Input key */ unobserveInput(key) { if (this.inputs$$[key] !== undefined) { this.inputs$$[key].unsubscribe(); this.inputs$$[key] = undefined; } } /** * Unsubscribe to all outputs. */ unobserveAllInputs() { Object.values(this.inputs$$).forEach((s) => { if (s !== undefined) { s.unsubscribe(); } }); this.inputs$$ = {}; } /** * Unsubscribe to all outputs. */ unsubscribeAll() { this.subscriptions.forEach((s) => s.unsubscribe()); this.subscriptions = []; } } /** * Service to creates DynamicComponent instances from base component classes */ class DynamicComponentService { resolver; constructor(resolver) { this.resolver = resolver; } /** * Creates a DynamicComponent instance from a base component class * @param componentCls The component class * @returns DynamicComponent instance */ create(componentCls) { const factory = this.resolver.resolveComponentFactory(componentCls); return new DynamicComponent(factory); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DynamicComponentService, deps: [{ token: i0.ComponentFactoryResolver }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DynamicComponentService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DynamicComponentService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: i0.ComponentFactoryResolver }] }); class DynamicOutletComponent { dynamicComponentService; cdRef; /** * The dynamic component base class or the dynamic component itself */ component; /** * The dynamic component inputs */ inputs = {}; /** * The subscribers to the dynamic component outputs */ subscribers = {}; /** * The dynamic component */ dynamicComponent; /** * The view element to render the component to * @ignore */ target; constructor(dynamicComponentService, cdRef) { this.dynamicComponentService = dynamicComponentService; this.cdRef = cdRef; } /** * If the dynamic component changes, create it. * If the inputs or subscribers change, update the current component's * inputs or subscribers. * @internal */ ngOnChanges(changes) { const component = changes.component; const inputs = changes.inputs; const subscribers = changes.subscribers; const eq = ObjectUtils.objectsAreEquivalent; if (!component || !component.currentValue) { return; } if (component.currentValue !== component.previousValue) { this.createComponent(component.currentValue); } else { const inputsAreEquivalents = inputs && eq(inputs.currentValue || {}, inputs.previousValue || {}); const subscribersAreEquivalents = subscribers && eq(subscribers.currentValue || {}, subscribers.previousValue || {}); if (inputsAreEquivalents === false) { this.updateInputs(); } if (subscribersAreEquivalents === false) { this.updateSubscribers(); } } this.cdRef.detectChanges(); } /** * Destroy the dynamic component and all it's subscribers * @internal */ ngOnDestroy() { if (this.dynamicComponent) { this.dynamicComponent.destroy(); } } /** * Create a DynamicComponent out of the component class and render it. * @internal */ createComponent(component) { if (this.dynamicComponent !== undefined) { this.dynamicComponent.destroy(); } this.dynamicComponent = component instanceof DynamicComponent ? component : this.dynamicComponentService.create(component); this.renderComponent(); } /** * Create and render the dynamic component. Set it's inputs and subscribers * @internal */ renderComponent() { this.updateInputs(); this.updateSubscribers(); this.dynamicComponent.setTarget(this.target); } /** * Update the dynamic component inputs. This is an update so any * key not defined won't be overwritten. * @internal */ updateInputs() { this.dynamicComponent.updateInputs(this.inputs); } /** * Update the dynamic component subscribers. This is an update so any * key not defined won't be overwritten. * @internal */ updateSubscribers() { this.dynamicComponent.updateSubscribers(this.subscribers); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DynamicOutletComponent, deps: [{ token: DynamicComponentService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.15", type: DynamicOutletComponent, isStandalone: true, selector: "igo-dynamic-outlet", inputs: { component: "component", inputs: "inputs", subscribers: "subscribers" }, viewQueries: [{ propertyName: "target", first: true, predicate: ["target"], descendants: true, read: ViewContainerRef, static: true }], usesOnChanges: true, ngImport: i0, template: "<ng-template #target></ng-template>\n", styles: [":host{display:block;width:100%;height:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: DynamicOutletComponent, decorators: [{ type: Component, args: [{ selector: 'igo-dynamic-outlet', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "<ng-template #target></ng-template>\n", styles: [":host{display:block;width:100%;height:100%}\n"] }] }], ctorParameters: () => [{ type: DynamicComponentService }, { type: i0.ChangeDetectorRef }], propDecorators: { component: [{ type: Input }], inputs: [{ type: Input }], subscribers: [{ type: Input }], target: [{ type: ViewChild, args: ['target', { read: ViewContainerRef, static: true }] }] } }); /** * @deprecated import the DynamicOutletComponent directly */ class IgoDynamicComponentModule { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: IgoDynamicComponentModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.15", ngImport: i0, type: IgoDynamicComponentModule, imports: [DynamicOutletComponent], exports: [DynamicOutletComponent] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: IgoDynamicComponentModule }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: IgoDynamicComponentModule, decorators: [{ type: NgModule, args: [{ imports: [DynamicOutletComponent], exports: [DynamicOutletComponent] }] }] }); /** * @deprecated import the DynamicOutletComponent directly */ class IgoDynamicOutletModule { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: IgoDynamicOutletModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.15", ngImport: i0, type: IgoDynamicOutletModule, imports: [DynamicOutletComponent], exports: [DynamicOutletComponent] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: IgoDynamicOutletModule }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.15", ngImport: i0, type: IgoDynamicOutletModule, decorators: [{ type: NgModule, args: [{ imports: [DynamicOutletComponent], exports: [DynamicOutletComponent] }] }] }); /** * Generated bundle index. Do not edit. */ export { DynamicComponent, DynamicComponentService, DynamicOutletComponent, IgoDynamicComponentModule, IgoDynamicOutletModule }; //# sourceMappingURL=igo2-common-dynamic-component.mjs.map