@igo2/common
Version:
376 lines (368 loc) • 14.1 kB
JavaScript
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