skip to Main Content

I’m looking for a hand with making an observable contain only distinct records.
I’m using the observable in an angular mat-autocomplete with an async pipe and querying firebase with the user’s typed-in values to bring back the list of options.

The template mat-autocomplete:

      <mat-form-field appearance="outline">
        <mat-label>Customer Name</mat-label>
        <input
          type="text"
          placeholder="Start typing customer name"
          formControlName="customerName"
          matInput
          [matAutocomplete]="customerName"
        />
        <mat-autocomplete
          #customerName="matAutocomplete"
          [displayWith]="displayFn"
        >
          <mat-option
            *ngFor="let option of filteredPurchaseOrders | async"
            [value]="option"
            (onSelectionChange)="autofill(option)"
          >
            {{ option.customerName }}
          </mat-option>
        </mat-autocomplete>
      </mat-form-field>

Subscribing to valuechanges of the input field:

    this.filteredPurchaseOrders =
      this.purchaseOrderForm.controls.customerName.valueChanges.pipe(
        filter((val) => typeof val !== 'object' && val !== null),
        debounceTime(400),
        switchMap((value) => {
          return this.purchaseOrdersService.searchCustomerDetails(value);
        })
      );
  }

From the purchaseOrdersService is returned the observble:

  searchCustomerDetails(searchString: string): Observable<CustomerDetails[]> {
    const start = searchString.toLowerCase();
    const end = start.replace(/.$/, (c) =>
      String.fromCharCode(c.charCodeAt(0) + 1)
    );

    return this.firestore
      .collection('purchaseOrders', (ref) =>
        ref
          .where('customerNameLower', '>=', start)
          .where('customerNameLower', '<', end)
      )
      .get()
      .pipe(
        map((results) => {
          return results.docs.map((po) => {
            var data = po.data() as CustomerDetails;
            return {
              customerAddress: data.customerAddress,
              customerBusinessIdent: data.customerBusinessIdent,
              customerBusinessIdentType: data.customerBusinessIdentType,
              customerName: data.customerName,
            };
          });
        })
      );
  }

This is working fine – the search returns the documents that match the customer name as typed in by the user and the mat-autocomplete options show the values. The issue is that, as should be fairly obvious, if there are many records that have the same customer details then there will be many identical-looking results in the observable.

I need to be able to filter the observable so that it only contains distinct records of the CustomerDetails object.

Can anyone help me with the appropriate RXJS pipe operations (or some other solution) to get this done?

3

Answers


  1. Chosen as BEST ANSWER

    Fixed it with an a little ES6

      searchCustomerDetails(searchString: string): Observable<CustomerDetails[]> {
        const start = searchString.toLowerCase();
        const end = start.replace(/.$/, (c) =>
          String.fromCharCode(c.charCodeAt(0) + 1)
        );
        console.log('%c fetching customers from database', 'color: #ff7a70');
        return this.firestore
          .collection('purchaseOrders', (ref) =>
            ref
              .where('customerNameLower', '>=', start)
              .where('customerNameLower', '<', end)
          )
          .get()
          .pipe(
            map((results) => {
              const resultsDocs = results.docs.map((po) => {
                var data = po.data() as CustomerDetails;
    
                return {
                  customerAddress: data.customerAddress,
                  customerBusinessIdent: data.customerBusinessIdent,
                  customerBusinessIdentType: data.customerBusinessIdentType,
                  customerName: data.customerName,
                };
              });
    
              const uniqueObjArray = [
                ...new Map(
                  resultsDocs.map((item) => [item['customerName'], item])
                ).values(),
              ];
              return uniqueObjArray;
            })
          );
      }
    

  2. Let’s assume the unique field is customerName. We can use a hashMap to filter out duplicates.

    searchCustomerDetails(searchString: string): Observable<CustomerDetails[]> {
        const start = searchString.toLowerCase();
        const end = start.replace(/.$/, c => String.fromCharCode(c.charCodeAt(0) + 1));
    
        return this.firestore
            .collection('purchaseOrders', ref =>
                ref.where('customerNameLower', '>=', start).where('customerNameLower', '<', end)
            )
            .get()
            .pipe(
                map(results => {
                    const output = [];
                    const hashMap = {};
                    results.docs.forEach(po => {
                        var data = po.data() as CustomerDetails;
                        if (!hashMap[data.customerName]) {
                            output.push({
                                customerAddress: data.customerAddress,
                                customerBusinessIdent: data.customerBusinessIdent,
                                customerBusinessIdentType: data.customerBusinessIdentType,
                                customerName: data.customerName,
                            });
                            hashMap[data.customerName] = true;
                        }
                    });
                    return output;
                })
            );
    }
    
    Login or Signup to reply.
  3. RxJS -> distinct()

    https://rxjs.dev/api/operators/distinct

    this.filteredPurchaseOrders = this.purchaseOrderForm.controls.customerName.valueChanges.pipe(
      filter((val) => typeof val !== 'object' && val !== null),
      debounceTime(400),
      switchMap((value) => {
        return this.purchaseOrdersService.searchCustomerDetails(value).pipe(
          concatAll(),
          distinct(({ customerName }) => customerName),
          toArray(),
        );
      }),
    );
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search