Odonno / ngrx-signalr-core

A library to handle realtime SignalR (.NET Core) events using @angular, rxjs and the @ngrx library
https://www.npmjs.com/package/ngrx-signalr-core
MIT License
28 stars 13 forks source link
angular ngrx rxjs signalr signalr-core typescript

ngrx-signalr-core

A library to handle realtime SignalR (.NET Core) events using angular, rxjs and the @ngrx library.

This library is made for the SignalR client using .NET Core. If you need to target .NET Framework, please check this repository : https://github.com/Odonno/ngrx-signalr

Get started

Install dependencies

npm install rxjs @ngrx/store @ngrx/effects @microsoft/signalr --save
npm install ngrx-signalr-core --save

Once everything is installed, you can use the reducer and the effects inside the AppModule.

@NgModule({
    ...,
    imports: [
        StoreModule.forRoot({ signalr: signalrReducer }),
        EffectsModule.forRoot([SignalREffects, AppEffects])
    ],
    ...
})
export class AppModule { }
Start with a single Hub...
First, you will start the application by dispatching the creation of one Hub. ```ts // TODO : your hub definition const hub = { hubName: "hub name", url: "https://localhost/path", }; this.store.dispatch(createSignalRHub(hub)); ``` Creating a SignalR Hub is not enough. You need to start it manually. ```ts initRealtime$ = createEffect(() => this.actions$.pipe( ofType(signalrHubUnstarted), map((hub) => startSignalRHub(hub)) ) ); ``` Then you will create an effect to start listening to events once the hub is connected. ```ts listenToEvents$ = createEffect(() => this.actions$.pipe( ofType(signalrConnected), mergeMapHubToAction(({ hub }) => { // TODO : add event listeners const whenEvent1$ = hub .on("eventName1") .pipe(map((x) => createAction(x))); const whenEvent2$ = hub .on("eventName2") .pipe(map((x) => createAction(x))); return merge(whenEvent1$, whenEvent2$); }) ) ); ``` You can also send events at anytime. ```ts sendEvent$ = createEffect(() => this.actions$.pipe( ofType(SEND_EVENT), // TODO : create a custom action mergeMap(({ params }) => { const hub = findHub(timeHub); if (!hub) { return of(hubNotFound(timeHub)); } // TODO : send event to the hub return hub.send("eventName", params).pipe( map((_) => sendEventFulfilled()), catchError((error) => of(sendEventFailed(error))) ); }) ) ); ```
...or use multiple Hubs
Now, start with multiple hubs at a time. ```ts // simplified hub creation const dispatchHubCreation = (hub) => this.store.dispatch(createSignalRHub(hub)); const hub1 = {}; // define hubName and url const hub2 = {}; // define hubName and url const hub3 = {}; // define hubName and url dispatchHubCreation(hub1); dispatchHubCreation(hub2); dispatchHubCreation(hub3); ``` You will then initialize your hubs in the same way but you need to know which one is initialized. ```ts const hub1 = {}; // define hubName and url const hub2 = {}; // define hubName and url initHubOne$ = createEffect(() => this.actions$.pipe( ofType(signalrHubUnstarted), ofHub(hub1), mergeMapHubToAction(({ action, hub }) => { // TODO : init hub 1 }) ) ); initHubTwo$ = createEffect(() => this.actions$.pipe( ofType(signalrHubUnstarted), ofHub(hub2), mergeMapHubToAction(({ action, hub }) => { // TODO : init hub 2 }) ) ); ``` And then you can start your app when all hubs are connected the first time. ```ts appStarted$ = createEffect(() => this.store.pipe( select(selectAreAllHubsConnected), filter((areAllHubsConnected) => !!areAllHubsConnected), first(), map((_) => of(appStarted())) // TODO : create a custom action when hubs are connected ) ); ```
Handling reconnection
~~Since .NET Core, you need to handle the SignalR Hub reconnection by yourself.~~ The simple way to enable hub reconnection is to enable `automatic reconnect` when creating the hub. You can use one of the 3 options described here: ```ts // Using the Default reconnection policy. // By default, the client will wait 0, 2, 10 and 30 seconds respectively before trying up to 4 reconnect attempts. const action = createSignalRHub(hub, url, options, true); // Using an array containing the delays in milliseconds before trying each reconnect attempt. // The length of the array represents how many failed reconnect attempts it takes before the client will stop attempting to reconnect. const action = createSignalRHub(hub, url, options, [10000, 20000, 30000]); // after 10s, after 20s, after 30s // Using a custom reconnect policy. // The retry policy that controls the timing and number of reconnect attempts. const action = createSignalRHub(hub, url, options, { nextRetryDelayInMilliseconds: (context) => { // ... return 10000; }, }); this.store.dispatch(action); ``` It is currently deprecated but you can perform your own reconnection strategy using the power of @ngrx. Here is an example on how to apply periodic reconnection: ```ts // try to reconnect all hubs every 10s (when the navigator is online) whenDisconnected$ = createReconnectEffect(this.actions$); ``` In this example, we did not use a custom reconnection policy. So the default behavior will automatically be to apply a periodic reconnection attempt every 10 seconds when the hub is `disconnected` and when there is a network connection. Of course, you can write your own `reconnectionPolicy` inside the options of the function, so you have the benefit to write your own reconnection pattern (periodic retry, exponential retry, etc..). You can also filter by `hubName` so that it will affect only one hub.

