text: import {
    Directive,
    DoCheck,
    Inject,
    Injector,
    Input,
    KeyValueChanges,
    KeyValueDiffers,
    Optional,
    Renderer2,
    Type,
  } from '@angular/core';
  
  import {
    DynamicComponentInjector,
    DynamicComponentInjectorToken,
  } from '../component-injector';
  
  export interface AttributesMap {
    [key: string]: string;
  }
  
  interface AttributeActions {
    set: AttributesMap;
    remove: string[];
  }
  
  @Directive({
    selector: '[ndcDynamicAttributes],[ngComponentOutletNdcDynamicAttributes]',
    exportAs: 'ndcDynamicAttributes',
  })
  export class DynamicAttributesDirective implements DoCheck {
    @Input()
    ndcDynamicAttributes: AttributesMap;
    @Input()
    ngComponentOutletNdcDynamicAttributes: AttributesMap;
  
    private attrsDiffer = this.differs.find({}).create<string, string>();
    private lastCompType: Type<any>;
    private lastAttrActions: AttributeActions;
  
    private get _attributes() {
      return (
        this.ndcDynamicAttributes || this.ngComponentOutletNdcDynamicAttributes
      );
    }
  
    private get _nativeElement() {
      return this.componentInjector.componentRef?.location.nativeElement;
    }
  
    private get _compType() {
      return this.componentInjector.componentRef?.componentType;
    }
  
    private get _isCompChanged() {
      if (this.lastCompType !== this._compType) {
        this.lastCompType = this._compType;
        return true;
      }
      return false;
    }
  
    constructor(
      private renderer: Renderer2,
      private differs: KeyValueDiffers,
      private injector: Injector,
      @Inject(DynamicComponentInjectorToken)
      @Optional()
      private componentInjector?: DynamicComponentInjector,
    ) {}
  
    ngDoCheck(): void {
      const isCompChanged = this._isCompChanged;
      const changes = this.attrsDiffer.diff(this._attributes);
  
      if (changes) {
        this.lastAttrActions = this._changesToAttrActions(changes);
      }
  
      if (changes || (isCompChanged && this.lastAttrActions)) {
        this._updateAttributes(this.lastAttrActions);
      }
    }
  
    setAttribute(name: string, value: string, namespace?: string) {
      if (this._nativeElement) {
        this.renderer.setAttribute(this._nativeElement, name, value, namespace);
      }
    }
  
    removeAttribute(name: string, namespace?: string) {
      if (this._nativeElement) {
        this.renderer.removeAttribute(this._nativeElement, name, namespace);
      }
    }
  
    private _updateAttributes(actions: AttributeActions) {
      // ? Early exit if no dynamic component
      if (!this._compType) {
        return;
      }
  
      Object.keys(actions.set).forEach(key =>
        this.setAttribute(key, actions.set[key]),
      );
  
      actions.remove.forEach(key => this.removeAttribute(key));
    }
  
    private _changesToAttrActions(
      changes: KeyValueChanges<string, string>,
    ): AttributeActions {
      const attrActions: AttributeActions = {
        set: {},
        remove: [],
      };
  
      changes.forEachAddedItem(r => (attrActions.set[r.key] = r.currentValue));
      changes.forEachChangedItem(r => (attrActions.set[r.key] = r.currentValue));
      changes.forEachRemovedItem(r => attrActions.remove.push(r.key));
  
      return attrActions;
    }
  }
  