File
        
        
            Description
            
            Overloaded directive which acts as
A) a tagging mechanism to create an acl hierarchy (aclTag="my-tag")
B) people can also use it as a structural directive (*aclTag="'my-tag'")
to hide resources depending on their granted acl view action
            
            Implements
            
            
        
            
    Index
    
        
                
                    | Properties | 
                
                    |  | 
                
                    | Methods | 
                
                    |  | 
                
                    | Inputs | 
                
                    |  | 
                    
                        | Accessors | 
                    
                        |  | 
        
    
            
            
    
        
        
            
                
                    | aclTagHint | 
                
                    | Type : boolean | 
                
                    | Default value : false | 
                        
                            |  | 
            
        
        
            
                
                    | aclTagTransient | 
                
                    | Type : boolean | 
                
                    | Default value : false | 
                        
                            |  | 
            
        
            
    
    
        Methods
    
    
    
        
            
                | renderComponent | 
            
                | renderComponent() | 
            
                |  | 
            
                |  | 
        
    
    
    
        
            
                | renderWhenAclViewGranted | 
            
                | renderWhenAclViewGranted() | 
            
                |  | 
            
                |  | 
        
    
            
    
    
    
    
        
            
                | currentViewRef | 
                
                    | Type : EmbeddedViewRef<> | 
                    
                        |  | 
        
    
    
        
            
                | destroyed$ | 
                
                    | Default value : new Subject<void>() | 
                    
                        |  | 
        
    
    
        
            
                | inspectorServiceOverrideShowHint | 
                
                    | Default value : false | 
                    
                        |  | 
        
    
            
     
    
        import { AclTag } from '@allianz/taly-acl';
import {
  ComponentRef,
  Directive,
  EmbeddedViewRef,
  Input,
  OnChanges,
  OnDestroy,
  OnInit,
  SimpleChanges,
  TemplateRef,
  ViewContainerRef,
  inject
} from '@angular/core';
import { BehaviorSubject, Subject } from 'rxjs';
import { map, switchMap, takeUntil, tap } from 'rxjs/operators';
import { AclTagHintComponent } from '../acl-hint/public-api';
import { AclInspectorService } from '../inspector/inspector.service';
import { createAsyncAclTagProviders } from '../providers/public-api';
import { AclService } from '../services/acl.service';
import { ACL_TAG_NAME_ASYNC_TOKEN, ACL_TAG_TOKEN, ACL_TAG_TRANSIENT } from '../tokens/public-api';
/**
 * Overloaded directive which acts as
 * A) a tagging mechanism to create an acl hierarchy (aclTag="my-tag")
 * B) people can also use it as a structural directive (*aclTag="'my-tag'")
 * to hide resources depending on their granted acl view action
 */
