Follow me on Twitter, happy to take your suggestions on topics or improvements /Chris
NGRX is an implementation of the Redux pattern. The Redux pattern is itself a publish/subscribe pattern also called Pub-Sub. Essentially this pattern is about one key thing, when a change happens it might be the concern of one or many parts of your application and you need a way to convey that change. You also need a way to do so without your code being coupled
Ok then, let's take this from the beginning and learn Pub Sub, Redux, the Typescript we need and some basic RxJS, no more being confused Ok?
This article is part of series:
- NGRX - from the beginning, part I, Pub-sub, we are here
- NGRX - from the beginning, part II, Redux, core concepts and different implementations, in progress
- NGRX - from the beginning, part III, NGRX store, this covers basic usage of the Store
- NGRX - from the beginning, part IV, NGRX effects, in progress
- NGRX - from the beginning, part V, NGRX entity, in progress
This article covers the following:
- Pub-Sub, what it is and why we need it
- Communicating with messages, an implementation of the Pub-Sub pattern and how we can apply that to any component based library/framework that we are using.
The need for Pub-Sub
As we said in the beginning NGRX is an implementation of Redux and what we really add to our application is the ability to do Pub-Sub, to broadcast a message when there is a change in the system. Ok, when does that happen then? Well, imagine you have set your app to a certain language and then you change that language, what should happen? Well, most likely a certain language set means that your app have translated parts or all of the App to that chosen language. So when a language is introduced some translation will need to happen, the question is what part of your App is affected? You probably know this better than me but let's try to introduce some reasoning. Your App is probably split up into several components to make maintenance easier. If your App consists of a number of pages where there is one component per page you don't need Pub-Sub.
Huh?
If you have a simple app with let's say 5 pages and one component per page then when you change to a new language you only need to affect the page you are standing on. At that point you probably call a service asking for these new translations with pseudo code like so:
function onLanguageChange(newLang) {
service.getTranslationsByLang(newLang);
}
Now you need to figure out a way to take this new translations and apply them in your site like so:
function onLanguageChange(newLang) {
const translations = service.getTranslationsByLang(newLang);
this.title = translations.title;
this.description = translations.description
}
Then when you change from this page to another page you need to do something similar like above but as early as possible so in a constructor or in ngOnInit
, like so:
constructor(){
const translations = service.getTranslationsByLang(newLang);
this.title = translations.title;
this.description = translations.description
}
So when do I need Pub/Sub?
As soon as you have many components on a page and you make an application wide change like a language change, for example, you need to tell all those components that a change has happened that you need to care about. Now, if you are using Angular, you can just set a property on those components using their @Input
binding, but that quickly becomes a very tangled mess, let me show you:
class Component {
private _lang = '';
@Input('language')
set lang(value) {
this._lang = value;
const translations = service.getTranslationsByLang(newLang)
this.title = translations.title;
this.description = translations.description;
};
get language() {
return this._lang;
}
}
Feels a bit coupled right and a lot to write?
Communicating with messages
At this point, the hurt is real as you most likely have written the above code for maybe 10 different components. You might have come up with a good way of omitting the call to service.getTranslationsByLang
and only do so in one central place and then we only send the translations.
What is the hurt consisting of though? Ah, now we are asking the right question, the hurt ISN'T because we need to write 3-4 lines and assign the correct translation to each property on a component - the hurt is because we use a property to set a language on X number of components. That way of communicating this info is very coupled and also very reliant on the specific framework we are using.
Ok, so what's a better way to do it?
A better way is to communicate with messages. Communicating with messages means we have a sender and one or more listeners. There are many ways to do this. Here is a non exhaustive list:
- Using a Vanilla implementation
- Using the library EventEmitter
- Using RxJS
Let's not make this article too long but let's implement the first case, a vanilla implementation.
A vanilla implementation
In an implementation, using no libraries, we need to fulfill the following:
- we need a way to send a message
- a way to subscribe, unsubscribe to messages
Ok, let's have a look at a naive implementation:
class PubSub {
constructor() {
this.listeners = [];
}
send(messageType, message) {
this.listeners.forEach(l => l(messageType, message));
}
subscribe(listener) {
this.listeners.push(listener);
}
unsubscribe(list) {
this.listeners = this.listeners.filter( l => l !== list);
}
}
module.exports = PubSub;
Our implementation above supports sending a message using the send()
method and the methods subscribe()
and unsubscribe()
gives us a wayo to add/remove listeners. This is pretty much all we need to do Pub-Sub.
Taking this for a spin it looks something like this:
const PubSub = require('./pubsub');
const ps = new PubSub();
const l1 = (type, message) => {
console.log('sub1');
console.log(`Type: ${type}, Message: ${message} `);
};
const l2 = (type, message) => {
console.log('sub2');
console.log(`Type: ${type}, Message: ${message} `);
}
ps.subscribe(l1)
ps.subscribe(l2)
ps.send('INCREMENT', 1);
ps.send('language', 'en');
ps.unsubscribe(l1);
ps.send('spam', 'hello');
We can see above that we define two listeners l1
and l2
. Both the listeners subscribe to our PubSub
class. Then we see that l1
stops listening by calling unsubscribe()
. Running the program we get the following output:
The output shows how both of our listeners gets the message INCREMENT
and language
whereas only l2
gets the message spam
as l1
unsubscribed before that message could happen.
Ok, our PubSub
class seems to work. So what's the point with all this? The idea is to show two things:
- communication with messages
- communicating in way that is loosely coupled, i.e we are not relying on implementation details on Angular, Vue, React or whatever library we use.
Applying this to our components, where we change the language, we would get the following code:
// PubSub.js
class PubSub {
constructor() {
this.listeners = [];
}
send(messageType, message) {
this.listeners.forEach(l => l(messageType, message));
}
subscribe(listener) {
this.listeners.push(listener);
}
unsubscribe(list) {
this.listeners = this.listeners.filter( l => l !== list);
}
}
const pubSub = new PubSub();
module.exports = pubsub;
Above we changed our PubSub
class slightly by creating an instance of PubSub
and it is that instance that we are exporting.
// sending component
import PubSub = require('./pubsub');
class Component {
constructor() {
}
setLanguage() {
pubsub.send('changeLanguage', 'en');
}
}
The sending component above just imports our PubSub
instance and sends a message by invoking the method send()
.
// listening component
import PubSub = require('./pubsub');
class OtherComponent {
constructor() {
pubsub.subscribe(this.onMessage.bind(this));
}
onMessage(type, message) {
if (type === 'changeLanguage') {
const translations = service.getTranslationsByLang(message);
this.title = translations.title;
}
}
}
The listening component is using the same instance of PubSub
and sets its onMessage()
method as a listener in the constructor. That same onMessage()
method is then invoked when a message is sent. Worth noting is also how we check that the message being sent is of type changeLanguage
before we go on and fetch new translations from the translation service.
Summary
The is article set out to talk about the underlying pattern to both NGRX and Redux, namely Pub-Sub. The ability to send a message to one or many listeners. We discussed when we need it and even constructed a very simple implementation, a class called PubSub
and showed how we could use it with our components.
In the next part we will look specifically at the pattern Redux which is a more specialized version of Pub-Sub, essentially the same pattern but with the memory of our current state is and a more guarded way of changing the state.
Top comments (0)