src/app/features/skill/link-external-profile/edit-external-profile-link.component.ts
CustomFormControlDirective<string>
| changeDetection | ChangeDetectionStrategy.OnPush |
| providers |
EditExternalProfileLinkComponent
|
| selector | app-edit-external-profile-link |
| standalone | true |
| imports |
MatButton
MatTooltip
MatProgressSpinnerModule
|
| styleUrls | ./edit-external-profile-link.component.scss |
| templateUrl | ./edit-external-profile-link.component.html |
| styleUrl | ./edit-external-profile-link.component.scss |
No results matching.
MatButton
MatTooltip
FormsModule
ReactiveFormsModule
MatProgressSpinnerModule
FaDynamicIconComponent
CustomFormControlDirective<string>
OnInit
EditComponent
Properties |
Methods |
Inputs |
Outputs |
Accessors |
| entity | |
Type : Entity
|
|
| formFieldConfig | |
Type : FormFieldConfig
|
|
| aria-describedby | |
Type : string
|
|
|
Inherited from
CustomFormControlDirective
|
|
| disabled | |
Type : boolean
|
|
|
Inherited from
CustomFormControlDirective
|
|
| ngControl | |
Type : any
|
|
Default value : inject(NgControl, { optional: true, self: true })
|
|
|
Inherited from
CustomFormControlDirective
|
|
| placeholder | |
Type : string
|
|
|
Inherited from
CustomFormControlDirective
|
|
| required | |
Type : boolean
|
|
|
Inherited from
CustomFormControlDirective
|
|
| value | |
Type : T
|
|
|
Inherited from
CustomFormControlDirective
|
|
| valueChange | |
Type : EventEmitter
|
|
|
Inherited from
CustomFormControlDirective
|
|
| Async searchMatchingProfiles |
searchMatchingProfiles()
|
|
Returns :
any
|
| unlinkExternalProfile |
unlinkExternalProfile()
|
|
Returns :
void
|
| Async updateExternalData |
updateExternalData()
|
|
Returns :
any
|
| blur |
blur()
|
|
Inherited from
CustomFormControlDirective
|
|
Returns :
void
|
| focus |
focus()
|
|
Inherited from
CustomFormControlDirective
|
|
Returns :
void
|
| onContainerClick | ||||||
onContainerClick(event: MouseEvent)
|
||||||
|
Inherited from
CustomFormControlDirective
|
||||||
|
Parameters :
Returns :
void
|
| registerOnChange | ||||||
registerOnChange(fn: any)
|
||||||
|
Inherited from
CustomFormControlDirective
|
||||||
|
Parameters :
Returns :
void
|
| registerOnTouched | ||||||
registerOnTouched(fn: any)
|
||||||
|
Inherited from
CustomFormControlDirective
|
||||||
|
Parameters :
Returns :
void
|
| setDescribedByIds | ||||||
setDescribedByIds(ids: string[])
|
||||||
|
Inherited from
CustomFormControlDirective
|
||||||
|
Parameters :
Returns :
void
|
| setDisabledState | ||||||
setDisabledState(isDisabled: boolean)
|
||||||
|
Inherited from
CustomFormControlDirective
|
||||||
|
Parameters :
Returns :
void
|
| writeValue | |||||||||||||||
writeValue(value: T, notifyFormControl: unknown)
|
|||||||||||||||
|
Inherited from
CustomFormControlDirective
|
|||||||||||||||
|
Implementation for Angular ControlValueAccessor interface that links the form control value to the component value
Parameters :
Returns :
void
|
| 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"
|
|
Inherited from
CustomFormControlDirective
|
| elementRef |
Type : unknown
|
Default value : inject<ElementRef<HTMLElement>>(ElementRef)
|
|
Inherited from
CustomFormControlDirective
|
| Readonly enabled |
Type : Signal<boolean>
|
Default value : computed(() => !this._disabled())
|
|
Inherited from
CustomFormControlDirective
|
|
Whether the control is currently enabled, as a signal (tracks disabled). |
| errorStateMatcher |
Type : unknown
|
Default value : inject(ErrorStateMatcher)
|
|
Inherited from
CustomFormControlDirective
|
| id |
Type : unknown
|
Default value : `custom-form-control-${CustomFormControlDirective.nextId++}`
|
|
Inherited from
CustomFormControlDirective
|
| Static nextId |
Type : number
|
Default value : 0
|
|
Inherited from
CustomFormControlDirective
|
| onChange |
Type : unknown
|
Default value : () => {...}
|
|
Inherited from
CustomFormControlDirective
|
| onTouched |
Type : unknown
|
Default value : () => {...}
|
|
Inherited from
CustomFormControlDirective
|
| parentForm |
Type : unknown
|
Default value : inject(NgForm, { optional: true })
|
|
Inherited from
CustomFormControlDirective
|
| parentFormGroup |
Type : unknown
|
Default value : inject(FormGroupDirective, { optional: true })
|
|
Inherited from
CustomFormControlDirective
|
| stateChanges |
Type : unknown
|
Default value : new Subject<void>()
|
|
Inherited from
CustomFormControlDirective
|
| Readonly valueSignal |
Type : Signal<T>
|
Default value : computed(() => this._value())
|
|
Inherited from
CustomFormControlDirective
|
|
The current value of the control as a signal.
Authoritative in both modes: it reflects the bound |
| additional |
getadditional()
|
|
The configuration details for this external profile link,
defined in the config field's
Returns :
ExternalProfileLinkConfig
|
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