0

I have an Angular 9 application that for the most part uses observables. In one component I need to:

  1. Get all companies (as they contain certain info I need)
  2. Then get all responses and map some of the extra info in the companies (i.e. the company name for the response).
  3. Return that list as an observabe which is subscribed to in the template using the async pipe.

To do this I originally had the following code (which appears to work):

getPopulatedResponses(): Observable<ResponseModel[]> {
    return this.companyFilesStore
      .select(companyFilesSelector)
      .pipe(
        switchMap(companies => {
          return this.getAccessibleResponses(companies);
        })
      )
  }

getAccessibleResponses(accessibleCompanies: CompanyFilesModel[]): Observable<ResponseModel[]> {
    return this.responsesStore
      .select(responsesSelector)
      .pipe(
        map((responses) => {
          return responses?.map((response) => {
            const company = accessibleCompanies?.find(c => c.companyGuid === response.companyId);
            response.companyName = company?.companyName;
            return response;
          }).sort((a, b) => {
            return a.completedDateTime < b.completedDateTime ? 1 : -1;
          })
        })
      )

Firstly I'm not sure if switchMap was the right operator because if the companyFilesSelector was updating and then the responsesSelector kicked in part way through would that not terminate the previous subscription?

And now I need to also add a subscription to a filter service that I have so I've made the following changes to the getAccessibleResponses method (which again appear to be working but I feel like it may be more through luck/good connection than anything else):

getAccessibleResponses(
    accessibleCompanies: CompanyFilesModel[],
  ): Observable<ResponseModel[]> {
    return this.searchAndFilterService.getFilter()
      .pipe(
        switchMap(filter => {
          return this.responsesStore
            .select(responsesSelector)
            .pipe(
              map((responses) => {
                return responses?.map((response) => {
                  const company = accessibleCompanies?.find(c => c.companyGuid === response.companyId);
                  response.companyName = company?.companyName;
                  return response;
                })
                  ?.filter(r => !r.isArchived)
                  ?.filter(r => !filter?.selectedCompany || r.companyId === filter.selectedCompany.companyId)
                  ?.filter(r => !filter.selectedAssessmentTemplate ||
                    r.assessmentTemplateId === filter.selectedAssessmentTemplate.assessmentTemplateId)
                  .sort((a, b) => {
                    return a.completedDateTime < b.completedDateTime ? 1 : -1;
                  })
              })
            )
        })
      )
  }

I'm fairly confident this is not the best or even right approach but I'm getting a bit lost and struggling to understand how to achieve the following:

  1. Get all companies first and foremost.
  2. Combine results from the companies observable with the responses.
  3. When the filter is changed (i.e. the filter service observable) filter the results of 1/2.

Any help or guidance would be greatly appreciated.

Ben Thomson
  • 1,083
  • 13
  • 29
  • There seems to be no asynchronous actions, right? – Andrei Gătej Apr 16 '20 at 15:39
  • So in theory you could get all companes and all responses async and then map the results from both observables to return the complete objects but currently it's not working async and I'm not 100% on the best way to do that. – Ben Thomson Apr 16 '20 at 22:38

1 Answers1

1

As far as I can tell, you have 3 main parts to work with(companies, responses and filter) and whenever one of these parts changes(e.g emits a new value), you'd like to recompute the data based on what's changed.

If that's the case and assuming that your app state is held in a single source of truth, here is how I'd do it:

const companies$ = this.store.select(companyFilesSelector);

const accessibleResponses$ = this.store.select(responsesSelector);

const activeFilter$ = this.store.select(activeFilterSelector);

const data$ = combineLatest(
  companies$,
  accessibleResponses$,
  activeFilter$,
).pipe(
  map(([companies, responses, filter]) => {
    // Logic here...
  })
)

combineLatest is used here because you want to compute the shown data whenever one of the 3 parts changes.

So, if your initial state might be:

{
 companies: [],
 responses: [],
 activeFitler: null,
}

Whenever you add, for example, some new companies, the function provided to map will be invoked in order to update what will be shown to the user depending on the new companies. The same happens when you add new responses or a new filter.


Edit

could I use a switchMap from that so no subscription looks at that store to potentially save memory?

I'm don't think it saves much memory, as all what switchMap does it to unsubscribe from the active observable and create a new one, based on the newly arrived value and the provided function.

Also, since you're subscribing to the store, the first data retrieval should happen synchronously since I suppose the store is a sort of BehaviorSubject(e.g NgRx)

Andrei Gătej
  • 11,116
  • 1
  • 14
  • 31
  • That is what I'm looking for. As companies VERY rarely changes could I use a switchMap from that so no subscription looks at that store to potentially save memory? – Ben Thomson Apr 18 '20 at 00:39
  • Actually what I need might be a bit different because the companies have to be loaded before I can process the responses! – Ben Thomson Apr 18 '20 at 03:32
  • @BenThomson I've updated my answer. 'companies have to be loaded before I can process the responses' hmm, couldn't everything fit into the `map`'s callback? Because I wouldn't say you're loading anything, but you're selecting parts of the store. So I think you could check in that cb whether the `companies` have arrived or not. – Andrei Gătej Apr 18 '20 at 15:19
  • Yeah after reading your comments and thinking it through this would be fine with maybe a filter check on companies != null somewhere to ensure they've been loaded. Thanks. – Ben Thomson Apr 18 '20 at 23:57