3

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);
          }
Flash
  • 924
  • 3
  • 22
  • 44
  • I dont' know exacly what are trying to do. Do you have an autocomplete component and as the field changes you want to fill the filtered options to be shown with data from a service? – Thomaz Capra Jun 29 '21 at 14:59
  • Yes that's right but because it's in a form array it gets tricky. I was basing it off of this example: https://stackblitz.com/edit/angular-szxkme?file=app%2Fautocomplete-display-example.html – Flash Jun 29 '21 at 15:18
  • I think what you are trying to do would be much easier if you restructured the code a bit. Can you try and create a stackblitz of your setup? I would make it so you don't have any sort of observable that you're passing to the autocomplete. Process all your data, then set a variable in the component to your processed data, then use that variable in your *ngFor. I think you should start with that – Chad K Jul 01 '21 at 15:03
  • It sounds like you're using a service to filter the array. I'm assuming you are doing an autocomplete that get's the next few suggestions from the server, correct? You should not need an array of observable. Take a look at the second answer on this question: https://stackoverflow.com/questions/57852645/angular-material-autocomplete-from-api – Chad K Jul 01 '21 at 15:09
  • yes that's correct @ChadK I had to add an array to be able to track the user entires for multiple rows in a formArray if that makes sense. I was basing it off of [this](https://stackblitz.com/edit/angular-autocomplete-formarray?file=app%2Fautocomplete-display-example.ts) example – Flash Jul 01 '21 at 15:28
  • First of all, type your data instead of using `any`. Second, please provide a [mcve], best would be a StackBlitz. There is not enough code to reproduce the issue. – AT82 Jul 02 '21 at 06:40
  • Can you produce a valid result without a FormArray? If you just did a single FormControl or FormGroup, can you get the result you want? @Flash – Chad K Jul 02 '21 at 15:09
  • Yup it worked just fine when I didn't need to do a form array – Flash Jul 02 '21 at 18:22
  • https://stackoverflow.com/questions/61503425/angular-9-formarray-search-operation-executing-for-only-first-dynamic-control/61519813#61519813 – Eliseo Jul 04 '21 at 19:34

2 Answers2

4

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.

deaks
  • 305
  • 2
  • 8
0

If i see your code correctly you subscribe to the observable. Therefore you don't get the Array values in the template but the subscription. You should remove the subscribe method to get the template working, or you save the value inse the subscribe method and use it withou observables. I don't fully understand what you want to do with the "filteredList" in your example but you can wrap the code inside a tap operator https://rxjs.dev/api/operators/tap

Krosan
  • 684
  • 1
  • 6
  • 15