angularangular-materialngrx

Implement a search-function in mat-selection-list


I have been searching around for a solution for this but cant figure it out how this should me made. Here is a stackblitz of what im almost looking for, but i dont want it to appear like a dropdown list. https://stackblitz.com/edit/angular-yo2o9o?file=app%2Fapp.component.html

I would love to have a list like my own code below, but implement a search field over the list and use it to filter users$ depending on the input (but still saving the selected users) ...

Edit: Found this one, https://stackblitz.com/edit/angular-i3pfu2-xgembc?file=app%2Flist-selection-example.ts,

This is how it should look, but i cant get this to work with my code..

Html:

<div class="userContainer">
    <p>Choose participants</p>
    <mat-selection-list class="form-group" #selectedUsers formControlName="users">
      <mat-list-option *ngFor="let user of users$ | async" [value]="user">
        {{ user.name }}
      </mat-list-option>
    </mat-selection-list>
    <p>Invited users: {{ selectedUsers.selectedOptions.selected.length }}</p>
  </div>

users$ is coming from an selector from my state

this.store$.dispatch(new fromUsers.GetUsers());
this.users$ = this.store$.pipe(select(fromUsers.getRelevantUsers(+this.userId)));

And this is the form i'm using..

createEventForm() {
 this.eventForm = this.fb.group(
   {
     users: [null],
     more inside...
   }
  );
 }

Solution

  • A "filter search" is't only a FormControl, you subscribe to valueChanges and, using switchMap and debounce return a list, well, in code. If you has an array of values and a formControl

      search = new FormControl();
      typesOfShoes: string[] = ["Boots","Clogs","Loafers","Moccasins","Sneakers"];
    

    You create an observable

    $search = this.search.valueChanges.pipe(
        startWith(null),
        debounceTime(200),
        switchMap((res: string) => {
          if (!res) return of(this.typesOfShoes);
          res = res.toLowerCase();
          return of(
            this.typesOfShoes.filter(x => x.toLowerCase().indexOf(res) >= 0)
          );
        })
      );
    

    And use

    <mat-form-field >
        <mat-label>Search</mat-label>
        <input matInput [formControl]="search">
      </mat-form-field>
    
    <mat-selection-list #shoes [formControl]="shoesControl">
      <mat-list-option *ngFor="let shoe of $search|async" [value]="shoe">
        {{shoe}}
      </mat-list-option>
    </mat-selection-list>
    

    See stackblitz

    Updated As Charlie point, the problem is that if we change the search, you loose the values checked, so we can NOT use [formControl]="shoesControl"

    So, we are going to use the event (selectionChange) of the mat-list and the property [selected] of the mat-option to change the value of the FormControl

    So, we are going to has

    <mat-selection-list #shoes 
      (selectionChange)="selectionChange($event.option)" >
      <mat-list-option *ngFor="let shoe of $search|async" 
       [value]="shoe" [selected]="shoesControl.value && 
                    shoesControl.value.indexOf(shoe)>=0">
        {{shoe}}
      </mat-list-option>
    </mat-selection-list>
    

    See that there're no [formControl], but remember that a FormControl exist if we has an input or not. Well the function selectionChange received as argument an object with the property selected and value, so we has

    selectionChange(option:any)
    {
      let value=this.shoesControl.value || []
      if (option.selected)
        value.push(option.value)
      else
        value=value.filter((x:any)=>x!=option.value)
      this.shoesControl.setValue(value)
    }
    

    NOTE: I updated the stackblitz