javascript - Hur vet man när effekten är klar inom effekt i ngrx component store (rxjs-flöde)?


Jag implementerar för närvarande en funktion som "öppnar en dialog" (effekt), efter att dialogen öppnas trycker användaren på spara och sedan vill jag stänga dialogen.

Jag gjorde falsk dialog och flöde för exemplet.

Jag delade också upp flödet i två effekter: openDialogEffect och saveEffect för att undvika "operator-hell" och göra koden mer läsbar samt återanvända save-effekten från andra anrop.

detta fungerar bra. tills jag vill stänga dialogrutan EFTER att sparandet är framgångsrikt. Problemet är hur man kan "knyta in" i pipelinen för saveEffect?

Jag återger problemet här: stackblitz

readonly openDialogEffect = this.effect(($: Observable<any>) =>
    $.pipe(
      exhaustMap((dialogRef) =>
        dialogRef.open().pipe(
          tap({
            next: () => {
              this.saveEffect(); // <----- after that need to close the dialog... but how??
              // then: dialogRef.close();
            },
          })
        )
      )
    )
  );

  readonly saveEffect = this.effect(($) =>
    $.pipe(
      exhaustMap(() =>
        this.api.save().pipe(
          switchMap((res) =>
            this.api.updateFoo(res).pipe(
              tap({
                next: () => {
                  console.log('after save');
                  this.toast.success();
                },
                error: () => {
                  console.log('in error');
                  this.toast.error();
                },
              })
            )
          )
        )
      )
    )
  );

Svar #1

ngrx kan hantera rena sidoeffekter (effekter som inte manipulerar applikationens tillstånd). Även om du behöver skicka en åtgärd från inom en effekt för att uppnå det, annars kan du inte vara säker på om det lyckades eller inte.

@Component({
  // component metadata
})
export class MyComponent implements OnInit {
  constructor(
    private readonly actions$: Actions,
  ) {}

  ngOnInit() {
    this.actions$.pipe(
      ofType(someAction),
      takeUntil(this.destroy$), // remember to unsubscribe
    ).subscribe(({payload}) => {
      // show toast
      // close dialog
    });
  }
}

Svar #2

Du kan skapa en callback och skicka den till saveEffect genom openDialogEffect:

återuppringningen kan vara:

  1. funktion
  2. anpassad rxjs-operator

1. använda funktionsåteruppringning :

funktion callback

@Injectable()
export class AppService extends ComponentStore<State> {
  readonly openDialogEffect = this.effect<{
    // new code
    whenSaved: () => void;
    dialogRef;
  }>((arg$) =>
    arg$.pipe(
      exhaustMap(({ whenSaved, dialogRef }) =>
        dialogRef.open().pipe(
          tap({
            next: () => {
              this.saveEffect({
                whenSaved,
              });
              // then: dialogRef.close();
            },
          })
        )
      )
    )
  );

  readonly saveEffect = this.effect<{
    // new codes
    whenSaved: () => void;
  }>((arg$) =>
    arg$.pipe(
      exhaustMap((arg) =>
        this.api.save().pipe(
          switchMap((res) =>
            this.api.updateFoo(res).pipe(
              tap({
                next: () => {
                  console.log('after save');
                  arg.whenSaved();
                  this.toast.success();
                },
                error: () => {
                  console.log('in error');
                  this.toast.error();
                },
              })
            )
          )
        )
      )
    )
  );

  constructor(private api: ApiService, private toast: ToastrService) {
    super();

    this.openDialogEffect(
      of({
        dialogRef: dialog,
        whenSaved: () => {
          console.log('saved!!');
        },
      })
    );
  }
}

2. använda anpassad rxjs-operator :

anpassad rxjs-operator

@Injectable()
export class AppService extends ComponentStore<State> {
  readonly openDialogEffect = this.effect<{
    // new code
    whenSaved$: () => MonoTypeOperatorFunction<unknown>;
    dialogRef;
  }>((arg$) =>
    arg$.pipe(
      exhaustMap(({ whenSaved$, dialogRef }) =>
        dialogRef.open().pipe(
          tap({
            next: () => {
              this.saveEffect({
                whenSaved$,
              });
              // then: dialogRef.close();
            },
          })
        )
      )
    )
  );

  readonly saveEffect = this.effect<{
    // new codes
    whenSaved$: () => MonoTypeOperatorFunction<unknown>;
  }>((arg$) =>
    arg$.pipe(
      exhaustMap((arg) =>
        this.api.save().pipe(
          switchMap((res) =>
            this.api.updateFoo(res).pipe(
              tap({
                next: () => {
                  console.log('after save');
                  this.toast.success();
                },
                error: () => {
                  console.log('in error');
                  this.toast.error();
                },
              }),
              arg.whenSaved$()
            )
          )
        )
      )
    )
  );

  constructor(private api: ApiService, private toast: ToastrService) {
    super();

    this.openDialogEffect(
      of({
        dialogRef: dialog,
        whenSaved$: () => {
          return function <T>(source: Observable<T>): Observable<T> {
            return source.pipe(
              tap((isSaved) => console.log('save!', isSaved)),
              tap(() => console.log('do some thing...'))
            );
          };
        },
      })
    );
  }
}