top of page

Subscribe to get best practices, tutorials, and many other cool things directly to your email inbox

  • Writer's pictureAhmed Tarek

The Right Way to Create Observable Subjects in JavaScript

A best practice on how to define your Subjects in JavaScript objects.


The Right Way to Create Observable Subjects in JavaScript. A best practice on how to define your Observable Subjects in JavaScript objects. JavaScript JS Promise Async Await. Best Practice Code Coding Programming Software Development Architecture Engineering
Photo by Robert Ruggiero on Unsplash

While working on your cool JavaScript project, you might need to implement a service which manages a stream of data. In this case, the first thing pops up into your mind is using Observables or Subjects.


You can do this using libraries like Rxjs or you can even implement it yourself as I showed you before on my article How to Use Observables with Vanilla JavaScript. No frameworks used, just pure vanilla JavaScript.


Either ways, at the end you would need to wrap a Subject inside your service object. However, there are more than one way to do it.


In this article, we would get introduced to a Best Practice on how to implement this.


 

The Right Way to Create Observable Subjects in JavaScript. A best practice on how to define your Observable Subjects in JavaScript objects. JavaScript JS Promise Async Await. Best Practice Code Coding Programming Software Development Architecture Engineering
Photo by Chris Ried on Unsplash

Time for Code


Let’s come up with a simple example. In our solution, we have an Authentication Service which handles logging in, logging out, getting current logged in user,….


We also have Some Module which does some interesting stuff and needs to know about the logged in user at some point.


We will implement this solution in simple steps and see where it goes.


The Right Way to Create Observable Subjects in JavaScript. A best practice on how to define your Observable Subjects in JavaScript objects. JavaScript JS Promise Async Await. Best Practice Code Coding Programming Software Development Architecture Engineering
Photo by Lindsay Henwood on Unsplash

In our solution, I would use my own implementation of the Subscription and Subject objects as I explained on my other article.


Therefore, just for brevity, I will include the code here as a quick reference.


const Subscription = function(handlerId, unsubscribeNotificationCallback) {
    const self = this;

    self.unsubscribe = () => {
        if (unsubscribeNotificationCallback) {
            unsubscribeNotificationCallback(handlerId);
        }
    };

    return self;
};

const Subject = function(subscribersStateChangeNotificationCallback) {
    const self = this;

    let handlers = {};

    Object.defineProperty(self, "subscribersFound", {
        get() {
            let found = false;

            for (const prop in handlers) {
                if (handlers.hasOwnProperty(prop)) {
                    found = true;
                    break;
                }
            }

            return found;
        }
    });

    Object.defineProperty(self, "subscribersCount", {
        get() {
            let count = 0;

            for (const prop in handlers) {
                if (handlers.hasOwnProperty(prop)) {
                    count++;
                }
            }

            return count;
        }
    });

    let unsubscribeNotificationCallback = (handlerId) => {
        if (handlerId && handlerId !== '' && handlers.hasOwnProperty(handlerId)) {
            delete handlers[handlerId];

            if (subscribersStateChangeNotificationCallback && !self.subscribersFound) {
                subscribersStateChangeNotificationCallback(false);
            }
        }
    };

    let createGuid = function() {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
            var r = Math.random() * 16 | 0,
                v = c === 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
    };

    self.subscribe = (handler) => {
        let handlerId = createGuid();
        handlers[handlerId] = handler;

        if (subscribersStateChangeNotificationCallback && self.subscribersCount === 1) {
            subscribersStateChangeNotificationCallback(true);
        }

        return new Subscription(handlerId, unsubscribeNotificationCallback);
    };

    self.next = (data) => {
        for (const handlerId in handlers) {
            handlers[handlerId](data);
        }
    };

    return self;
};

 

Authentication Service


const AuthenticationService = function() {
    const self = this;

    self.loggedInUser = new Subject();

    self.logIn = function(username, password) {
        // log in, get some user details (username, age,....), and update loggedInUser

        let user = {
            username: username,
            age: age
        };

        self.loggedInUser.next(user);
    };

    return self;
};

