javascripthtmlangularangular-materialmat-autocomplete

Mat-AutoComplete error when using formArray


I added a formarray to account for multiple rows. In order to make it work with the index I had to change my definition from : shoppingCartList Observable<any[]>; to shoppingCartList: Observable<any[]>[] = []; however when I do that it throws an error in my filter function saying missing the following properties from type 'Observable<any[]>[]': length, pop, push, concat. it looks like it's because I am calling a service to filter a list. What is the correct way to implement this: .HTML

<mat-autocomplete #auto="matAutocomplete" [displayWith]="entry">
       <mat-option *ngFor="let case of shoppingCartList[i] | async" [value]="case">
            {{title}}
         </mat-option>
 </mat-autocomplete>

.TS

    shoppingCartList: Observable<any[]>[] = [];
            
             this.shoppingCartList[index] = arrayControl.at(index).get('name').valueChanges //ERROR: (property) DraftformComponent.filteredPrimaryCaseSerial: Observable<any[]>[]
    Type 'Subscription' is missing the following properties from type 'Observable<any[]>

                .pipe(debounceTime(this.debounceTime))
                  .subscribe(
                    (val) => {
                        this.filteredList= this.getFilteredlist(val); //ERROR: (property) DraftformComponent.filteredPrimaryCaseSerial: Observable<any[]>[]
    Type 'Observable<any[]>' is missing the following properties from type 'Observable<any[]>[]
                      }
                  );
        
         public getFilteredList(val: string): Observable<any[]> {
            return this.myService.getListByValue(val);
          }

Solution

  • Firstly, the reason you are seeing the error you are seeing is because of the way you've typed your list.

    Observable<any[]>[] = [];

    You've told the compiler here that the type of your shopping cart list is an array of multiple arrays of observables.

    So, the error missing the following properties from type 'Observable<any[]>[]': length, pop, push, concat is just telling you that what you are saying getFilteredList will return is not actually what the function is expecting to return -- it's expecting to return an observable of an array (of any type).

    It isn't anything to do with using a service to filter a list. I am also a bit unsure by what you mean when you refer to using a form array to account for multiple rows in this context.

    With that in mind - here's what I think you are aiming for based on your question.

      /**
       * Define a form which contains your FormControl you wish
       * to use for your autocomplete.
       */
    
      form = new FormGroup({
        shoppingListSearch: new FormControl()
      });
    

    Access it like this:

      get shoppingListSearch(): FormControl {
        return this.form.get('shoppingListSearch') as FormControl;
      }
    

    FormControls expose their value changes as an observable, handily called valueChanges. You already are on the right track in your attempt!

    Rather than manually subscribing to observables, I would use Angular's templates for this. It manages the tidying up of subscriptions for you on destroy of the component. If you manually subscribe to observables as you have done above, and don't unsubscribe, you will leave subscriptions lying around, leading to memory leaks.

    Define an observable like so:

      filteredList$: Observable<
        ShoppingListItem[]
      > = this.shoppingListSearch.valueChanges.pipe(
        debounceTime(1000),
        // Use a switchMap here to subscribe to the inner observable.
        // This is assuming your filtering needs to make a HTTP request.
        // switchMap will maintain one inner subscription, so should you 
        // make another request while one is in flight, the in flight 
        // request will be cancelled and a new one made with your new
        // search term.  
        switchMap((searchTerm: string) =>
          // this can be whatever filtering logic you want I guess.
          // To reiterate, I used a switchMap above as getFilteredList
          // returns an observable. An operator like map or tap wouldn't 
          // work here, as they don't handle subscribing to inner observables.
          this.myService.getFilteredList(searchTerm)
        )
      );
    

    Here's the template:

    <form class="example-form" [formGroup]="form">
      <mat-form-field appearance="fill">
        <mat-label>Search...</mat-label>
        <input
          class="example-full-width"
          placeholder="Search for something..."
          formControlName="shoppingListSearch"
          [matAutocomplete]="auto"
          matInput
        />
      </mat-form-field>
    
      <mat-autocomplete #auto="matAutocomplete" [displayWith]="entry">
        <!-- Here is where we subscribe to the filtered list observable -->
        <mat-option *ngFor="let shoppingItem of filteredList$ | async" [value]="shoppingItem.name">
          {{shoppingItem.name}}
        </mat-option>
      </mat-autocomplete>
    </form>
    

    Here's the StackBlitz.