API features

SignalR Hub
The SignalR Hub is an abstraction of the hub connection. It contains function you can use to: - start the connection - listen to events emitted - send a new event ```ts interface ISignalRHub { hubName: string; url: string; options: IHttpConnectionOptions | undefined; start$: Observable; stop$: Observable; state$: Observable; error$: Observable; constructor( hubName: string, url: string, options: IHttpConnectionOptions | undefined ); start(): Observable; stop(): Observable; on(eventName: string): Observable; stream(methodName: string, ...args: any[]): Observable; send(methodName: string, ...args: any[]): Observable; sendStream(methodName: string, subject: Subject): Observable; hasSubscriptions(): boolean; } ``` You can find an existing hub by its name and url. ```ts function findHub(hubName: string, url: string): ISignalRHub | undefined; function findHub({ hubName, url, }: { hubName: string; url: string; }): ISignalRHub | undefined; ``` And create a new hub. ```ts function createHub( hubName: string, url: string, options: IHttpConnectionOptions | undefined ): ISignalRHub | undefined; ```
State
The state contains all existing hubs that was created with their according status (unstarted, connected, disconnected). ```ts const unstarted = "unstarted"; const connected = "connected"; const disconnected = "disconnected"; type SignalRHubState = | typeof unstarted | typeof connected | typeof disconnected; type SignalRHubStatus = { hubName: string; url: string; state: SignalRHubState; }; ``` ```ts class BaseSignalRStoreState { hubStatuses: SignalRHubStatus[]; } ```
Actions
#### Actions to dispatch `createSignalRHub` will initialize a new hub connection but it won't start the connection so you can create event listeners. ```ts const createSignalRHub = createAction( "@ngrx/signalr/createHub", props<{ hubName: string; url: string; options?: IHttpConnectionOptions | undefined; }>() ); ``` `startSignalRHub` will start the hub connection so you can send and receive events. ```ts const startSignalRHub = createAction( "@ngrx/signalr/startHub", props<{ hubName: string; url: string }>() ); ``` `stopSignalRHub` will stop the current hub connection. ```ts const stopSignalRHub = createAction( "@ngrx/signalr/stopHub", props<{ hubName: string; url: string }>() ); ``` `reconnectSignalRHub` will give you a way to reconnect to the hub. ```ts const reconnectSignalRHub = createAction( "@ngrx/signalr/reconnectHub", props<{ hubName: string; url: string }>() ); ``` `hubNotFound` can be used when you do retrieve your SignalR hub based on its name and url. ```ts const hubNotFound = createAction( "@ngrx/signalr/hubNotFound", props<{ hubName: string; url: string }>() ); ```
Effects
```ts // create hub automatically createHub$; ``` ```ts // listen to start result (success/fail) // listen to change connection state (connecting, connected, disconnected, reconnecting) // listen to hub error beforeStartHub$; ``` ```ts // start hub automatically startHub$; ``` ```ts // stop hub stopHub$; ```
Selectors
```ts // used to select all hub statuses in state const hubStatuses$ = store.pipe(select(selectHubsStatuses)); // used to select a single hub status based on its name and url const hubStatus$ = store.pipe(select(selectHubStatus, { hubName, url })); // used to know if all hubs are connected const areAllHubsConnected$ = store.pipe(select(selectAreAllHubsConnected)); // used to know when a hub is in a particular state const hasHubState$ = store.pipe( select(selectHasHubState, { hubName, url, state }) ); ```