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

Posted

技术标签:

【中文标题】如何覆盖 ngrx 商店中的某些操作?【英文标题】:How to override some of the actions in ngrx store? 【发布时间】:2018-10-24 02:02:51 【问题描述】:

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

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

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

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

我们来看下面的例子:(这是来自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)中导航到此页面时,它会打开而不是添加一本书到收藏夹本机页面和用户将在该本机页面上执行操作。我的意思是,他们希望 Web 应用程序在移动应用程序中出现时表现不同。我可以在 Web 应用程序中使用一堆 if(window.nativeFlag === true) 来实现这一点。然而,这只是我们想要避免的一个肮脏的黑客行为。因为,我们使用的是ngrxrxjs,所以我们觉得这可以通过rxjsObservables 和ngrxActions 来完成。

到目前为止,我们尝试将storeactions 对象暴露给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)
)

当一本书被添加到收藏中时会收到通知。但是,Web 应用程序仍会执行其正常操作,我们无法覆盖此行为。

我的问题是如何从移动应用订阅一些ngrx 操作并取消网络应用行为?

编辑

我自己提出了一个解决方案,但是任何其他解决方案都值得赞赏,如果您能提出更好的解决方案,我将给予奖励。

【问题讨论】:

尽管我不确定如何回答这个问题 - 我认为这是一个很好的问题,NX 看起来是一个很棒的框架。希望了解 NX 的人对这个问题采取更多行动...... @AviadP。到目前为止,我会用我的试验来更新这个问题。如果您仍然感兴趣,我也考虑今天悬赏。 当您订阅任何事件时,它会返回一个对象。您可以将其存储在一些全局字典中,然后在同一字典中调用 unsubscribe。您可以使用不同的密钥来存储 id,然后使用由 web 发起的密钥或需要被本机页面覆盖的密钥。然后原生页面可以在web 上调用unsubscribe。 ***.com/questions/47496383/…。我不是 Angular 开发者,但我相信这种方法应该可行? 它可以用 Rx 风格的 Observables 来完成,但在向您提出任何具体答案之前,请澄清一下,您是否能够从您的 javascript 调用本机代码(目前是 android 中的 java)网络应用程序?如果是,那么您是在使用 @JavascriptInterface 还是其他东西? @abhaytripathi 是的,我有。另外,对 javascriptInterface 是的。我会更新我的问题。 【参考方案1】:

到目前为止我能做到的事情

我在网络应用程序中写了以下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 方法,我会将这本书保存在某个类中以供以后使用。

@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 中的方法永远不会被调用。

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

【讨论】:

以上是关于如何覆盖 ngrx 商店中的某些操作?的主要内容,如果未能解决你的问题,请参考以下文章

如何清除#ngrx 中的状态?

ngrx如何订阅组件中的操作或订阅状态

Ngrx 状态可变性

ngrx 商店选择仅订阅特定操作

使用 @ngrx/entity 处理商店中的集合,getSelectors() 不起作用

将变量从商店绑定到角度ngrx中的组件