src/app/features/skill/link-external-profile/edit-external-profile-link.component.ts

Extends

CustomFormControlDirective<string>

Implements

OnInit EditComponent

Metadata

Relationships

Used by

No results matching.

Index

Properties
Methods
Inputs
Outputs
Accessors

Inputs

entity
Type : Entity
formFieldConfig
Type : FormFieldConfig
aria-describedby
Type : string
disabled
Type : boolean
ngControl
Type : any
Default value : inject(NgControl, { optional: true, self: true })
placeholder
Type : string
required
Type : boolean
value
Type : T

Outputs

valueChange
Type : EventEmitter

Methods

Async searchMatchingProfiles
searchMatchingProfiles()
Returns : any
unlinkExternalProfile
unlinkExternalProfile()
Returns : void
Async updateExternalData
updateExternalData()
Returns : any
blur
blur()
Returns : void
focus
focus()
Returns : void
onContainerClick
onContainerClick(event: MouseEvent)
Parameters :
Name Type Optional
event MouseEvent No
Returns : void
registerOnChange
registerOnChange(fn: any)
Parameters :
Name Type Optional
fn any No
Returns : void
registerOnTouched
registerOnTouched(fn: any)
Parameters :
Name Type Optional
fn any No
Returns : void
setDescribedByIds
setDescribedByIds(ids: string[])
Parameters :
Name Type Optional
ids string[] No
Returns : void
setDisabledState
setDisabledState(isDisabled: boolean)
Parameters :
Name Type Optional
isDisabled boolean No
Returns : void
writeValue
writeValue(value: T, notifyFormControl: unknown)

Implementation for Angular ControlValueAccessor interface that links the form control value to the component value

Parameters :
Name Type Optional Default value Description
value T No

The new value to set

notifyFormControl unknown No false

Whether to notify the FormControl of this change (for internal updates)

Returns : void

Properties

externalProfile
Type : unknown
Default value : signal<ExternalProfile | undefined>(undefined)
externalProfileError
Type : unknown
Default value : signal(false)
isDisabled
Type : unknown
Default value : signal(false)
isLoading
Type : WritableSignal<boolean>
Default value : signal(false)
controlType
Type : string
Default value : "custom-control"
elementRef
Type : unknown
Default value : inject<ElementRef<HTMLElement>>(ElementRef)
Readonly enabled
Type : Signal<boolean>
Default value : computed(() => !this._disabled())

Whether the control is currently enabled, as a signal (tracks disabled).

errorStateMatcher
Type : unknown
Default value : inject(ErrorStateMatcher)
id
Type : unknown
Default value : `custom-form-control-${CustomFormControlDirective.nextId++}`
Static nextId
Type : number
Default value : 0
onChange
Type : unknown
Default value : () => {...}
onTouched
Type : unknown
Default value : () => {...}
parentForm
Type : unknown
Default value : inject(NgForm, { optional: true })
parentFormGroup
Type : unknown
Default value : inject(FormGroupDirective, { optional: true })
stateChanges
Type : unknown
Default value : new Subject<void>()
Readonly valueSignal
Type : Signal<T>
Default value : computed(() => this._value())

The current value of the control as a signal. Authoritative in both modes: it reflects the bound FormControl (synced in ngDoCheck) as well as [(value)] / writeValue updates.

Accessors

additional
getadditional()

The configuration details for this external profile link, defined in the config field's additional property.

import {
  ChangeDetectionStrategy,
  Component,
  DestroyRef,
  inject,
  input,
  OnInit,
  signal,
  WritableSignal,
} from "@angular/core";
import { FormGroup, FormsModule, ReactiveFormsModule } from "@angular/forms";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { MatButton } from "@angular/material/button";
import { MatDialog } from "@angular/material/dialog";
import { MatFormFieldControl } from "@angular/material/form-field";
import { MatProgressSpinnerModule } from "@angular/material/progress-spinner";
import { MatTooltip } from "@angular/material/tooltip";
import { of } from "rxjs";
import { catchError } from "rxjs/operators";
import { CustomFormControlDirective } from "../../../core/common-components/basic-autocomplete/custom-form-control.directive";
import { FormFieldConfig } from "../../../core/common-components/entity-form/FormConfig";
import { DynamicComponent } from "../../../core/config/dynamic-components/dynamic-component.decorator";
import { EditComponent } from "../../../core/entity/entity-field-edit/dynamic-edit/edit-component.interface";
import { Entity } from "../../../core/entity/model/entity";
import { retryOnServerError } from "../../../utils/retry-on-server-error.rxjs-pipe";
import { ExternalProfileLinkConfig } from "../external-profile-link-config";
import { ExternalProfile } from "../skill-api/external-profile";
import { SkillApiService } from "../skill-api/skill-api.service";
import {
  LinkExternalProfileDialogComponent,
  LinkExternalProfileDialogData,
} from "./link-external-profile-dialog/link-external-profile-dialog.component";
import { FaDynamicIconComponent } from "#src/app/core/common-components/fa-dynamic-icon/fa-dynamic-icon.component";

