How to filter async pipes into Angular child components

TL,DR; I have successfully passed the output of Async Pipe into a child component. But when the condition fails (filter => false), I want to pass in the previously emitted value from an Observable.

Detail;

Using Selectors, I subscribe to two slices of @ngrx/store:

export interface AppState {
  data: string[];
  pending: boolean;  // Are we waiting for a HTTP response?
  dirty: boolean;  // Has the data been edited locally?
}

...although there are three slices above, this question involves only dirty and data.

The reducer has three cases:

  1. AppActions.GET_NEW_DATA:
    • Set pending to true to update the UI.
    • Results in a HTTP request being made (via ngrx/effects).
  2. AppActions.GET_NEW_DATA_SUCCESS:
    • Set pending to false to update the UI.
    • Set dirty to false because no edits have been made.
  3. AppActions.EDIT_DATA:
    • Update data provided in payload.
    • Set dirty to true because an edit has been made.

The code:

export function appReducer(state: AppState = initialState,
    action: AppActions.All
): AppState {
    switch (action.type) {
        case AppActions.GET_NEW_DATA:
            // Action results in HTTP request.
            return {
                ...state,
                pending: true
            };
        case AppActions.GET_NEW_DATA_SUCCESS:
            // HTTP response, update data!
            return {
                data: action.payload,
                pending: false,
                pristine: true,
            };
        case AppActions.EDIT_DATA:
            return {
                ...state,
                data: action.payload,
                pristine: false
            };
        default: {
          return state;
        }
     }
 }

As mentioned earlier, using Selectors, I subscribe to two slices of @ngrx/store from within the smart component

@Component({
  selector: 'app-smart',
  templateUrl: './smart.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class SmartComponent {

  public data$: Observable<string[]>;
  public dirty$: Observable<boolean>;

  constructor(private readonly store: Store<AppState>) {
      this.data$ = this.store.select(fromReducer.getData);
      this.dirty$ = this.store.select(fromReducer.getDirty);
  }
}

The smart component uses Async Pipe to detect when a new value is emitted from the Observable and mark the dumb component to be checked for changes.

<!-- From within <app-smart> -->
<app-dumb [data]="data$|async"></app-dumb>

This works as expected, however, I only want to be updated if dirty is false - i.e. when a new HTTP request is made and response received.

I don't want to redraw <app-dumb> when the data$ Observable emits a change from an editing. In summary, I require the following behavior:

When creating:

  • data$ emits new value.
    • dirty$ is false.
    • Mark <app-dumb> to be checked for changes.

When editing:

  • data$ emits new value.
    • dirty$ is true.
    • Do NOT mark <app-dumb> to be checked for changes.

This looked like a prime case for combing Observables:

this.data$ = this.store
  .select(fromReducer.getData)
  .withLatestFrom(this.dirty$)
  .filter(([_, dirty]) => !dirty)
  .map(([tree, _]) => tree);

...breaking this down

  1. this.store.select(fromReducer.getData) grab an Observable from ngrx/store using selectors.
  2. Combine newly emitted data$ result with latest result from dirty$.
  3. Filter on whether dirty$ is false.
    • If NOT dirty, we have new data from the server and want to update <app-dumb>.
    • If dirty, then an edit caused data$ to emit a new value, and we should ignore it.

The problem is <app-smart> is contained within a Material Tab, and as such may be removed (ngOnDestroy) and recreated (ngOnInit) as the user switches tabs. See Life Cycle Hooks.

If and edit has been made, dirty$ is true. If the user then switches tabs and subsequently returns, data$ is filtered out because dirty$ is true:

filter(([_, dirty]) => !dirty)

I have successfully passed the output of Async Pipe into the child <app-dumb> component. But. When the condition fails, I want to pass in the previously emitted value from data$ - and this is where I could really do with some help.

Answers:

Answer

try to create the observable of data starting from the dirty observable something like this

this.data$ = this.store.select(fromReducer.getDirty)
.filter(dirty=>dirty)
.flatMap(()=>this.store.select(fromReducer. getData))

In that way you always going to receive new data if the filter of dirty pass

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.