text: import {
    ComponentFactoryResolver,
    Directive,
    Injector,
    Input,
    OnChanges,
    OnInit,
    SimpleChanges,
    TemplateRef,
    Type,
    ViewContainerRef,
  } from '@angular/core';
  import compare from 'just-compare';
  import { Subscription } from 'rxjs';
  import { filter } from 'rxjs/operators';
  import snq from 'snq';
  import { ABP } from '../models/common';
  import { ReplaceableComponents } from '../models/replaceable-components';
  import { ReplaceableComponentsService } from '../services/replaceable-components.service';
  import { SubscriptionService } from '../services/subscription.service';
  
  @Directive({ selector: '[abpReplaceableTemplate]', providers: [SubscriptionService] })
  export class ReplaceableTemplateDirective implements OnInit, OnChanges {
    @Input('abpReplaceableTemplate')
    data: ReplaceableComponents.ReplaceableTemplateDirectiveInput<any, any>;
  
    providedData = { inputs: {}, outputs: {} } as ReplaceableComponents.ReplaceableTemplateData<
      any,
      any
    >;
  
    context = {} as any;
  
    externalComponent: Type<any>;
  
    defaultComponentRef: any;
  
    defaultComponentSubscriptions = {} as ABP.Dictionary<Subscription>;
  
    initialized = false;
  
    constructor(
      private injector: Injector,
      private templateRef: TemplateRef<any>,
      private cfRes: ComponentFactoryResolver,
      private vcRef: ViewContainerRef,
      private replaceableComponents: ReplaceableComponentsService,
      private subscription: SubscriptionService,
    ) {
      this.context = {
        initTemplate: ref => {
          this.resetDefaultComponent();
          this.defaultComponentRef = ref;
          this.setDefaultComponentInputs();
        },
      };
    }
  
    ngOnInit() {
      const component$ = this.replaceableComponents
        .get$(this.data.componentKey)
        .pipe(
          filter(
            (res = {} as ReplaceableComponents.ReplaceableComponent) =>
              !this.initialized || !compare(res.component, this.externalComponent),
          ),
        );
  
      this.subscription.addOne(
        component$,
        (res = {} as ReplaceableComponents.ReplaceableComponent) => {
          this.vcRef.clear();
          this.externalComponent = res.component;
          if (this.defaultComponentRef) {
            this.resetDefaultComponent();
          }
  
          if (res.component) {
            this.setProvidedData();
            const customInjector = Injector.create({
              providers: [{ provide: 'REPLACEABLE_DATA', useValue: this.providedData }],
              parent: this.injector,
            });
            this.vcRef.createComponent(
              this.cfRes.resolveComponentFactory(res.component),
              0,
              customInjector,
            );
          } else {
            this.vcRef.createEmbeddedView(this.templateRef, this.context);
          }
  
          this.initialized = true;
        },
      );
    }
  
    ngOnChanges(changes: SimpleChanges) {
      if (snq(() => changes.data.currentValue.inputs) && this.defaultComponentRef) {
        this.setDefaultComponentInputs();
      }
    }
  
    setDefaultComponentInputs() {
      if (!this.defaultComponentRef || (!this.data.inputs && !this.data.outputs)) return;
  
      if (this.data.inputs) {
        for (const key in this.data.inputs) {
          if (this.data.inputs.hasOwnProperty(key)) {
            if (!compare(this.defaultComponentRef[key], this.data.inputs[key].value)) {
              this.defaultComponentRef[key] = this.data.inputs[key].value;
            }
          }
        }
      }
  
      if (this.data.outputs) {
        for (const key in this.data.outputs) {
          if (this.data.outputs.hasOwnProperty(key)) {
            if (!this.defaultComponentSubscriptions[key]) {
              this.defaultComponentSubscriptions[key] = this.defaultComponentRef[key].subscribe(
                value => {
                  this.data.outputs[key](value);
                },
              );
            }
          }
        }
      }
    }
  
    setProvidedData() {
      this.providedData = { ...this.data, inputs: {} };
  
      if (!this.data.inputs) return;
      Object.defineProperties(this.providedData.inputs, {
        ...Object.keys(this.data.inputs).reduce(
          (acc, key) => ({
            ...acc,
            [key]: {
              enumerable: true,
              configurable: true,
              get: () => this.data.inputs[key].value,
              ...(this.data.inputs[key].twoWay && {
                set: newValue => {
                  this.data.inputs[key].value = newValue;
                  this.data.outputs[`${key}Change`](newValue);
                },
              }),
            },
          }),
          {},
        ),
      });
    }
  
    resetDefaultComponent() {
      Object.keys(this.defaultComponentSubscriptions).forEach(key => {
        this.defaultComponentSubscriptions[key].unsubscribe();
      });
      this.defaultComponentSubscriptions = {} as ABP.Dictionary<Subscription>;
      this.defaultComponentRef = null;
    }
  }
  