@Directive({
  selector: '[aclTag]',
  providers: [createAsyncAclTagProviders()],
  standalone: false
})
export class AclTagDirective implements OnInit, OnDestroy, OnChanges {
  private aclTagUpdate$ = inject<BehaviorSubject<string>>(ACL_TAG_NAME_ASYNC_TOKEN, {
    optional: true,
    self: true
  });
  private aclTagTransient$ = inject<BehaviorSubject<boolean>>(ACL_TAG_TRANSIENT, {
    optional: true,
    host: true
  });
  private aclTag = inject<AclTag>(ACL_TAG_TOKEN, { optional: true, self: true });
  private templateRef = inject<TemplateRef<unknown>>(TemplateRef, { optional: true });
  private viewContainer = inject(ViewContainerRef, { optional: true });
  private aclService = inject<AclService>(AclService);
  private aclInspectorService = inject<AclInspectorService>(AclInspectorService);
  private show = false;
  private hintIsVisible = false;
  private componentIsVisible = false;
  destroyed$ = new Subject<void>();
  @Input('aclTagHint') hint = false;
  @Input('aclTagTransient') transient = false;
  @Input('aclTag') tag!: string;
  inspectorServiceOverrideShowHint = false;
  aclTagHintComponentRef!: ComponentRef<AclTagHintComponent>;
  currentViewRef!: EmbeddedViewRef<unknown>;
  constructor() {
    if (!this.aclTag) {
      throw new Error('Could not find any Acl Tag');
    }
    this.aclInspectorService.showAclHints$
      .pipe(
        takeUntil(this.destroyed$),
        tap((value) => {
          this.inspectorServiceOverrideShowHint = value;
          this.render();
        })
      )
      .subscribe();
  }
  get tagName() {
    return this.tag;
  }
  get showHint() {
    return this.hint || this.inspectorServiceOverrideShowHint;
  }
  ngOnInit(): void {
    this.updateTagName(this.tagName);
    /**
     * Only in case this directive is used as a structural directive:
     * Subscribe for any acl key change as the key is async by design
     * to prevent any circular dependencies. See ACL_TAG_NAME_ASYNC_TOKEN & aclTagProvider()
     */
    if (this.isStructuralDirective()) {
      this.renderWhenAclViewGranted();
    }
  }
  renderWhenAclViewGranted() {
    this.aclTag?.aclKey$
      .pipe(
        map((item) => {
          return item.aclKey;
        }),
        switchMap((resourceName) => this.aclService.isHidden$(resourceName)),
        tap((isHidden) => {
          this.show = !isHidden;
          this.render();
        }),
        takeUntil(this.destroyed$)
      )
      .subscribe();
    this.render();
  }
  ngOnChanges(changes: SimpleChanges): void {
    let renderRequired = false;
    if (false === changes['tag']?.firstChange) {
      this.updateTagName(changes['tag']?.currentValue);
      renderRequired = true;
    }
    if (false === changes['transient']?.firstChange) {
      this.updateTransient(changes['transient']?.currentValue);
      renderRequired = true;
    }
    if (renderRequired) {
      this.render();
    }
  }
  renderComponent() {
    if (!this.viewContainer) {
      throw new Error('Could not find any ViewContainerRef');
    }
    if (!this.show && this.componentIsVisible) {
      const index = this.viewContainer.indexOf(this.currentViewRef);
      if (index !== -1) {
        this.viewContainer.remove(index);
        this.componentIsVisible = false;
      }
    } else if (this.show && !this.componentIsVisible) {
      if (!this.templateRef) {
        throw new Error('Could not find any TemplateRef');
      }
      this.currentViewRef = this.viewContainer.createEmbeddedView(this.templateRef, 1);
      this.componentIsVisible = true;
    }
  }
  renderHint() {
    if (!this.viewContainer) {
      throw new Error('Could not find any ViewContainerRef');
    }
    if (!this.showHint && this.hintIsVisible) {
      const index = this.viewContainer.indexOf(this.aclTagHintComponentRef.hostView);
      if (index !== -1) {
        this.viewContainer.remove(index);
        this.hintIsVisible = false;
      }
    } else if (this.showHint) {
      if (!this.hintIsVisible) {
        this.attachAclTagHint();
        this.hintIsVisible = true;
      }
      this.updateAclHintContentShownInput();
    }
  }
  render() {
    this.renderComponent();
    this.renderHint();
  }
  ngOnDestroy(): void {
    // complete the tag stream to notify anyone subscribed (like the AclTag instances)
    this.aclTagUpdate$?.complete();
    this.aclTagTransient$?.complete();
    this.destroyed$.next();
  }
  private attachAclTagHint() {
    if (!this.viewContainer) {
      throw new Error('Could not find any ViewContainerRef');
    }
    if (!this.aclTag) {
      throw new Error('Could not find any Acl Tag');
    }
    this.aclTagHintComponentRef = this.viewContainer.createComponent(AclTagHintComponent, {
      index: 0
    });
    const hintComponentInstance = this.aclTagHintComponentRef.instance;
    hintComponentInstance.givenAclTag = this.aclTag;
  }
  private updateAclHintContentShownInput() {
    const hintComponentInstance = this.aclTagHintComponentRef.instance;
    /**
     * the contentShown input tells the hint if some content is shown
     * which we answer as truthy if the content is indeed shown
     * or if the directive is not used as a structural directive
     */
    hintComponentInstance.contentShown = this.show || !this.isStructuralDirective();
  }
  private updateTagName(value: string) {
    this.aclTagUpdate$?.next(value);
  }
  private updateTransient(transient: boolean) {
    this.aclTagTransient$?.next(transient);
  }
  private isStructuralDirective() {
    return null !== this.templateRef;
  }
}