How can I patch values to mat-autocomplete only after I have gotten the values via...

Discussão em 'Angular' iniciado por Neel, Dezembro 30, 2024.

  1. Neel

    Neel Guest

    I am in a team that is developing an enterprise-level full-stack web application using Spring-boot and Angular v18. The website will have multiple tables and each table will have its respective add/edit form. The configuration for each form is acquired by sending a request to the backend REST API. Then based on the configuration the forms are rendered via a form-component in the frontend. Some forms might have dropdowns whose options are get via a dropdown service which itself uses WritableSignals to acquire the options by again doing a call to the backend. The issue I am facing right now is that only for the first time when I open the form the options are not getting patched to the respective dropdown. If I close the form and open it back again immediately then everything seems to be working. From debugging the code what I can understand is that the field is getting rendered before the dropdown service can acquire the options, but I am not sure how to move forward with solving this issue.

    dropdown-helper.service.ts:

    @Injectable({
    providedIn: 'root'
    })
    export class DropdownHelperService {
    dropdownOptionsMap = new Map<string, WritableSignal<Option[]>>();

    constructor(private dataService: DataService) {
    environment.tableDetails.map(table => {
    return table.tableDatabaseName;
    }).forEach(table => {
    this.dropdownOptionsMap.set(table, signal<Option[]>([]))
    });
    }

    loadDropdownOptions(tableName: string) {
    this.dataService.getDropdownSuggestions(tableName).subscribe((dropdownOptions: any[]) => {
    const options: Option[] = dropdownOptions.map(option => ({
    value: option.id,
    label: option.values.join(' / ')
    }));

    const signalRef = this.dropdownOptionsMap.get(tableName);

    if(signalRef) {
    signalRef.set(options);
    }
    }
    }
    }


    form.component.ts:


    @Component({
    selector: 'app-form',
    standalone: true,
    imports: [
    /* various imports */
    ],
    templateUrl: './form.component.html',
    styleUrl: './form.component.css',
    providers: [provideNativeDateAdapter()]
    })

    export class FormComponent implements OnChanges {
    @Input('getFormRequestData') getFormRequestData: FormComponentDataModel;
    @Input() fieldsArray: FormField[];

    dataForm: FormGroup;
    formArray: FormArray;
    filteredOptions: Map<string, Observable<Option[]>>;

    constructor(private formBuilder: FormBuilder, public dropdownHelper: DropdownHelperService) {
    this.getFormRequestData = {
    tableName: '',
    mode: '',
    data: []
    }
    this.dataForm = this.formBuilder.group({
    forms: this.formBuilder.array([])
    });

    this.formArray = this.dataForm.get('forms') as FormArray;
    this.fieldsArray = [];
    this.filteredOptions = new Map<string, Observable<Option[]>>();
    }

    ngOnChanges(changes: SimpleChanges) {
    if (changes['fieldsArray'] && !changes['fieldsArray'].firstChange && this.fieldsArray?.length > 0) {
    this.addForm();
    }
    }

    addForm() {
    if (this.formArray.length > 0) {
    this.formArray.clear();
    }

    const {mode, data} = this.getFormRequestData;
    if (mode === 'edit' && data.length > 0) {
    for (const initialData of data) {
    this.formArray.push(this.createFormGroup(initialData));
    }
    } else if (mode === 'add' && data.length > 0 && data[0]['numberOfRowsToBeAdded'] !== 0) {
    for (let i = 0; i < data[0]['numberOfRowsToBeAdded']; i++) {
    this.formArray.push(this.createFormGroup());
    }
    }
    }

    createFormGroup(initialData?: any) {
    const group = this.formBuilder.group({});
    this.fieldsArray.forEach(field => {
    if (field.controllerName !== null && field.controllerName !== undefined) {

    const controller = new FormControl();
    this.setupFormControl(controller, field);

    if (field.type === 'select' && field.selectOptions.dropdownTable !== null) {
    // setting up the options for each dropdown
    this.setupDropdown(field, controller);
    }

    if (this.getFormRequestData.mode === 'edit' && initialData && (initialData[field.controllerName] !== null || initialData[field.controllerName] !== '')) {
    this.patchControlValue(field, controller, initialData[field.controllerName]);
    }

    controller.updateValueAndValidity();
    group.addControl(field.controllerName, controller);
    }
    });

    return group;
    }

    setupFormControl(control: FormControl, field: FormField) {
    if (field.validators.validators !== null && field.validators.validators !== undefined) {
    const validatorFns: ValidatorFn[] = [];

    field.validators.validators.forEach(validator => {
    switch (validator.type) {
    case 'required':
    validatorFns.push(Validators.required);
    break;
    case 'maxLength':
    validatorFns.push(Validators.maxLength(Number(validator.arg)));
    break;
    case 'minLength':
    validatorFns.push(Validators.minLength(Number(validator.arg)));
    break;
    case 'exactDigitsValidator':
    validatorFns.push(exactDigitsValidator(Number(validator.arg)));
    break;
    case 'requiredNonWhitespaceValidator':
    validatorFns.push(requiredNonWhitespaceValidator())
    break;
    }
    });

    if (validatorFns.length > 0) {
    control.setValidators(validatorFns);
    }
    }

    field.disabled ? control.disable() : control.enable();
    }

    patchControlValue(field: FormField, control: FormControl, data: any) {
    let controlData = data;

    if (field.inputType === 'date') {
    controlData = new Date(data);
    }

    if (field.type === 'select') {
    this.filteredOptions.get(field.controllerName)?.subscribe(options => {
    const selectedOption = options.find(option => option.value === data);
    if (selectedOption) {
    controlData = selectedOption.value;
    }
    });
    }

    control.setValue(controlData);
    }

    setupDropdown(field: FormField, control: FormControl) {
    let filterColumns: string[] = [];
    let filterOptions: string[] = [];
    let column = field.selectOptions.filterColumns.columns;

    if (column !== null && column.length > 0) {
    column.forEach(c => {
    filterColumns.push(c.columnName);
    filterOptions.push(c.filter);
    })
    }

    // populating the dropdownmap in the dropdown service
    this.dropdownHelper.loadDropdownOptions(field.selectOptions.dropdownTable, filterColumns, filterOptions);

    this.filteredOptions.set(
    field.controllerName,
    control.valueChanges.pipe(
    startWith(''),
    map(value => {
    const filterValue = typeof value === 'string' ? value.toLowerCase() : '';
    return this.dropdownHelper.dropdownOptionsMap
    .get(field.selectOptions.dropdownTable)!()
    .filter(option =>
    option.label.toLowerCase().includes(filterValue)
    );
    })
    ))
    }
    }


    form.component.html:

    <form [formGroup]="dataForm">
    <div formArrayName="forms">
    @for (form of getForms(); track form; let i = $index) {
    @if (i > 0) {
    <mat-divider class="m-5"></mat-divider>
    }
    <div [formGroupName]="i">
    @for (field of fieldsArray; track $index) {
    @if (field.display) {
    @switch (field.type) {
    <!-- different cases for different field types -->
    @case ('select') {
    <mat-form-field class="w-50 pb-3 pe-2" appearance="outline">
    <mat-label>{{ field.displayName }}</mat-label>
    <input type="text"
    placeholder="Pick one"
    matInput
    [formControlName]="field.controllerName"
    [matAutocomplete]="auto">
    <mat-error
    *ngIf="isFormControlInvalid(field.controllerName, i) && form.get(field.controllerName)?.hasError('required')">
    This field is required
    </mat-error>
    <mat-icon matSuffix>arrow_drop_down</mat-icon>
    <mat-autocomplete #auto="matAutocomplete"
    [displayWith]="displayFn(filteredOptions.get(field.controllerName)! | async)">
    @for (option of filteredOptions.get(field.controllerName)! | async; track option) {
    <mat-option [value]="option.value">{{ option.label }}</mat-option>
    }
    </mat-autocomplete>
    </mat-form-field>
    }
    }
    }
    }
    </div>
    }
    </div>
    </form>

    Continue reading...

Compartilhe esta Página