如何覆盖 ngrx store 中的某些操作?

How to override some of the actions in ngrx store?

我们有一个angular项目在工作(待开发,尚未开始)。它非常复杂并且具有复杂的数据流。此外,我们的应用程序中有两种用户。 经理用户。这些用户将看到类似的视图,但每个用户都有一些不同的自定义视图。 Manager 将可以访问您想象的更多功能。为了以可扩展的方式管理这个相当庞大和复杂的应用程序,我们遵循 NX 模式。 IE。单个回购中的多个应用程序。最后,在单个回购中我有以下应用程序。

大部分开发将在 common 应用程序中完成,不同的视图和自定义将分别在 mngr-appuser-app 中完成。

我们还考虑在我们的状态管理应用程序中使用 ngrx。我已经看过多个示例和教程来说明如何做到这一点。到目前为止一切正常。这部分只是背景信息。

我们的问题从这之后开始。我们的业务团队还希望我们使用包含 Web 应用程序的 Web 视图开发 iOS 和 Android 应用程序(我忘了提到它是一个响应式 Web 应用程序)。因此,我们所做的一切都将在 webview 中发送给移动用户。但是,业务团队也希望我们为移动应用程序开发一些自定义原生视图。

我们来看下面的例子:(来自ngrx example

当用户点击 Add Book to Collection 按钮时,[Collection] Add Book 动作类型被发送到商店,效果将按如下方式处理:

@Effect()
addBookToCollection$: Observable<Action> = this.actions$
  .ofType(collection.ADD_BOOK)
  .map((action: collection.AddBookAction) => action.payload)
  .switchMap(book =>
    this.db.insert('books', [ book ])
      .map(() => new collection.AddBookSuccessAction(book))
      .catch(() => of(new collection.AddBookFailAction(book)))
  );

这是网络应用程序的正常流程。

我们的业务团队希望我们为移动应用程序构建某种自定义逻辑,以便当用户在移动应用程序(iOS 或 Android)中导航到此页面时,而不是向 collection 添加一本书会打开一个本地页面,用户将在该本地页面上执行操作。我的意思是他们希望 Web 应用程序在出现在移动应用程序中时表现不同。我可以在 Web 应用程序中使用一堆 if(window.nativeFlag === true) 来实现这一点。然而,这只是我们想要避免的肮脏黑客攻击。因为,我们正在使用 ngrxrxjs,我们觉得这可以用 rxjsObservablengrxAction 来完成。

到目前为止我们尝试的是将 storeactions object 公开给 DOM 以便我们可以在移动应用程序中访问它。

  constructor(private store: Store<fromRoot.State>, private actions$: Actions) {
    window['store'] = this.store;
    window['actions'] = this.actions$;
  }

通过这种方式,我们可以订阅[Collection] Add Book事件如下

actions().ofType('[Collection] Add Book').subscribe(data => {
    console.log(data)
})

并在图书添加到 collection 时收到通知。但是,Web 应用程序仍会正常运行,我们无法覆盖此行为。

我的问题是如何从移动应用程序订阅某些 ngrx 操作并取消 Web 应用程序行为?

编辑

我自己想出了一个解决方案,但是任何其他解决方案都会受到赞赏,如果你能想出更好的解决方案,我会给予赏金。

到目前为止我能做什么

我在 Web 应用程序中写了以下 bridge

window['bridge'] = {
  dispatch: (event: string, payload = '') => {
    // I had to use zone.run, 
    // otherwise angular won't update the UI on 
    // an event triggered from mobile.
    this.zone.run(() => {
      this.store.dispatch({type: event, payload: payload});
    });
  },
  ofEvent: (event: string) => {
    // I register this event on some global variable 
    // so that I know which events mobile application listens to.
    window['nativeActions'].push(event);
    return this.actions.ofType(event);
  }
};

从 Android 应用程序,我可以按如下方式收听 [Collection] Add Book

webView.loadUrl("
    javascript:bridge.ofEvent('[Collection] Add Book')
       .subscribe(function(book) {
           android.addBook(JSON.stringify(book.payload));
       });
");

这会触发我的 addBook 方法,我会把这本书保存在一些 class 上以备后用。

@JavascriptInterface
public void addBook(String book) {
    Log.i("addBook", book);
    this.book = book;
}

我也在 android 应用程序中添加了一个按钮来删除这本书。

webView.evaluateJavascript("
      bridge.dispatch('[Collection] Remove Book', "+this.book+")
", null);

通过这段代码,我能够侦听由 Web 应用程序触发的事件并将结果保存在某处。我也能够从 android 应用程序触发一些事件来删除 Web 应用程序中的图书。

另外,之前我提到我在全局变量上注册了这个事件。

我将我的@Effect更改为关注

@Effect()
addBookToCollection$: Observable<Action> = this.actions$.pipe(
  ofType<AddBook>(CollectionActionTypes.AddBook),
  map(action => action.payload),
  switchMap(book => {
    if (window['nativeActions'].indexOf(CollectionActionTypes.AddBook) > -1) {
      return of();
    } else {
      return this.db
        .insert('books', [book])
        .pipe(
        map(() => new AddBookSuccess(book)),
        catchError(() => of(new AddBookFail(book)))
        );
    }
  })
);

使用 if (window['nativeActions']..) 我能够检查移动应用程序是否注册了此特定事件。但是,我不会对我拥有的每个 @Effect 执行此控制。在这一点上,我正在考虑编写一些自定义 RxJs 运算符来从我的 effects 中抽象出此检查。我觉得我离答案很近了,但我会很感激任何意见。

自定义 RxJs 运算符

我编写了以下自定义运算符并解决了我的问题。

export function nativeSwitch(callback) {
    return function nativeSwitchOperation(source) {
        return Observable.create((subscriber: Observer<any>) => {
            let subscription = source.subscribe(value => {
                try {
                    if (window['nativeActions'].indexOf(value.type) > -1) {
                        subscriber.complete();
                    } else {
                        subscriber.next(callback(value));
                    }
                } catch (err) {
                    subscriber.error(err);
                }
            },
            err => subscriber.error(err),
            () => subscriber.complete());

            return subscription;
        });
    };
}

将我的效果更改为以下内容:

@Effect()
addBookToCollection$: Observable<Action> = this.actions$.pipe(
  ofType<AddBook>(CollectionActionTypes.AddBook),
  nativeSwitch(action => action.payload),
  switchMap((book: Book) =>
    this.db
      .insert('books', [book])
      .pipe(
      map(() => new AddBookSuccess(book)),
      catchError(() => of(new AddBookFail(book)))
      )
  )
);

这正是我想要的。因为,如果给定事件由移动应用程序注册,我会调用 subscriber.complete(),因此永远不会调用 switchMap 中的方法。

希望这会对某人有所帮助。