This is what we can notice here:

  1. Inside the AuthenticationService, we have a loggedInUser which is a Subject.

  2. Using this loggedInUser, other modules can subscribe to the stream of changes applied on the logged in user.

  3. We also have logIn function which does some API calls and finally sets the logged in user and trigger the loggedInUser Subject.


 

Initializing the Authentication Service


Somewhere in the main application, it is as simple as that.


const authenticationService = new AuthenticationService();

 

Some Module


const SomeModule = function() {
    const self = this;

    let loggedInUser = null;

    const subscription = authenticationService.loggedInUser.subscribe((user) => {
        loggedInUser = user;
    });

    self.DoSomeStuff = function() {
        // need to know the logged in user
        console.log(self.loggedInUser);
    };

    console.log(self.loggedInUser); // self.loggedInUser would be null

    return self;
};

This is what we can notice here:

  1. The module subscribes to the authenticationService.loggedInUser Subject to get updates about the logged in user.

  2. Whenever an update happens, a local variable called loggedInUser would be set.

  3. On the DoSomeStuff function, we log the value of the local variable loggedInUser which should be in sync with the latest updates on the AuthenticationService… or not?

  4. Actually, not. The problem is that by the time we are creating an instance of the SomeModule, the AuthenticationService would already be created and initialized.

  5. This means that on line number 15 in the code above, the local loggedInUser variable would still be null because up to that moment it was never set to another value.


 

The Right Way to Create Observable Subjects in JavaScript. A best practice on how to define your Observable Subjects in JavaScript objects. JavaScript JS Promise Async Await. Best Practice Code Coding Programming Software Development Architecture Engineering
Photo by Markus Spiske on Unsplash

The Right Way to Do It


It is not that complicated actually. There is a pattern used in this kind of cases and I am sure you had seen it before. It is even applied in different client-side Frameworks.


Enough talking, let’s see some code.


 

Enhanced Authentication Service


const AuthenticationService = function() {
    const self = this;

    self.snapshot = {
        loggedInUser: null
    };

    self.loggedInUser = new Subject();

    self.logIn = function(username, password) {
        // log in, get some user details (username, age,....), and update loggedInUser

        self.snapshot = {
            loggedInUser: {
                username: username,
                age: age
            }
        };

        self.loggedInUser.next(self.snapshot.loggedInUser);
    };

    return self;
};

This is what we can notice here:

  1. We added a new member called snapshot and it represents an object with a logged in user as a member inside.

  2. We also updated the logIn function implementation in a way that it first updates the snapshot object and then does what it used to do.


 

Enhanced Some Module


const SomeModule = function() {
    const self = this;

    let loggedInUser = authenticationService.snapshot.loggedInUser;

    const subscription = authenticationService.loggedInUser.subscribe((user) => {
        loggedInUser = user;
    });

    self.DoSomeStuff = function() {
        // need to know the logged in user
        console.log(self.loggedInUser);
    };

    console.log(self.loggedInUser); // self.loggedInUser would have some value

    return self;
};

This is what we can notice here:

  1. We updated the initialization of the local loggedInUser variable to get its value from the authenticationService.snapshot.loggedInUser.

  2. Therefore, on line 15 in the code above, the value of the local loggedInUser variable would not be null, it would hold the latest value of the logged in user.


 

The Right Way to Create Observable Subjects in JavaScript. A best practice on how to define your Observable Subjects in JavaScript objects. JavaScript JS Promise Async Await. Best Practice Code Coding Programming Software Development Architecture Engineering
Photo by Grégoire Bertaud on Unsplash

Finally, hope you found reading this story as interesting as I found writing it.



Recent Posts

See All

Subscribe to get best practices, tutorials, and many other cool things directly to your email inbox

bottom of page
Mastodon Mastodon