0

I am trying to optimize the performances of my keyword search engine by sending aggregations/facet query and the actual search query in parallel.

I have tried many solutions, and in particular that one:

Angular2: which is the best way to make multiple sync calls with Observables?

The problem is that this solutions uses forkJoin that sends the call in parallel but wait for all http get to return. This defeat the point of my parallel calls. My understanding is that the solution is to use the same type of code but using mergeMap instead.

My existing code is the following:

ngOnInit(): void {
    this.route.queryParamMap
      .pipe(
        // extract parameters
        switchMap(params => {
          const requestedQuery = params.get('query');
          if (this.query !== requestedQuery) {
            // we reset the results and criteria
            this.results = undefined;
            this.aggregationCriteria = [];
          }
          this.query = requestedQuery;
          // set the search field
          this.searchForm.get('search').setValue(this.query);
          // extract the page if present
          const page = this.extractPageFromParameters(params);
          // extract the aggregations if there are some
          // we consider all parameters as potential aggregations, except `query` and `page`
          this.aggregationCriteria = this.extractCriteriaFromParameters(params);
          // launch the search
          return this.searchService.search(this.query, true, this.aggregationCriteria, page)
          // handle a potential error, by returning no result
          // but allow to trigger a new search
            .pipe(
              catchError(() => EMPTY)
            );
        })
      )
      .subscribe(results => {
        this.loading = false;
        // sets the results and the aggregations if there are some
        this.results = results;
        if (results.aggregations.length) {
          this.aggregations = results.aggregations;
        }
      });
    this.suggesterTypeahead = this.searchService.getSuggesterTypeahead();
  }

I have tried the following, among many things, but this last version doesn't compile and I am getting stuck. Any suggestions or hint would be much welcomed.

ngOnInit(): void {
    this.route.queryParamMap
      .pipe(
        // extract parameters
        switchMap(params => {
          const requestedQuery = params.get('query');
          if (this.query !== requestedQuery) {
            // we reset the results and criteria
            this.results = undefined;
            this.aggregationCriteria = [];
          }
          this.query = requestedQuery;
          // set the search field
          this.searchForm.get('search').setValue(this.query);
          // extract the page if present
          const page = this.extractPageFromParameters(params);
          // extract the aggregations if there are some
          // we consider all parameters as potential aggregations, except `query` and `page`
          this.aggregationCriteria = this.extractCriteriaFromParameters(params);
          // launch the search and handle a potential error, by returning no result
          // but allow to trigger a new search
          // return this.searchService.search(this.query,  this.aggregationCriteria, page)
          //   .pipe(
          //     catchError(() => EMPTY),
          //   );
          return map(
            searchQuery => {
              return this.searchService.search(this.query,  this.aggregationCriteria, page)
                .pipe(
                  catchError(() => EMPTY),
                );
            },
              aggQuery => {return this.searchService.aggregate(this.query,  this.aggregationCriteria) // ERROR: "TS7006: Parameter 'aggQuery' implicitely has 'any' type "
              .pipe(
                catchError(() => EMPTY)
              );
            }
            );
        }))
      .subscribe(results => {
        this.loading = false;
        if (results.aggregations.length) {//ERROR: "TS2339: Property aggregation does not exist on type '{}'"
          // sets the aggregations if there are some
          this.aggregations = results.aggregations;
        } else {
          this.results = results;
        }
      });
    this.suggesterTypeahead = this.searchService.getSuggesterTypeahead();
  }

  • 3
    If it doesn't compile, then you have an error message explaining where and why. Reading this error message is what you (and we) should do first. You can do that, but we can't because you didn't post it. Whyyyyy? – JB Nizet Aug 03 '19 at 08:37
  • Hi JB, thanks a lot for taking some time to help me! – Cyril Pommier Aug 03 '19 at 17:02
  • Hi JB, thanks a lot for taking some time to help me! I hadn't posted the error, because my main concern was wether my approach was good or not. But you are right, I should have precised the compile error. The error is on the aggQuery (second member of the returned map) which says : "TS7006 : Parameter 'aggQuery' implicitely has 'any' type". Furthermore, in the subscribe, I have "TS2339: property 'aggregation' does not exist on type {}". For that one, I assume I am not handling correctly the switchmap return. I am updating the code above for clarity. – Cyril Pommier Aug 03 '19 at 17:13

1 Answers1

1

what you really want is merge here, if you want all requests to execute in parallel and have them emit as they complete:

      return merge(
        this.searchService.search(this.query,  this.aggregationCriteria, page)
            .pipe(
              catchError(() => EMPTY),
            );
        },
        this.searchService.aggregate(this.query,  this.aggregationCriteria)
          .pipe(
            catchError(() => EMPTY)
          );
      );

the drawback, is in your subscribe handler, you won't really know which is which unless you do some mapping to help you figure it out.

map isn't an observable, it's an operator, merge will take two observables and "merge" their results.

btw, forkJoin doesn't defeat the purpose of parallel calls, it's just a different use case, where you want to make your calls in parallel (ie not wait for one before you start the other), but want to wait until you have all results to take action, or if you just need to know which result belongs to which action.

bryan60
  • 28,215
  • 4
  • 48
  • 65
  • Great, that did the trick, all tests are back to green :) Thanks bryan60 and @jb-nizet – Cyril Pommier Aug 05 '19 at 07:56
  • will merge kill previous call, if other value was selected from dropdown, while the request is in flight? switchMap cancels previous observable, and triggers new one. – Janatbek Orozaly Mar 15 '21 at 20:42
  • the observables in this question don't run on a value change, they run on a query param change. merge doesn't cancel observables, but the outter switchMap will still cancel the merge. – bryan60 Mar 15 '21 at 22:22