Home > OS >  Extract distinct records from a Firestore collection query
Extract distinct records from a Firestore collection query

Time:08-19

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?

CodePudding user response:

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;
            })
        );
}

CodePudding user response:

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;
        })
      );
  }
  • Related