如何覆盖 ngrx store 中的某些操作?
How to override some of the actions in ngrx store?
我们有一个angular项目在工作(待开发,尚未开始)。它非常复杂并且具有复杂的数据流。此外,我们的应用程序中有两种用户。 经理 和用户。这些用户将看到类似的视图,但每个用户都有一些不同的自定义视图。 Manager 将可以访问您想象的更多功能。为了以可扩展的方式管理这个相当庞大和复杂的应用程序,我们遵循 NX 模式。 IE。单个回购中的多个应用程序。最后,在单个回购中我有以下应用程序。
大部分开发将在 common
应用程序中完成,不同的视图和自定义将分别在 mngr-app
和 user-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)
来实现这一点。然而,这只是我们想要避免的肮脏黑客攻击。因为,我们正在使用 ngrx
和 rxjs
,我们觉得这可以用 rxjs
的 Observable
和 ngrx
的 Action
来完成。
到目前为止我们尝试的是将 store
和 actions
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
中的方法。
希望这会对某人有所帮助。
我们有一个angular项目在工作(待开发,尚未开始)。它非常复杂并且具有复杂的数据流。此外,我们的应用程序中有两种用户。 经理 和用户。这些用户将看到类似的视图,但每个用户都有一些不同的自定义视图。 Manager 将可以访问您想象的更多功能。为了以可扩展的方式管理这个相当庞大和复杂的应用程序,我们遵循 NX 模式。 IE。单个回购中的多个应用程序。最后,在单个回购中我有以下应用程序。
大部分开发将在 common
应用程序中完成,不同的视图和自定义将分别在 mngr-app
和 user-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)
来实现这一点。然而,这只是我们想要避免的肮脏黑客攻击。因为,我们正在使用 ngrx
和 rxjs
,我们觉得这可以用 rxjs
的 Observable
和 ngrx
的 Action
来完成。
到目前为止我们尝试的是将 store
和 actions
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
中的方法。
希望这会对某人有所帮助。