Dropdown Option `<mat-option>` Not Visually Selected After Setting Value with `patchValue` in...

Discussão em 'Angular' iniciado por Rocky, Dezembro 9, 2024.

  1. Rocky

    Rocky Guest

    Current Behavior: When setting the value of the product form control via patchValue inside the getProducts method, the form control updates its value correctly. However, the corresponding <mat-option> in the autocomplete dropdown does not visually show as selected on the initial load of the component. [​IMG]

    Expected Behavior: The <mat-option> corresponding to the value set by patchValue should visually appear as selected in the autocomplete dropdown when the component is loaded. [​IMG]

    Steps to Reproduce:

    1. Open the application and navigate to the stepper component.
    2. Select a user in Step 1.
    3. Proceed to Step 2, where the getProducts method sets the value of the productCtrl control programmatically using patchValue.
    4. Open the autocomplete dropdown for the product selection.

    Code Snippet:

    <mat-stepper
    linear
    #stepper
    (selectedIndexChange)="selectedStepperIndex.set($event)"
    >
    <mat-step [stepControl]="userFormGroup">
    <form class="example-form" [formGroup]="userFormGroup">
    <ng-template matStepLabel>Step 1</ng-template>
    @defer (when selectedStepperIndex() === 0; prefetch on idle) {
    <user [form]="userFormGroup"></user>
    }
    <div>
    <button mat-button matStepperNext>Next</button>
    </div>
    </form>
    </mat-step>
    <mat-step [stepControl]="productFormGroup">
    <form [formGroup]="productFormGroup">
    <ng-template matStepLabel>Step 2</ng-template>
    @defer (when selectedStepperIndex() === 1; prefetch on idle) {
    <product
    [form]="productFormGroup"
    [selectedUserId]="userFormGroup.controls.userCtrl.value?.id ?? ''"
    ></product>
    }
    <div>
    <button mat-button matStepperPrevious>Back</button>
    <button mat-button matStepperNext>Next</button>
    </div>
    </form>
    </mat-step>
    <mat-step>
    <ng-template matStepLabel>Done</ng-template>
    <p>You are now done.</p>
    <div>
    <button mat-button matStepperPrevious>Back</button>
    <button mat-button (click)="stepper.reset()">Reset</button>
    </div>
    </mat-step>
    </mat-stepper>


    import { Component, inject, signal } from '@angular/core';
    import {
    FormBuilder,
    Validators,
    FormsModule,
    ReactiveFormsModule,
    FormControl,
    FormGroup,
    } from '@angular/forms';
    import { MatAutocompleteModule } from '@angular/material/autocomplete';
    import { MatInputModule } from '@angular/material/input';
    import { MatFormFieldModule } from '@angular/material/form-field';
    import { MatStepperModule } from '@angular/material/stepper';
    import { MatButtonModule } from '@angular/material/button';
    import { UserComponent } from 'src/user/user.component';
    import { ProductComponent } from 'src/product/product.component';

    export interface User {
    id: string;
    name: string;
    }

    export interface Product {
    id: string;
    desc: string;
    }

    export interface UserForm {
    userCtrl: FormControl<User | null>;
    }

    export interface ProductForm {
    productCtrl: FormControl<Product | null>;
    }

    @Component({
    selector: 'stepper-editable-example',
    templateUrl: 'stepper-editable-example.html',
    styleUrl: 'stepper-editable-example.css',
    imports: [
    MatButtonModule,
    MatStepperModule,
    FormsModule,
    ReactiveFormsModule,
    MatFormFieldModule,
    MatInputModule,
    MatAutocompleteModule,
    UserComponent,
    ProductComponent,
    ],
    })
    export class StepperEditableExample {
    private _formBuilder = inject(FormBuilder);
    selectedStepperIndex = signal(0);

    userFormGroup: FormGroup<UserForm> = this._formBuilder.group({
    userCtrl: [null as User | null, Validators.required],
    });

    productFormGroup: FormGroup<ProductForm> = this._formBuilder.group({
    productCtrl: [null as Product | null, Validators.required],
    });
    }


    <mat-form-field class="example-full-width">
    <mat-label>User</mat-label>
    <input
    type="text"
    placeholder="Pick one"
    matInput
    [formControl]="form.controls.userCtrl"
    [matAutocomplete]="auto"
    />
    <mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
    @for (user of users; track user) {
    <mat-option [value]="user">{{ user.name }}</mat-option>
    }
    </mat-autocomplete>
    </mat-form-field>


    import { Component, Input } from '@angular/core';
    import { FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
    import { MatAutocompleteModule } from '@angular/material/autocomplete';
    import { MatInputModule } from '@angular/material/input';
    import { MatFormFieldModule } from '@angular/material/form-field';
    import { User, UserForm } from 'src/example/stepper-editable-example';

    @Component({
    selector: 'user',
    templateUrl: 'user.component.html',
    styleUrl: 'user.component.css',
    imports: [
    FormsModule,
    MatFormFieldModule,
    MatInputModule,
    MatAutocompleteModule,
    ReactiveFormsModule,
    ],
    })
    export class UserComponent {
    @Input() form: FormGroup<UserForm>;
    users: User[] = [
    {
    id: '1',
    name: 'John',
    },
    {
    id: '2',
    name: 'David',
    },
    {
    id: '3',
    name: 'Smith',
    },
    ];

    displayFn(user: User): string {
    return user ? user.name : '';
    }
    }


    <div style="display: flex; flex-direction: column;">
    <mat-form-field class="example-full-width">
    <mat-label>Product</mat-label>
    <input
    type="text"
    placeholder="Pick one"
    matInput
    [formControl]="form.controls.productCtrl"
    [matAutocomplete]="autoProduct"
    />
    <mat-autocomplete
    #autoProduct="matAutocomplete"
    [displayWith]="displayProductFn"
    >
    @for (product of products; track product) {
    <mat-option [value]="product">{{ product.desc }}</mat-option>
    }
    </mat-autocomplete>
    </mat-form-field>
    </div>


    import {
    Component,
    Injectable,
    Input,
    OnChanges,
    OnInit,
    SimpleChanges,
    } from '@angular/core';
    import { FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
    import { MatAutocompleteModule } from '@angular/material/autocomplete';
    import { MatInputModule } from '@angular/material/input';
    import { MatFormFieldModule } from '@angular/material/form-field';
    import {
    Product,
    ProductForm,
    User,
    } from 'src/example/stepper-editable-example';
    import { HttpClient } from '@angular/common/http';
    import { BehaviorSubject, Observable, of } from 'rxjs';

    @Component({
    selector: 'product',
    templateUrl: 'product.component.html',
    styleUrl: 'product.component.css',
    imports: [
    FormsModule,
    MatFormFieldModule,
    MatInputModule,
    MatAutocompleteModule,
    ReactiveFormsModule,
    ],
    })
    export class ProductComponent implements OnInit {
    @Input() form: FormGroup<ProductForm>;
    private _selectedUserId: string = '';
    private isFirstChange = true;
    @Input()
    get selectedUserId(): string {
    return this._selectedUserId;
    }
    set selectedUserId(value: string) {
    if (this._selectedUserId !== value) {
    this._selectedUserId = value;
    if (!this.isFirstChange) {
    console.log('SETTER INSIDE IF...');
    this.onSelectedUserIdChange();
    }
    this.isFirstChange = false;
    }
    }
    products: Product[] = [];

    constructor(private productService: ProductService) {}

    ngOnInit() {
    if (this.selectedUserId) {
    this.getProducts();
    }
    }

    displayProductFn(product: Product): string {
    return product ? product.desc : '';
    }

    getProducts() {
    this.productService
    .getProductsService(this.selectedUserId ? this.selectedUserId : '')
    .subscribe((products) => {
    this.products = products;
    this.form.patchValue({
    productCtrl: products[1],
    });
    });
    }

    private onSelectedUserIdChange(): void {
    this.form.controls.productCtrl.setValue(null);
    this.getProducts();
    }
    }

    @Injectable({ providedIn: 'root' })
    export class ProductService {
    private sub = new BehaviorSubject<Product[]>([]);

    private productMap: { [key: string]: Product[] } = {
    '1': [
    {
    id: '1',
    desc: 'One',
    },
    {
    id: '2',
    desc: 'Two',
    },
    {
    id: '3',
    desc: 'Three',
    },
    ],
    '2': [
    {
    id: '1',
    desc: 'Four',
    },
    {
    id: '2',
    desc: 'Five',
    },
    {
    id: '3',
    desc: 'Six',
    },
    ],
    '3': [
    {
    id: '1',
    desc: 'Seven',
    },
    {
    id: '2',
    desc: 'Eight',
    },
    {
    id: '3',
    desc: 'Nine',
    },
    ],
    };

    public observable$: Observable<Product[]> = this.sub.asObservable();
    constructor(private httpClient: HttpClient) {}

    getProductsService(userId: string): Observable<Product[]> {
    const products = this.productMap[userId] || [];
    return of(products);
    // return this.httpClient.get<Product[]>(http://api/${userId});
    }
    }


    Environment:

    • Angular: 18.2.9
    • Angular Material: 18.2.10

    StackBlitz link: https://stackblitz.com/edit/zzialkdj-d8rtr2sa

    Continue reading...

Compartilhe esta Página