### File

`src/lib/directives/options-from-method.directive.ts`

    import {
      AfterViewInit,
      ChangeDetectorRef,
      Directive,
      inject,
      INJECTOR,
      Injector,
      Input,
      OnChanges,
      SimpleChanges,
      TemplateRef,
      ViewContainerRef,
    } from '@angular/core';
    import {
      AbstractControlDirective,
      NgControl,
    } from '@angular/forms';
    import { MatFormField } from '@angular/material/form-field';
    import { RxapFormControl } from '@rxap/forms';
    import { Mixin } from '@rxap/mixin';
    import { Method } from '@rxap/pattern';
    import {
      ControlOption,
      ControlOptions,
    } from '@rxap/utilities';
    import { ExtractOptionsMethodMixin } from '../mixins/extract-options-method.mixin';

    export interface OptionsFromMethodTemplateContext {
      $implicit: ControlOption;
    }

    // eslint-disable-next-line @typescript-eslint/no-empty-interface
    export interface OptionsFromMethodDirectiveSettings {
    }

    // eslient-disable-next-line @typescript-eslint/no-empty-interface
    export interface OptionsFromMethodDirective<Value = any, Parameters = any>
      extends AfterViewInit, OnChanges, ExtractOptionsMethodMixin {
    }

    @Mixin(ExtractOptionsMethodMixin)
    @Directive({
      // eslint-disable-next-line @angular-eslint/directive-selector
      selector: '[rxapOptionsFromMethod]',
      standalone: true,
    })
    export class OptionsFromMethodDirective<Value = any, Parameters = any> implements AfterViewInit, OnChanges {

      static ngTemplateContextGuard(
        dir: OptionsFromMethodDirective,
        ctx: any,
      ): ctx is OptionsFromMethodTemplateContext {
        return true;
      }

      @Input('rxapOptionsFromMethodParameters')
      public parameters?: Parameters;

      @Input('rxapOptionsFromMethodResetOnChange')
      public resetOnChange?: Value;

      public options: ControlOptions | null                                    = null;
      // eslint-disable-next-line @angular-eslint/no-input-rename
      @Input('rxapOptionsFromMethodCall')
      public method!: Method<ControlOptions, Parameters>;
      protected ngControl: NgControl | AbstractControlDirective | null         = null;
      protected matFormField: MatFormField | null                              = null;
      protected settings: OptionsFromMethodDirectiveSettings                   = {};
      protected readonly viewContainerRef: ViewContainerRef                    = inject(ViewContainerRef);
      protected readonly injector: Injector                                    = inject(INJECTOR);
      protected readonly cdr: ChangeDetectorRef                                = inject(ChangeDetectorRef);
      private readonly template: TemplateRef<OptionsFromMethodTemplateContext> = inject(TemplateRef);

      public async ngAfterViewInit() {
        this.matFormField = this.injector.get(MatFormField, null);
        this.ngControl    = this.injector.get(NgControl, this.matFormField?._control.ngControl);
        this.control      = (this.ngControl?.control as RxapFormControl) ?? undefined;
        this.method ??= this.extractOptionsMethod();
        // ensure that the options are loaded. It is possible that the ngOnChange is triggered before the ngAfterViewInit
        // then the options are not loaded.
        if (!this.options) {
          this.setOptions(await this.loadOptions(this.parameters));
        }
      }

      public async ngOnChanges(changes: SimpleChanges) {
        // only try to load the options if the method is defined
        // else the initial load will be triggered in the ngAfterViewInit
        if (!this.method) {
          console.debug('The method is not yet defined');
          return;
        }
        const parametersChanges = changes['parameters'];
        if (parametersChanges) {
          this.setOptions(await this.loadOptions(parametersChanges.currentValue));
        }
      }

      protected async loadOptions(parameters?: Parameters): Promise<ControlOptions | null> {
        return this.method.call(parameters);
      }

      protected renderTemplate() {

        if (!this.options) {
          throw new Error('The options are not yet loaded');
        }

        this.viewContainerRef.clear();

        for (const option of this.options) {
          this.viewContainerRef.createEmbeddedView(this.template, {$implicit: option});
        }

        if (this.resetOnChange !== undefined) {
          this.control?.reset(this.resetOnChange);
        }

        this.cdr.detectChanges();

      }

      protected setOptions(options: ControlOptions | null) {
        if (!options) {
          this.options = null;
          this.viewContainerRef.clear();
        } else {
          this.options = options.slice();
          this.renderTemplate();
        }
      }

    }

# results matching ""

# No results matching ""