@DynamicComponent("EditExternalProfileLink")
@Component({
  selector: "app-edit-external-profile-link",
  standalone: true,
  imports: [
    MatButton,
    MatTooltip,
    FormsModule,
    ReactiveFormsModule,
    MatProgressSpinnerModule,
    FaDynamicIconComponent,
  ],
  templateUrl: "./edit-external-profile-link.component.html",
  styleUrl: "./edit-external-profile-link.component.scss",
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [
    {
      provide: MatFormFieldControl,
      useExisting: EditExternalProfileLinkComponent,
    },
  ],
})
export class EditExternalProfileLinkComponent
  extends CustomFormControlDirective<string>
  implements OnInit, EditComponent
{
  formFieldConfig = input<FormFieldConfig>();
  entity = input<Entity>();

  /**
   * The configuration details for this external profile link,
   * defined in the config field's `additional` property.
   */
  get additional(): ExternalProfileLinkConfig {
    return this.formFieldConfig()?.additional as ExternalProfileLinkConfig;
  }

  isLoading: WritableSignal<boolean> = signal(false);
  externalProfile = signal<ExternalProfile | undefined>(undefined);
  externalProfileError = signal(false);
  isDisabled = signal(false);

  private readonly dialog: MatDialog = inject(MatDialog);
  private readonly skillApi: SkillApiService = inject(SkillApiService);
  private readonly destroyRef = inject(DestroyRef);

  ngOnInit() {
    this.formControl.statusChanges
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe(() => this.isDisabled.set(this.formControl.disabled));
    this.isDisabled.set(this.formControl.disabled);

    if (this.formControl.value) {
      this.skillApi
        .getExternalProfileById(this.formControl.value)
        .pipe(
          retryOnServerError(2),
          catchError(() => {
            this.externalProfileError.set(true);
            return of(undefined);
          }),
        )
        .subscribe((profile) => {
          this.externalProfile.set(profile);
        });
    }
  }

  async searchMatchingProfiles() {
    const currentEntity = Object.assign(
      {},
      this.entity(),
      this.formControl.parent?.getRawValue(),
    );

    this.dialog
      .open(LinkExternalProfileDialogComponent, {
        data: {
          entity: currentEntity,
          config: this.additional,
        } as LinkExternalProfileDialogData,
      })
      .afterClosed()
      .subscribe((result: ExternalProfile | undefined) => {
        if (result) {
          this.linkProfile(result);
        }
      });
  }

  unlinkExternalProfile() {
    this.externalProfile.set(undefined);
    this.formControl.setValue(null);
    this.formControl.markAsDirty();
  }

  async updateExternalData() {
    this.isLoading.set(true);

    if (!this.formControl.value) {
      return;
    }

    await this.skillApi.applyDataFromExternalProfile(
      this.formControl.value,
      this.additional,
      this.formControl.parent as FormGroup,
    );

    this.isLoading.set(false);
    // TODO: run import / update automatically?
  }

  private linkProfile(externalProfile: ExternalProfile) {
    this.externalProfile.set(externalProfile);
    this.formControl.setValue(externalProfile.id);
    this.formControl.markAsDirty();
  }
}
<div class="margin-bottom-regular flex-column gap-small">
  <!-- Label / Description -->
  <div>
    <app-fa-dynamic-icon
      icon="person-walking-arrow-right"
      class="standard-icon-with-text"
    ></app-fa-dynamic-icon>

    @if (valueSignal()) {
      <span
        i18n
        matTooltip="external ID: {{ formControl?.value }}"
        i18n-matTooltip
      >
        Linked to external profile:
        {{
          externalProfile()
            ? externalProfile().fullName + " (" + externalProfile().email + ")"
            : ""
        }}
      </span>
      @if (externalProfileError()) {
        <app-fa-dynamic-icon
          icon="exclamation-triangle"
          matTooltip="Could not load external profile."
          i18n-matTooltip
        ></app-fa-dynamic-icon>
      }
    } @else {
      <span
        i18n
        matTooltip='Switch to "Edit" mode to search and link an external profile.'
        i18n-matTooltip
        style="font-style: italic"
      >
        No external profile linked
      </span>
    }
  </div>

  <!-- Action Buttons -->
  @if (!isDisabled()) {
    <div class="flex-row gap-small">
      @if (!formControl?.value) {
        <button
          mat-stroked-button
          matTooltip="Search and link a profile from an external system with this record to load additional data automatically."
          i18n-matTooltip
          i18n
          (click)="searchMatchingProfiles()"
        >
          Link external profile
        </button>
      } @else {
        <button mat-stroked-button i18n (click)="unlinkExternalProfile()">
          Unlink
        </button>

        <button
          mat-stroked-button
          class="flex-grow"
          matTooltip="Load the latest linked external profile and update the related fields of this record with the external data."
          i18n-matTooltip
          (click)="updateExternalData()"
          [disabled]="isLoading()"
        >
          @if (isLoading()) {
            <div style="display: flex">
              <label i18n>loading data...</label>
              <mat-spinner
                diameter="18"
                style="margin-left: 12px"
              ></mat-spinner>
            </div>
          } @else {
            <span i18n>Update data</span>
          }
        </button>
      }
    </div>
  }
</div>

./edit-external-profile-link.component.scss

Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""