Search code examples
angularrxjsngrx

How to use an ngrx selector that has a parameter that depends on another selector


I'm trying to use an ngrx selector that takes a property of jobId to return some data as an observable.

I'm first loading all of the jobs and tasks from a database but they don't come back in time for the selectors and I get an error because this.task is undefined, which I believe is keeping the observable from running again.

Is there a way to wait for the data to return before the selectors try to fetch the data from the store?

this.store.dispatch(fromTasks.loadAllTasks());
this.store.dispatch(fromJobs.loadAllJobs());

taskSubscription$ = this.store.pipe(
	select(fromTask.getSelectedTask),
	tap(task => this.task = task)).subscribe()

jobSubscription$ = this.store.pipe(
	select(fromJob.getJobById, {jobId: this.task.jobId}),
	tap(job => this.job = job)).subscribe();


Solution

  • Here is an approach you can consider :

    1) Selector getSelectedTask return null if no selected task

    export const getSelectedTask = createSelector(
      selectEntities,
      getSelectedTaskId,
      (entities: Dictionary<Task>, selectedId: number) => {
        return selectedId ? entities[selectedId] : null;
      }
    )
    

    2) Use async pipe and *ngIf inside template

    Component.ts

    @Component({
      selector: 'my-component',
      ...
    })
    export class MyComponent {
      selectedTask$ = this.store.pipe(select(getSelectedTask));
    ...
    }
    

    Template.html

    <ng-container *ngIf="selectedTask$ | async as selectedTask else noSelection">
    
      <my-task [task]="selectedTask"></my-task>
      // or 
      <span>{{ selectedTask.description }}</span>
    
    </ng-container>
    
    <ng-template #noSelection>
      Please select a task !
    </ng-template>
    

    With this approach, you will use the powerful of observables with angular template syntax. If user unselect current task, getSelectedTask selector will return null and template will be automatically updated.

    Nevertheless, you can continue to use subscription, but in this case, prefer to set the value inside subscribe method and be sure to unsubscribe.

    @Component({
      selector: 'my-component',
      ...
    })
    export class MyComponent implements OnInit, OnDestroy {
      subscription: Subscription;
      selectedTask$ = this.store.pipe(select(getSelectedTask));
      selectedTask: Task;
    
      constructor(
        private store: Store<AppState>
      ) { }
    
      ngOnInit() {
        this.subscription = this.selectedTask$.subscribe(task 
          => this.selectedTask = task);
      }
    
      ngOnDestroy() {
        this.subscription.unsubscribe();
      }
    }