关于此Angular + ngRx(效果)的建议 - websocket事件怎么样?

时间:2018-03-02 20:00:36

标签: angular redux ngrx ngrx-store ngrx-effects

所以我正在试验ngrx&通过构建以下沙箱来实现ngrx /效果:

https://stackblitz.com/edit/ngrx-vanilla

快速介绍:

  • 它在app / store
  • 中有一个根存储
  • 它在app / features中延迟加载了两个模块
  • 它在app / commons中有单例服务

三页:

  • 操作项:路由到此页面会触发随机生成三个愚蠢的公司操作项
  • 用户:基本主人>具有路由器支持的detail redux实现
  • 会议:提出问题的地方,点击"开始会议"见证相关的意见交流。

问题&上下文:

  • 我了解redux中的所有数据更新都是通过操作发生的
  • "效果"库是为了处理异步事件,以便根据第三方事件和异步调用发送新动作。
  • app / common / meeting / service模仿例如websocket或firebase实时数据库推送更新的行为。

收到更新后(在app / store / effects / meeting.effects.ts中说明),会调度一个新动作。

最后,问题是:让公共服务了解商店是否干净?将一个监听器注册到websocket / firebase实时数据库以便在推送数据时调度操作的最佳位置在哪里?

在这里,我做了一个效果(meeting.effects)对meetingActions.START_MEETING操作类型作出反应,每当推送数据时,都会向商店发送更新订单,但由于一系列原因,我觉得这是错误的起来:

  • 难以单独进行单元测试(需要比自身更多的上下文)
  • 如果"停止会议"动作,此方法需要存储订阅(或?)以停止订阅。在我的方法中,无法控制在荒野中创造的可观察物。

这些案件通常如何处理?

3 个答案:

答案 0 :(得分:5)

假设websocket发出不同类型的事件,请将每个事件映射到websocket服务中的不同操作,例如

@Injectable()
class WebsocketService{
    private socket$:Observable<Action>
    getActions():Observable<Action>{
        if(!this.socket$) {
           this.socket$ = Observable.webSocket(url).pipe(
               map(functionToMapActions)
            ,shareReplay(1)
           );
        }
        return this.socket$;
    }
}

其中functionToMapActions将webSocket事件映射到操作,我建议在末尾添加shareReplay运算符,以便我们只读取一次webSocket。

Observable.webSocket连接到webSocket,并在事件到达时发出事件

当您怀疑webService.getActions()

时,将建立webSocket连接

您可以在@Effects初始化see here

中订阅websocket操作
@Effect()
init$ = this.websocketService.getActions();

这将在您的应用程序启动后立即发出所有操作(如果在根模块中生效)或者如果模块在延迟加载模块中则加载模块;

或者,如果您对有限的一系列行动感兴趣,您可以这样做

@Effect()
init$ = this.websocketService.getActions().pipe(filter(filterNeededActionsHere));

你也可以在这样的特定事件之后开始听动作

@Effect()
init$ = this.actions$.pipe(
          ofType('Event which marks start')
          ,swichMapTo(this.websocketService.getActions())
     );

就像之前的例子一样,你也可以像以前一样过滤掉这里的行动

希望这能回答你的问题

答案 1 :(得分:2)

NgRx v9稍微更改了语法。将以下代码用于根级效果类:

init$ = createEffect(() => 
  this.actions$.pipe(
    ofType(ROOT_EFFECTS_INIT),
    // websocket events handling logic
    switchMap(() => webSocketEvents$)
  )
);

这只是来自Docs的示例。

如果您使用功能级效果类,则ROOT_EFFECTS_INIT不起作用,您需要使用OnRunEffects生命周期挂钩:

class UserEffects implements OnRunEffects  {
  ngrxOnRunEffects(resolvedEffects$: Observable<EffectNotification>) {
    // websocket events handling logic
    return webSocketEvents$.pipe(
      exhaustMap(() => resolvedEffects$)
    );
  }
}

更详细的示例在Docs中。

在两个示例中,

webSocketEvents$是一个Observable,可以使用rxjs函数之一来构造它:

webSocket(socketUrl) // if you want to manage websocket connection via rxjs
fromEvent(socketIoClient) // useful if websocket connection handled by socket.io-client

答案 2 :(得分:1)

使用适配器为实体Vehicle发出不同类型事件的WebSocket定制示例

1。创建一个Vehicle-Socket-Adapter.ts

import { fromEvent, merge, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { SocketIoService } from 'src/app/shared/services/socket-io.service';

export class VehicleSocketAdapter {
  public onVehicleEngineStateChange: Observable<boolean>;
  constructor(vehicleId: string, private socketIoService: SocketIoService) {

   //using merge from rxjs/operators to concat multiple ws events to one Observable 

    this.onVehicleEngineStateChange = merge(
      fromEvent(this.socketIoService.socket, `event.vehicle.${vehicleId}.start`).pipe(
        map(vehicleStatus => true)
      ),
      fromEvent(this.socketIoService.socket,`event.vehicle.${vehicleId}.stop`).pipe(
        map(vehicleStatus => false)
      ),
    )
  }
}

2。以后将适配器导入到您想使用的任何地方,例如app.component.ts

private subscriptions = new Subscription();
private listenForVehiclesState(vehicles) {
    vehicles.forEach((vehicle) => {
      const vehicleAdapter = new VehicleSocketAdapter(vehicle.id, this.webSocket);
      this.subscriptions.add( 
        vehicleAdapter.onVehicleEngineStateChange.subscribe(vehicleStatus => {
        
       // dispatch action when adapter commands you

        this.store.dispatch(
          VehiclesActions.BeginUpdateVehiclesStatusAction({
            payload: {
              vehicleId: vehicle.id,
              status: vehicleStatus
            }
          })
        );

      }));
    });
  }
  1. 当视图或组件被破坏时,别忘了取消订阅所有子项 this.subscriptions.unsubscribe();

奖金:套接字作为服务摘要:)

import { Injectable } from '@angular/core';
import * as io from 'socket.io-client';

@Injectable({
  providedIn: 'root'
})
export class SocketIoService {

  public socket: SocketIOClient.Socket = io('/', { path: '/api/livedata', transportOptions: { polling: extraHeaders: {'AuthToken': 'ifAny'} }});

constructor() { }
}

快乐编码:)