18

I'm using ng-select to select multiple values from a large list that is remotely loaded depending on what the user types in the box. Here are my requirements:

  1. Tags added dynamically
  2. Dropdown is not displayed for already selected values. It is only for typeahead of available, unselected values.

Here are the problems that I've run into so far:

  • Selected tags do not get displayed if not part of the items list.
  • Using the tag objects as an array with [(ngModel)] makes ng-select treat it like the value doesn't exist at all. ng-select seems to only use IDs for [(ngModel)] when [mutiple]=true, as opposed to the selected object when [multiple]=false
  • The tagsInput$ observable doesn't get fired if [isOpen]=false

I have verified that tagsService is operating correctly.

Current View:

<ng-select [items]="tags$ | async"
           bindValue="name"
           [loading]="tagsLoading"
           [typeahead]="tagsInput$"
           [multiple]="true"
           [isOpen]="tagsOpen"
           [searchFn]="filterSelectedTags"
           [(ngModel)]="selectedTagIds"></ng-select>

Current Controller:

class TagFormComponent {

  public tags$: Observable<Tag[]>;
  public tagsLoading: boolean;
  public tagsInput$: Subject<string>;
  public tagsChanger$: Subject<Tag[]>;
  public selectedTags: Tag[];
  public selectedTagIds: number[];
  public tagsOpen: boolean;

  constructor(private tagService: TagService) {

    this.tagsInput$ = new Subject<string>();
    this.tagsChanger$ = new Subject<Tag[]>();
    this.tagsChanger$.subscribe( tags => {
      this.selectedTagIds = tags.map(t => t.id);
      this.selectedTags = tags;
    });
    this.tags$ = concat(
      this.tagsChanger$,
      this.tagsInput$.pipe(
        debounceTime(200),
        distinctUntilChanged(),
        tap( () => {
          this.tagsOpen = true;
          this.tagsLoading = true;
        }),
        switchMap(q => this.tagService.search(q).pipe(
          catchError( err => { this.tagsLoading = false; console.error(err); return of([] ); }),
          tap( () => this.tagsLoading = false)
        ))
      )
    );

    this.tagsChanger$.next(this.assetGroup.tags);
  }

  public matcher(a: any, b: any): boolean {
    return !!a && b && a.id === b.id;
  }

  public filterSelectedTags(_q: string, item: Tag) {
    return !this.selectedTagIds.includes(item.id);
  }

  public tagAdded() {
    this.tagsOpen = false;
  }
}
eltiare
  • 1,817
  • 1
  • 20
  • 28

2 Answers2

0

Use the built-in "hideSelected" property of the ng-select to hide already selected items.

As for "Selected tags do not get displayed if not part of the items list", looks like you have to preload already selected items. They should always be added to the "items" list. Because of "hideSelected" they will not be shown in dropdown anyway. So component should first load existing items by their IDs and add them to "items" list, then component has to load items according to the typeahead and add them to the "items" list. This way existing items will always be available for ng-select.

Pupundra
  • 11
  • 2
0

You can easily achieve this by using mat-autocomplete in combination with mat-option. See complete examples for this here on the material-angular components website.

Or for multi-select you can combine mat-autocomplete with mat-option and a mat-chip-list. See complete examples for this here on the material angular components website

See also a similar question with suggestions here

In case none of those examples suffice your needs you can maybe elaborate and I could add more details to my answer.

Wilt
  • 41,477
  • 12
  • 152
  • 203