Introducing ngxs message plugin
TLDR; check out the project on github or install it using
npm i ngxs-message-plugin /
yarn add ngxs-message-plugin. You can also try it out in the iframe below.
In a recent project I was part of we needed to have functionality to undock parts of a web application onto separate monitors for some power users which required us to be able to share the state of our application easily between the windows to ensure that the state was consistent across the windows and to avoid running uneccessary requests to the server to avoid longer loading times when we undocked the application.
This was an Angular project where we are using the excelent
ngxs project to have a centralized store. This project aims to solve the same issues as redux, but it is developed with Angular in mind instead of beeing a generic JS library with hooks into js frameworks like redux. Since we had a centralized state store, I decided we could try to synchronize the state between the windows using
postMessage. I had already seen that ngxs does support plugins, so I tried to check out the documentation. Unfortunately I found it a bit lacking, but it was enough to get started. After I had looked at the documentation, I experimented a bit creating a plugin and intercepting incomming actions and running the actions on both the main browsing context and the child context (the popup).
This worked to a degree. I was able to run the same actions on both sides, but since the state wasn’t synchronized when the frame was opened so this didn’t work. Then I tried adding a method to request the initial state during startup of the child instance. This worked and we got our first prototype, although I quickly realized that we did all the work twice. This didn’t really make much of a difference when we were just retrieving data, but when we had an action that posted data, it would run on both the main and the child window so that we committed the data twice.
With this is mind I decided to change the architecture a bit. Instead of creating a plugin in the main window that sent a message to the child context so that we ran the same action in both contexts, I went for an architecture where we send the the updated state whenever an action has run instead of sending the action. This also made it possible for us to dispatch actions on the child that triggered on the store of the main window. This worked perfectly (or so we thought) and we were able to handle state changes on the host and child. I implemented this by using the
store.reset(...) method on the child whenever we got an updated state. Unfortunately this had the unfortunate side effect that we would trigger every store subscriber because the state was changed. I had to create a deep merge function (could have used the one in lodash, but wanted to avoid uneccessary dependencies) so that we traversed the object graph and only changed the part of the state that actually was changed. After we had done this it worked even better, but we still had one issue left.
Whenever we dispatched an action, we could change the state in our reducer using
setState without dispatching another action like in redux. Since this change wasn’t coupled directly to the dispatched action, we got an issue where the state would only update on the client whenever we was done loading an entity. This meant that the loaders wouldn’t show up in the child window. We got around this by ditching the plugin system on the host, and just subscribing to any store changes. This worked perfectly as far as I could tell and we are able to synchronize the state across the windows and any state updates from either the host or any of it’s children will automatically propagate to each other.
You can check out the project on my github, and install it using
npm i ngxs-message-plugin /
yarn add ngxs-message-plugin. I have documented how to use it in the README file, but the short version is to just import
NgxsMessagePlugin.forRoot() in your
App.module.ts for the host and
NgxsMessagePlugin.forChild() in your
App.module.ts for your child. You can see how the host and child modules are implemented in the demo project.
As a minor extra thing I implemented. I also created a chess game (github) where I used a custom transport implementation utilizing WebRTC so that you can play against other players. I haven’t implemented any chess rules, but it was a fun experiment seeing the implementation is robust enough to actually work over the network as well.