0

I have two components. One consists of a multi select dropdown. Another displays the data fetched from an api through a service.

I want to filter the data displayed based on the values selected on multi select. How to approach this?

stackblitz In this stackblitz, I reproduced the structure. select components contains the multiselect and hello component displays the data fetched from the api through api.service. The values selected in the multiselect is sent to hello component through another filter i.e filter.service. I'm able to display the values selected but I want to filter the data based on those values.

P.S: In the stackblitz, I explicilty mentioned the two components' selectors in app.component. But in my project, the components are served through a router outlet.

Nijoo
  • 1
  • 3

1 Answers1

1

The Angular IO Guides (into the fundamentals link) are a great place to gain basic familiarity for solving problems like this. This section explains some component interaction.

In your case, your two components might be children of the component that calls your service. That wrapper might feed the display component a list (or stream) copied from the result of the API, and might be listening to an output from the dropdown component. When that output triggers, the wrapper could modify the copied list (or stream) it's sending to the display component.

Here is a rough example, but as a disclaimer it's theoretical and I did not run it.

// wrapper html

<multiselect-component (selectionChange)="selectedItems($event)"></multiselect-component>
<display-component [items]="displayItems$ | async"></display-component>


//wrapper ts

private selectedSubject = new BehaviorSubject<any>(); // add your type
set selectedItems(items: any[]) { // add your type
    this.selectedSubject.next(items);
}

displayItems$: Observable<any>; // add your own type

ngOnInit() {
    // the static 'merge' from 'rxjs' to allow both streams to trigger
    this.displayItems$ = merge(
        this.apiService.getData(), // should return an observable
        this.selectedSubject.asObservable(),
    ).pipe(
        // something similar to this
        switchMap((data, filterItems) =>
            data.filter(d =>
                !filterItems.includes(d)
            )
        ),
    );
}

I'll also offer the disclaimer that Angular is a very opinionated framework. There are a lot of ways to do things! Also, RXJS can do a lot of stuff for you. The easiest way at this point is to probably do just about the same thing as before.


After StackBlitz was added:

this.changeDetectorRef.detectChanges(); should not be needed inside of init. I also don't think that you should have to detect changes on your own at all. I find that if you have to do this, it usually (but not always) means that something is slightly off in the plan/structure. There are multiple ways to solve that as well, I personally stick with container (page) components and display components.

Something like

//display component
ngOnInit() {
    // the static 'merge' from 'rxjs' to allow both streams to trigger
    this.obs = merge(
        this.cardsInfo.getCardsInfo(), // should return an observable
        this.filterService.getCategories(),
    ).pipe(
        // something similar to this
        switchMap((data, filterItems) =>
            data.filter(d =>
                !filterItems.includes(d)
            )
        ),
    );
}

It also seems to me like you're basically trying to follow this StackBlitz from this Angular Material example (go to table with filtering).

  • In my case, the display-component is served by a router outlet. So I tried using a service to send the selectedItems array from multiselect-component to display-component. But I didn't know how to proceed after that. Can you help with this if I provide stackblitz? – Nijoo Jan 27 '19 at 20:47
  • It couldn't hurt to post the stackblitz on the question, I can give you a slightly more relevant example then –  Jan 27 '19 at 20:49
  • I have added the code now. Please have a look at it. – Nijoo Jan 28 '19 at 12:18
  • I used `this.changeDetectorRef.detectChanges();` to get rid of `ExpressionChangedAfterItHasBeenCheckedError` error. In your code, does the `obs` gets updated everytime the multiselect value gets changed? I'm finding it a lot difficult to implement it too. If possible can you implement it in my stackblitz? – Nijoo Jan 28 '19 at 13:52
  • ExpressionChangedAfterItHasBeenCheckedError - https://stackoverflow.com/questions/43375532/expressionchangedafterithasbeencheckederror-explained. If this is difficult to implement, then taking more time learning foundational things via the hero editor https://angular.io/tutorial/toh-pt1 and learn-rxjs https://www.learnrxjs.io/ is my recommendation (it's what I'd do). –  Jan 28 '19 at 14:27