Search code examples
angularrxjsrxjs-pipeable-operators

how to use rxjs distinct on array of object


I have already consulted other questions regarding this issue but I have not pulled a spider out of the hole. I have to use distinct on an array of objects, with the code that I propose to you the distinct does not work and I have no errors in the console. How can I make it work?

carRecent

export class CarRecent {

    id:number;
    carId:number;
    userId:number;
    imageURL:string;

}

carsService

 getRecentCars(userId){

    let params = new HttpParams().set('userId', userId);
    let options = {headers:this.headers, params:params};

    return this.http.get<CarRecent[]>(this.host + 'getRecentCars', options);

  }

recentCar.component.ts

export class RecentCarsComponent implements OnInit {

  url:string = 'assets/';

  recentCars = new BehaviorSubject<CarRecent[]>(null);
  subscriptionRecentCars;

  constructor( public carsService:CarsService, public authService:AuthenticationService ) { }

  ngOnInit(): void {

    this.loadRecentCars();

  }

  loadRecentCars(){

    this.subscriptionRecentCars = this.carsService.getRecentCars(this.authService.currentUserValue.id)
    .pipe( 
      tap( cars => console.log(cars) ),
      distinct( (car:any) => car.carId )
    )  
    .subscribe({
        next:cars => {
          this.recentCars.next(cars);
        }
      })

  }

}

Solution

  • RxJS operators are often confused with array functions, but they operate on the whole value returned by the observable - they aren't specific to arrays.

    In your case you want to return a distinct array of objects based on some key. You want to transform the array returned by the observable to another value - a subset of the array. When transforming data in the pipe, use the map operator.

    Once you're inside the map operator, you can reduce the array to return distinct values.

    ngOnInit() {
    
      this.carsService.getRecentCars(this.authService.currentUserValue.id)
        .pipe( 
          tap(cars => console.log(cars)),
          map(cars => {
            const uniques = new Set();
            return cars.reduce((arr, current) => {  
              if (!uniques.has(current.carId)) {
                arr.push(current);
                uniques.add(current.carId);
              }
              return arr;
            }, []);
          })
        ).subscribe({ 
          next:cars => {
            this.recentCars.next(cars);
          }
        });  
    }
    

    I have used a Set to avoid redundant array iterations.

    DEMO: https://stackblitz.com/edit/angular-8qnsp8