简体   繁体   中英

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 - ie 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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM