April 2021 Update: The website who issued the API key I use in this tutorial no longer offers free API keys, so the examples here won't work. I'm very sorry :(
Hello everyone and thank you for clicking on this article. I'm really excited about our tutorial today because it combines my 2 favorite things: programming and languages (the spoken kind).
I'm going to show you how to implement the "observer" design pattern in JavaScript, and then I'm going to demonstrate its usefulness in a web project. You are 1000% encouraged to take this code, tweak it, make it your own, and demonstrate it in your portfolio, because our end product is going to be pretty cool if I do say so myself. (This is what we're going to make)
Here's today's agenda:
- Quickly talk about what is is the observer pattern
- Talk about how we're going to implement it in JavaScript
The code
- Where do we go from here?
What is...
..a design pattern?
In programming, design patterns are tried and true solutions to problems we always have. According to the ol' Wikipedia, they are
...formalized best practices that the programmer can use to solve common problems when designing an application or system
..the observer pattern?
The observer pattern is when we have one object controlling a list of subscribers. Officially, we call the subscription controlling object the "Subject" and the subscribers the "observers."
For a very simple and watered down example, think of the news agency BBC. The BBC will get news from everywhere, and then expect their different networks to report on it. They have BBC World Service reporting in English, BBC Arabic reporting in Arabic, and BBC Brasil reporting in Portuguese. In this case, BBC as a whole would be the subject, and the observers would be the various networks (World Service, Arabic, Brasil).
What are we going to do?
Glad you asked! We're going to create 2 JavaScript classes, News
our subject, and NewsOutlet
our observer. We will instantiate one News object and three observers, add the observers to the subject's subscriber list, and transmit data to all observers via the subject, translate it into a different language, and display it.
Sound confusing? I promise you, it's not. Lets just start coding, you'll see.
JavaScript ❤️
The Subject
First, our subject, we shall name it "News":
// The news class is the Observable class or "subject"
class News {
// A list of observers
constructor() {
this.observers = [];
}
// Method for subscribing to, or "observing" observable
addSubscriber(subscriber) {
this.observers.push(subscriber);
}
// Method for unsubscribing from observable
unsubscribe(subscriber) {
var index = this.observers.indexOf(subscriber);
this.observers.splice(index, index);
}
// Method for sending data to subsribers
transmit(data) {
this.observers.forEach(subscriber => subscriber.receive(data));
}
}
Ok, so lets talk about this method by method:
constructor
- Nothing special here, we just want to make sure that when news is created, it has an array of observers. We'll add them later.addSubscriber
- This is the method which will officially make the observer subscribed to this subject. The syntax to add a subscriber will be likeSubject.addSubscriber(observer)
unsubscribe
- Should go without saying, but the observer we pass to this function will stop getting data from the subject.transmit
- This is how the data is going to get passed to the observers. This method loops through the subject's array of observers, and calls the observer'sreceive
method. Obviously, this means we must give the observer classes areceive
method.
So, in a nutshell, that is a very basic Subject for our observer pattern. Lets now define the class that will make up our observers.
The Observer
Here's the code for our observer "NewsOutlet, we'll go through method by method
// The News Outlets are subscribers to the news in different languages
class NewsOutlet {
// We will set the language when we instantiate the news outlet
constructor(language = "en") {
this.language = language;
this.data = "";
this.news = "";
// For this example we'll use my API key, but please go to
// https://yandex.com/ and sign up to get your own
this.apiKey = "trnsl.1.1.20190807T020501Z.f95163fde699ac87.1f9b3df7b5d7c045104d21249dc322086ee38004";
this.translateUrl = "https://translate.yandex.net/api/v1.5/tr.json/translate";
}
receive(data) {
this.data = data;
var urlParamList = "?";
urlParamList += "key=" + this.apiKey;
urlParamList += "&text=" + this.data;
urlParamList += "&lang=" + this.language;
var self = this;
// Translate after receiving
jQuery.ajax({
url: this.translateUrl + urlParamList,
contenttype: "application/json",
datatype: "json",
success: function(result) {
self.news = result.text[0];
self.reportTheNews();
}
});
}
reportTheNews() {
// A shady workaround for our HTML efforts!
let elemId = this.language + "1";
document.getElementById(elemId).innerText = this.news;
}
}
Alright! There's a lot to unpack here so let's go slow.
First of all, we are going to be using the Yandex API for translation. Please go to https://yandex.com to get your own API key, and swap it out with mine there before you run this too many times. It's free!
constructor
- For the observers, we are going to give them a language when we instantiate them. We have to pass the ISO language code to the API to make this work (here is a list of all the supported languages). "en" is English and our default. We're making instance variables calleddata
(the data from our Subject) andnews
(translated data). Finally, for the sake of ease, we're putting the API key and translation API URL in instance variables.receive
- Remember in the News class when ourtransmit
method was calling thereceive
method of all our observers? Well this is it. This method is first taking the transmitted data from the Subject and putting it into its own data variable. Then, it builds the complete API URL that we'll use to translate the transmitted data.The
jQuery.ajax
part - This is a pretty basic way to use public APIs. We assign the URL, content type, and data type, then say what needs to happen after a successful call to the API. Notice before this function thevar self = this;
. We did this becausethis
will not be available in the ajax function, so we will useself
. Don't let this confuse you, in some languagesself
is a reserved word. Not in JavaScript, you could call this variable almost anything you want. Anyway, once the API call has returned successfully, it'll set thenews
instance variable as the first element of theresult.text
object that comes back (it'll look like["this is the text returned in an array for some reason"]
). Finally, it will call thereportTheNews
method, which you can see by the comment, is not something I'm super proud of.reportTheNews
- When we show this off in our HTML later, we will have somediv
elements displaying the news. If you want to see the results now in a console, change the function like this:
reportTheNews() {
console.log(this.news);
}
Almost done, lets recap
At this point, we have actually already built our observer pattern. If you want to see the whole thing in action, make sure to switch the code in reportTheNews
like shown above, and write this into your console:
let news = new News;
let enOutlet = new NewsOutlet("en");
let ptOutlet = new NewsOutlet("pt");
let arOutlet = new NewsOutlet("ar");
These lines create our subject news
and observers enOutlet
, ptOutlet
, and arOutlet
. Lets have our outlets subscribe:
news.addSubscriber(enOutlet);
news.addSubscriber(ptOutlet);
news.addSubscriber(arOutlet);
And we're pretty much ready to go. Decide what you want the first headline to be. Mine is going to be "Erik is the best"
> news.transmit("Erik is the best")
< undefined
Erik is the best
إريك هو أفضل
Erik é o melhor
Just call me Mr. Worldwide 😎
That is it! That is the observer pattern. We transmit our data via the subject (news
in this case) to our subscribers (the 3 **Outlet
variables).
Now, I'm going to put this into something somewhat worth looking at, and you are more than welcome to follow along. But for the sake of "learning the observer pattern," you are done! Congrats, skip to the "Where do We Go From Here?" section
Live Demo!
Ready to put what we learned into practice? Here's what we're going to build:
Implementing the Observer Pattern like the demo above
Lets get to it. First of all, if you switched the reportTheNews
function, switch it back to
reportTheNews() {
// A shady workaround for our HTML efforts!
let elemId = this.language + "1";
document.getElementById(elemId).innerText = this.news;
}
We will be using that after we make the HTML page. The reason this has been configured in a roundabout way is because of the way ajax can behave sometimes. I won't get into that yet because this project is not a great example of good ajax work, so lets press on.
The HTML part
Lets make an input where we can type in our news, a button to send it, and a few things to show us what the different outlets are saying:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="News.js"></script>
<script src="NewsOutlet.js"></script>
<script src="implement.js"></script>
</head>
<body onload="defaultBreaking()">
<h2 id="pageTitle">The World News</h2>
<h3>JavaScript implementation of the Observer Pattern</h3>
<div class="worldNews">
<div>
What's the news??
</div>
<div>
<input type="text" id="sourceNews">
</div>
<button onclick="sendNews()" id="transmitter">Transmit!</button>
<div>
</div>
</div>
<div class="row">
<div class="column" id="enOutlet">
<div class="newsTitle">The US Gazette</div>
<div id="en1" class="breaking"></div>
</div>
<div class="column" id="ptOutlet">
<div class="newsTitle">The Portugal Post</div>
<div id="pt1" class="breaking"></div>
</div>
<div class="column" id="arOutlet">
<div class="newsTitle">The Halab Herald</div>
<div id="ar1" class="breaking"></div>
</div>
</div>
<div class="footer" id="Yandex">
All translation Powered by <a href="https://translate.yandex.com/">Yandex</a>
</div>
<div class="footer">
<p>by Erik Whiting</p>
</div>
</body>
</html>
Note, if you're copy/pasting, you must name the other files we're creating the same as I have:
style.css
News.js
NewsOutlet.js
-
implement.js
(we haven't made this one yet)
The CSS
Side note I really hate CSS but I'm trying to get better. I'm not designer by any means, so don't laugh at me:
* {
box-sizing: border-box;
}
body, h2, p {
font-family: Courier, sans-serif;
}
#pageTitle {
font-size: 50px;
}
.worldNews {
background-color: #262626;
color: white;
padding: 30px;
text-align: center;
font-size: 35px;
border: 1px solid black;
border-radius: 8px;
}
button {
background-color: orange;
font-family: Courier;
font-color: black;
font-size: 20px;
width: 400px;
}
input {
width: 400px;
height: 40px;
font-size: 30px;
}
.column {
float: left;
width: 33.33%;
padding: 10px;
height: 300px;
border-radius: 8px;
border: 1px solid black;
}
.newsTitle {
width: 100%;
text-align: center;
}
.breaking {
text-align: left;
font-size: 20px;
border: 1px solid;
border-radius: 8px;
padding: 5px;
margin: 2px;
}
#enOutlet {
color: white;
background-color: #820400;
}
#enOutlet > .breaking {
background-color: #fc6f6a;
}
#ptOutlet {
color: black;
background-color: #008c00;
}
#ptOutlet > .breaking {
background-color: #78f580;
}
#arOutlet {
color: white;
background-color: #000485;
}
#arOutlet > .breaking {
background-color: #515cfc;
}
.newsTitle {
font-size: 20px;
}
.row:after {
content: "";
display: table;
clear: both;
}
.footer {
background-color: #f1f1f1;
padding: 10px;
text-align: left;
}
@media (max-width: 600px) {
.column {
width: 100%;
}
}
If you're going to make this into your own project, play around with the format and colors, make it your own, show people your artistic side!
The script that ties it together
Ok, last thing's last, we need a little script to kinda put everything together. I called it implement.js
for no other reason than I couldn't think of a better name:
// Create all the objects we'll use
let news = new News;
let enOutlet = new NewsOutlet("en");
let ptOutlet = new NewsOutlet("pt");
let arOutlet = new NewsOutlet("ar");
// Subscribe
news.addSubscriber(enOutlet);
news.addSubscriber(ptOutlet);
news.addSubscriber(arOutlet);
// A function for setting the news elements to prompt user to type
// you don't need this if you don't want, just remove the onload
// from the HTML file if you remove this
var defaultBreaking = function() {
var breaking = document.getElementsByClassName("breaking");
for (let item of breaking) { item.innerText = "Type some news..."; }
}
// The function that will call transmit on news
// setting off the chain event of subscribers receiving
// then translating, then showing the news
var sendNews = function() {
let theNews = document.getElementById("sourceNews").value;
news.transmit(theNews);
news.observers.forEach(function(o){
o.reportTheNews();
});
}
And that's it. If you've been copy/pasting, then your site should look like mine. If you're getting a weird error about cross site scripting or something, and you're on windows, open run.exe
and type this:
chrome.exe --user-data-dir="C:/Chrome dev session" --disable-web-security
but be careful with that and don't do that often.
Where do We Go From Here?
Design patterns are awesome and knowing them will make you a better programmer, I don't care what anyone says. Just like with data structures and algorithms, when you at least know of design patterns and their general use, you will have a list of solutions in your head to many common problems.
One example I always see when people are talking about the observer pattern is trading. Say a ticker symbol has gone from 1.02 to 1.14, and there's a thousand different interfaces that need to be alerted. Ideally, in whatever server this kind of thing is running in, each one of those interfaces would be subscribed to the object broadcasting the ticker symbol's price.
The example we used is very simple, and there's lots of things you can do with it. For example, I'm pretty sure the KnockoutJS
framework (hi C# developers!) was built entirely on this concept. This is just one example of a pretty cool design pattern, there are several more to learn.
If you liked this tutorial, please tell me what you liked about it and what you'd like me to talk about next. A new pattern? A different language? A new pattern in a different language? Something else entirely? Let me know and please don't hesitate to ask me any questions.