SPARTACUS customization of SearchboxComponent service

1.1k Views Asked by At

Problem: I am working on a custom search box configuration, as our search API endpoint requires 3 additional parameters. The endpoint should be called with dynamic parameters.

https://localhost:9002/occ/v2/{baseSiteId}/products/customsearch/param1/param2/param3?query=xyz&pageSize=5&lang=en&curr=USD&currentPage=1

I've received the following instructions so far:

The searchbox component is delegating the actual search to the SearchBoxComponentService. This service is using the SearchboxService which is a facade around the central store and lower level connectors/adapters. The search box configuration is passed into the facade/store, which you could use to extend the search query that is ending up at the backend.

You should start providing a custom version of SearchBoxComponentService, and override the search() method. If the additional search parameters would match the endpoint configuration query parameters, I believe you're good to go, but honestly i'm doing this from top of head so I might miss something.

Basis on the above i've came up with the following:

search(query: string, config: SearchBoxConfig): Observable<SearchResults> {
    if (!query || query === '') {
        this.clearResults();
        return;
    }

    if (
        config.minCharactersBeforeRequest &&
        query.length < config.minCharactersBeforeRequest
    ) {
        return;
    }

    if (config.displayProducts) {
        this.ProductSearch(query, {
        pageSize: config.maxProducts,
        }).subscribe(data => {
        return data;
        }, error => {
        });
    }

    if (config.displaySuggestions) {
        this.searchService.searchSuggestions(query, {
        pageSize: config.maxSuggestions,
        });
    }
}

    
// tslint:disable-next-line: typedef
ProductSearch(query: string, searchConfig?: SearchConfig): Observable<SearchResults> {
    const pageSize = searchConfig.pageSize ? searchConfig.pageSize : 5;
    const currentPage = searchConfig.currentPage ? searchConfig.currentPage : 1;
    const fetchUrl = `${this.occEndpointsService.getBaseEndpoint()}/products/markethubsearch/${this.soldTo}/${this.shipTo}/${this.businessCategory}?query=${query}&pageSize=${pageSize}&lang=en&curr=USD&currentPage=${currentPage}`;
    
        return this.http.get<SearchResults>(fetchUrl, {
        params: new HttpParams().set('fields', 'FULL')
        });
}

I am however still unable to subscribe to the SearchResults observable. Can you please help me with this?

2

There are 2 best solutions below

2
On BEST ANSWER

Thanks for the question. I guess I've overlooked a few things when I initially answered you on slack.

To begin with, the SearchBoxComponentService.search doesn't just take the search config and pass it down to the SearchboxService. It does only takes specifics from the config. Therefor, I'd recommend to override the SearchboxService instead, i.e.:

@Injectable({
  providedIn: 'root',
})
export class CustomSearchboxService extends SearchboxService {
  search(query: string, config?: SearchConfig): void {
    super.search(query, { ...config, foo: 'bar' } as SearchConfig);
  }
}

With this setup, your config will end up in the action state and therefor available at the OccProductSearchAdapter. The adapter will use the searchConfig to create the endpoint.

You can now configure your search endpoint, using dynamic parameters, by providing a configuration to Spartacus:

provideConfig({
  backend: {
    occ: {
      endpoints: {
        productSearch:
          'products/search?fields=products(code)&foo=${foo}'
      }
    }
  }
})

However, this will not allow for dynamic path properties. I'll talk to the team to see if we can allow for this. You might be able to change your endpoints to work with query parameters instead, and you'd be good to go.

To work with path parameters you evaluate these approaches:

  • customize the OccProductSearchAdapter and implement the getSearchEndpoint. You can leverage the searchConfig argument and feed in the path parameters.
  • introduce an angular interceptor. This isn't so clean, as all http requests will go through this interceptor.
1
On
  1. As @tobi-or-not-tobi noted, the easiest way will be to pass your custom dynamic parameters alongside in the SearchConfig.

  2. Currently the OccProductSearchAdapter.search() method by default places every property of the SearchConfig object into query params of the endpoint. So even if you keep the default endpoint shape, you'll get out of the box your custom params appended as query params: products/search?fields=...&query=...&pageSize=...&soldTo=...shipTo=...&businessCategory=...

    It's because the OccProductSearchAdapter.getSearchEndpoint() calls inside the generic url builder OccEndpointsService while passing the SearchConfig as 3rd argument (meant for building query params only):

    protected getSearchEndpoint(
      query: string,
      searchConfig: SearchConfig
    ): string {
      return this.occEndpoints.getUrl(
        'productSearch',  // endpoint name
        {},               // url params for placeholders ${...}
        {                 // query params to append:
          query,
          ...searchConfig,
        }
      );
    }
    
  3. To achieve your goal (dynamic params being part of the url params ), you'll need to both:

    i) add ${} placeholders in the endpoint config:

    provideConfig({
      backend: {
        occ: {
          endpoints: {
            productSearch:
              'products/search/${shipTo}/${soldTo}/${businessCategory}?fields=FULL`'
          }
        }
      }
    })
    

    ii) customize the method OccProductSearchAdapter.getSearchEndpoint() to unpack your custom params from SearchConfig object and pass them as the 2nd argument into the generic url builder. For example in your app.module, provide:

     @Injectable()
     export class CustomOccProductSearchAdapter extends OccProductSearchAdapter {
       protected getSearchEndpoint(
         query: string,
         searchConfig: SearchConfig
       ): string {
         // unpack custom params:
         const {
           shipTo,
           soldTo,
           businessCategory,
           ...restSearchConfig
         } = searchConfig as any;
    
         return this.occEndpoints.getUrl(
           'productSearch',      // endpoint name
           {                     // url params for placeholders ${...}:
             shipTo,
             soldTo,
             businessCategory,
           },
           {                    // query params to append:
             query,
             ...restSearchConfig,
           }
         );
       }
     }
    
     // AppModule:
     @NgModule({
       /* ... */
       providers: [
         {
           provide: ProductSearchAdapter,
           useClass: CustomOccProductSearchAdapter,
         },
       ]
    

Note: @Nagaprasad Vendra mentioned in comment that the search() method of the CustomSearchboxService is not called. To clarify: this service handles only the auxiliary search results (suggestions in the searchbox). The ProductListingComponent uses different facade to get search results - ProductSearchService. So you'll also need to customize that one too.