Writing real time applications using Spring and AngularJS (Part 3: AngularJS)

In the previous two tutorials I made the entire back-end for the real time application. You probably have noticed that the configuration part actually took longer than actually writing the application itself, this is because there’s a lot of magic behind the screens (think about the data access layer we didn’t have to write). This tutorial will actually handle the entire HTML + JavaScript part. The last few years JavaScript was really growing into a more mature environment. We now have frameworks like AngularJSEmber.js and many more that allow us to provide an abstraction layer for our DOM so we really don’t need to interact with it anymore. In this tutorial I will be using AngularJS.

View

The first part is to provide the view itself. In the <head> section we have to make sure we import the Semantic UI CSS file. In stead of the extremely popular Twitter Bootstrap, I’m going to use Semantic UI, a less popular, but a pretty fancy UI toolkit. If you used Bower to install Semantic UI, it should be available under the src/main/webapp/libs folder, so we need to add the following stylesheets:

<link rel="stylesheet" href="libs/semantic-ui/build/packaged/css/semantic.min.css" />
<link rel="stylesheet" href="assets/css/style.css" />

Then we need to bootstrap AngularJS and tell which controller we’re going to use, in this case I’m going to do that by using:

<body ng-app="myApp">
    <div ng-controller="ideaCtrl">
        <h2 class="ui header">
            <i class="settings icon"></i>
            <div class="content">
                Ideas
                <div class="sub header">View all posted ideas</div>
            </div>
        </h2>

        <!-- Listing the ideas -->
    </div>
</body>

Only the ng-app and ng-controller attributes are important here, the rest of the code is just some markup to style the application.

The next step is to provide a message if there are no ideas yet. I’m going to store the ideas as an array in our container in the model.ideas object. To show a message when the array is empty, I’m going to use the ng-hide attribute which will hide the element when model.ideas.length is defined (and bigger than zero) by doing:

<div class="ui info icon message" ng-hide="model.ideas.length">
    <i class="thumbs up icon"></i>
    <h2 class="header">First!</h2>
    It seems you're the first one using this web application, start by posting some new ideas.
</div>

Then I’m going to iterate through the ideas and display the information I want for each idea, you can do that using the ng-repeat attribute. The code I’m going to use is:

<div class="ui divided list">
    <div class="item" ng-repeat="idea in model.ideas | orderBy: 'votes' : true">
        <div class="right floated description" ng-class="{red: idea.votes < 0}">
            {{idea.votes}} votes
            <div class="mini ui buttons">
                <button class="ui button" ng-click="addVotes(idea, -1)">-1</button>
                <button class="ui button" ng-click="addVotes(idea, 1)">+1</button>
                <button class="ui red button" ng-click="remove(idea, $index)">Delete</button>
            </div>
        </div>
        <div class="content">
            <div class="header">{{idea.title}}</div>
            <div class="description" ng-bind-html="idea.description | markdown"></div>
        </div>
    </div>
</div>

The first thing to notice is the ng-repeat attribute, which does as I explained before, it will iterate over model.ideas and for each iteration it will return the current item in idea. After the pipelines I’m sorting my collection by the votes that an idea has. By default it will sort the collection ascending and to make sure we show the highest voted idea first, we need to reverse sorting by adding a second property which we put on true.

Then the next important thing to notice is the ng-class attribute on the line below. This allows us to provide a map of CSS class names and a boolean to indicate whether the class should be used or not. In this case the class "red" will be used when idea.votes < 0 (so when there’s a negative value). Then we show the number of votes to the user by using the {{idea.votes}} placeholder.

We also specified some additional action handlers by using the ng-click attribute. This attribute allows us to execute a specific method in our controller, in this case addVotes() and remove(). We also provide the index of the current idea in the list by using $index.

Finally we also used the ng-bind-html attribute, by default AngularJS will strip off all HTML, however, in this case we will use a markdown filter to markup our description. This markdown filter will convert the plain text to HTML, but in order to use that HTML we have to use the ng-bind-html attribute.

Then you only need the part of the view to add new ideas to the list. I’m going to use a simple form for this, for example:

<form ng-submit="add()" class="ui form">
    <div class="ui grid">
        <div class="thirteen wide column">
            <div class="ui field">
                <input type="text" placeholder="Title..." ng-model="model.newIdea.title" />
            </div>
            <div class="ui field">
                <textarea placeholder="Describe your idea..., markdown is allowed" ng-model="model.newIdea.description" rows="3"></textarea>
            </div>
        </div>
        <div class="three wide column">
            <button type="submit" class="ui fluid button">New idea</button>
        </div>
    </div>
</form>

Like the ng-click attribute from the previous code-part, I’m using ng-submit here which will make sure the add() function is called when the form is submitted.

I also have a few simple text fields and here you can see that we bind the value to model.newIdea.title and model.newIdea.description by using the ng-model attribute. What happens here is that for each change we make to the field, the model will be updated as well so when the form is submit we don’t need to retrieve the value from the form, because we already have it in the model.

The last step is that we’re going to add the necassary JavaScript files, the libraries (AngularJS, SockJS and Stomp) and the application itself. The code for this:

<script type="text/javascript" src="libs/angular/angular.min.js"></script>
<script type="text/javascript" src="libs/angular-resource/angular-resource.min.js"></script>
<script type="text/javascript" src="libs/sockjs/sockjs.min.js"></script>
<script type="text/javascript" src="libs/stomp-websocket/lib/stomp.min.js"></script>
<script type="text/javascript" src="libs/showdown/compressed/showdown.js"></script>
<script type="text/javascript" src="app/app.js"></script>
<script type="text/javascript" src="app/services.js"></script>
<script type="text/javascript" src="app/filters.js"></script>
<script type="text/javascript" src="app/controllers.js"></script>

Next to AngularJS we also need angular-resource which makes it easier to integrate RESTful webservices in the client. SockJS and STOMP are both used for the WebSocket communication, SockJS being the lower level component providing a WebSocket client (+ fallbacks if not supported) and STOMP providing a protocol above WebSockets. Showdown is a library to convert markdown markup syntax to plain HTML so we can render it.

Application configuration

Our first JavaScript file (app.js) contains the declaration of the myApp app (which we defined earlier when using the ng-app attribute earlier), in this case it’s just importing the myApp.controllers, myApp.services and myApp.filters “packages”:

angular.module("myApp", [ "myApp.controllers", "myApp.services", "myApp.filters" ]);

Service

The service actually contains a small REST client using angular-resource, the code for it (services.js) is:

angular.module("myApp.services", [ "ngResource" ]).factory("Idea", function($resource) {
    return $resource("./ideas/:id", {
        id: '@id'
    }, {
        update: {
            method: "PUT"
        },
        remove: {
            method: "DELETE"
        }
    });
});

On the first line we’re declaring the myApp.services package and importing ngResource (Angular resource), then we provide a factory which uses AngularJS to use the ./ideas/:id RESTful webservice endpoint. We also define additional methods for updating and removing a specific idea.

Filter

A filter (filters.js) is something that can be applied to input. For example, we used the orderBy filter to sort the idea collection and we also used a filter called markdown to format our description. However, this is a custom filter so we have to implement it first.

angular.module("myApp.filters", []).filter("markdown", function($sce) {
    var converter = new Showdown.converter();
    return function(value) {
        var html = converter.makeHtml(value || '');
        return $sce.trustAsHtml(html);
    };
});

What this does is quite easy, it uses the Showdown library to convert the markup to HTML and then it uses $sce to sanitize it as HTML. The entire encode HTML and sanitizing output principle prevents it from executing malicious content (XSS injections).

Controller

The controller (controllers.js) contains a bit more code, but the setup is quite similar, first we need to define the myApp.controllers package:

angular.module("myApp.controllers", []).controller("ideaCtrl", function($scope, Idea) {
    // Controller code
});

$scope will actually contain the contet to work on, all objects (model) and methods in our controller should be add to $scope. The Idea object refers to the factory we made earlier on.

The model for our application looks like:

$scope.model = {
    ideas: Idea.query(),
    newIdea: {
        title: null,
        description: null
    }
};

$scope.initiator = false;

$scope.socket = {
    client: null,
    stomp: null
};

$scope.model in our view, the only new thing here is that we use Idea.query() to retrieve all ideas, this will actually call the ./ideas/ endpoint, which is mapped on our Spring controller to give a list of all ideas (converted to JSON).

The $scope.initiator will be used to set a flag when the user changed an idea. If the user changed an idea, the local model is updated automatically, however, because the websockets are notifying each client of the changes, the client would reload the model, which is not necessary in this case. The $scope.initiator flag will prevent reloading the model in this case.

The next function I’m going to define is the add() function that is called when the form is submitted. This will actually create a new idea and that is automatically persisted because Angular resource will call the RESTful webservice which will in turn persist the changes to the datasource.

$scope.add = function() {
    $scope.initiator = true;
    var idea = new Idea();
    idea.title = $scope.model.newIdea.title;
    idea.description = $scope.model.newIdea.description;
    idea.votes = 0;
    idea.$save(function(response) {
        $scope.model.ideas.push(response);
    });
    $scope.model.newIdea.description = '';
    $scope.model.newIdea.title = '';
};

As you can see we also set the $scope.initiator flag to true and we use the title + description that we put in $scope.model.newIdea.title and $scope.model.newIdea.description to complete the idea object. These values are actually bound to the value of the textfield (thanks to the ng-model attribute).
As a last step we also reset the text fields by changing the model. This is a nice example of two way binding (changing the textfield also changes the model and changing the model also changes the textfield).
By using $save() the RESTful webservice is invoked, which we then use the locally change the model by adding the new idea using push().

The next function is the remove() function, the code for this is even easier since we only have to call the $remove() function on the idea to persist the changes and use splice() to locally change the model.

$scope.remove = function(/** Idea */ idea, /** Integer */ index) {
    $scope.initiator = true;
    $scope.model.ideas.splice(index, 1);
    idea.$remove();
};

Then there’s only one function left that is used to interact and that’s the addVotes() function. The code for this is:

$scope.addVotes = function(/** Idea */ idea, /** Integer */ votes) {
    $scope.initiator = true;
    idea.votes += votes;
    idea.$update();
};

Quite similar to removing the idea we call the $update() function and locally change the model by changing the idea.votes property. Due to this, both client and server are in sync.

We only have to configure the WebSockets now, I’m going to use the following code for it:

$scope.notify = function(/** Message */ message) {
    if (!$scope.initiator) {
        Idea.query(function(ideas) {
            $scope.model.ideas = ideas;
        });
    }
    $scope.initiator = false;
};

$scope.reconnect = function() {
    setTimeout($scope.initSockets, 10000);
};

$scope.initSockets = function() {
    $scope.socket.client = new SockJS('/spring-live-updates/notify');
    $scope.socket.stomp = Stomp.over($scope.socket.client);
    $scope.socket.stomp.connect({}, function() {
        $scope.socket.stomp.subscribe("/topic/notify", $scope.notify);
    });
    $scope.socket.client.onclose = $scope.reconnect;
};

$scope.initSockets();

I’m going to start explainin the code from the bottom to the top. The first thing done when initializing the controller is that the initSockets() function is called. This function will open a connection to /spring-live-updates/notify and use the Stomp library to listen to the /topic/notify topic. This is the same topic we used in the aspects in the back-end code, so all notifications will be sent to the $scope.notify() function.

This function first verifies if the current user is the initiator (by using the $scope.initiator flag) and then updates the model by query’ing the RESTful webservice. When it’s done the initiator flag is put to false again.

Then the only thing that has to be done is to reconnect to the server each time the WebSocket connection is broken, that’s what I do by calling the $scope.reconnect() function.

Stylesheet

One last thing to do is to add your own stylesheet (assets/css/style.css) which should contain:

.ui.list .item .red.description {
    color: #A95252;
}

.ui.form textarea {
    min-height: 0;
    height: auto;
}

.ui.list .item .content .description p {
    margin: 0;
}

These CSS rules just improve Semantic UI. The first one is used when the votes drop below zero. We said that when it gets below zero, the class red should be applied (thanks to the ng-class attribute). Semantic UI doesn’t support red colors in the description, so we added that.

The next element disables the height settings of the text area, because I want to use the rows attribute of the <textarea> to define the height of the element.

And the last one just prevents the margin of the description. By default it will wrap it all inside a <p> tag with default margins, so I just remove them.

Demo

Nowe we actually wrote our entire application so it’s time to see what we actually made. If you run the application on a server and start your web browser, you should be able to see the following screen:

start-screen

This is what we actually expect to see. Our database is empty, so that means the default message pops up. If you open your console you should also be able to see that the WebSocket connection is established:

socket-connectinitial-rest

We can also see that it already called our RESTful webservice to retrieve the ideas. If we add a new idea by entering the fields below, when we submit the form we will see it added immediately:

initial-ideanew-idea-post

As you can see the idea is added, the form fields are cleared (because we cleared the model) and the initial message has disappeared. If you look at the console you will also notice a few things:

rest-addsocket-connect

You can see that it posted the new idea on the server and a bit later that the WebSocket receives a message from the server indicating that the ideas have been changed. However, because the initiator flag was set to true, nothing really happens.
When you open up a second browser window and start adding votes to the idea, you will notice the changes in the second browser window as well. If you go look at the console, you will be able to see that in the first window it will do a PUT-request for every change, while in the other browser it will do a GET-request after retrieving a WebSocket message (because he isn’t the initiator).

rest-updaterest-update-second

When a new idea is posted (or an idea is updated), you can also see that every PUT or POST request contains a payload which contains the idea that is altered in JSON format.
If you add multiple ideas, you will see that the idea with the most votes is always on top (because we used the orderBy filter) and that when the votes go below zero, the vote count is red.

angular-orderby-class

Achievement: Tutorial finished

This ends our small series about writing real time applications usin Spring, WebSockets and AngularJS. If you’re interested in the full code example, you can find it on GitHub. If you want to try out the code yourself, you can download an archive from Github.

Writing real time applications using Spring, AngularJS and WebSockets

  1. Project setup and configuration
  2. RESTful webservice and WebSockets (Spring)
  3. AngularJS front-end

You can also find the complete code example on Github.

Tagged , , , , , , .

g00glen00b

Consultant at Cronos and Tech lead at Aquafin. Usually you can find me trying out new libraries and technologies. Loves both Java and JavaScript.

  • Ramesh Addanki

    Thanks….More helpfull article

    How to pass the set wait time for one user to onether user

  • Guillermo Pi Dote

    Excelent Post!!!!
    Was very easy run a copy of your code!

    Thanks.

  • This is Great Wonderful Coding Technique.. Awesome ideas Thanks..

    Web Development India

  • Phil

    Hi – I am working through your article and it is very useful article so far, especially having the code.

    I have one question currently – am I right in understanding that having the “remove” method in your services.js is redundant, as angularJS maps the $remove to DELETE anyway, as per: https://docs.angularjs.org/api/ngResource/service/$resource ?

    Thanks!

    • Now you mention it, probably yes :p

      • Phil

        Great, thanks!

        I have another question now….most of the things you’re using are new to me, so sorry if the question seems simple.

        I want to have another table, unrelated to the first. To do this, will I need another dto, another service and another controller right?

        To create the other service, I’ve just copied and pasted the other definition to create a new one. I’ve also added a new persistence-unit into persistence.xml, and added a mapping in beanMappingBuilder in AppConfig…(though I See setPersistenceUnitName to ideas, I haven’t created another for my second one….)

        For the controller, I have removed the @RequestMapping from above the 2 controllers, and just added mapping for / to the idea GET.

        I then pass this service into angularjs controller, but I’m getting an injection error: http://errors.angularjs.org/1.3.8/$injector/unpr?p0=UserProvider%20%3C-%20User%20%3C-%20storyCtrl

        Is what I’ve done correct, or can it not be done that way? I think I might be going about it the wrong way, having 2 persistence units indicates 2 databases which is not the case, so should I rewrite it to have a single service which can write to 2 tables(??)

        (I tried to add the second service to the same factory method, but had no luck with that either)

        • Well, you just create a new entity and a repository, that would be enough because in this example the configuration will automatically create a second table for you. If you want to make both resource available for live updates, then you probably want to adjust the NotifyAspect aspect so that you can send a topic with it (now it’s a constant value). With a different topic for both users and ideas, you can write the JavaScript code so that it will only listen to one of them.

          • Phil

            Wow that sounds so much simpler than what I’ve done. I finally got it working but it’s not pretty! I have both the entity and repository, but also have made another DTO, service, and controller. I’ve learnt about using 2 different data sources so not all bad, but I found it very difficult to get working.

            I don’t quite understand your suggestion, as surely you need a second service and controller to be able to forward map requests (write, read from tables)? But I will look into this, thank you!

          • Yes, you also need separate services probably and a controller. If you’re interested in creating REST services easily, then you should take a look at Spring Boot.

            But I think you’re on the right track, it’s not ugly I think.

  • Interesting article and helped to learn lot of things.. Thanks…

    One quest>(/** Idea */ idea, /** Integer */ index) what the notation /** ..*/ stands for?

    • g00glen00b

      O, those are just comments, but I often use them in JavaScript to indicate what the supposed types are.

      • thank you
        Can you tell me how the restful invocation happening from the controller.js file?

        • g00glen00b

          It’s thanks to the service I created halfway the tutorial. It’s injected into the controller (Idea) and then you can use stuff like

          Idea.query()

          for retrieving the ideas,

          var idea = new Idea()

          and

          idea.$save()

          to add new ideas, and

          idea.$remove()

          for removing.

          The ngResource module (responsible for the Task-service) will use the URL and when you call the query method, it will call your REST service as a GET request with the URL provided. When adding new stuff it will use a POST request to that URL.

          The update and remove functions are custom, and will use the UPDATE and DELETE request and will also provide the ID of the task so we know which one to delete.
          The RESTful invocation itself happens from the angular-resource library (it’s entirely abstracted away).

          • I have modified your example just to learn more about angular JS.

            I haven’t got the idea how to pass some values from spring mvc to the screen.

            What try to achieve is suppose in this idea entry screen I need a topic drop down which is getting the value from database. I am adding in the spring controller method like this

            public String viewIdeas(Model model) {
            model.addAttribute(“Topic”, service.getTopic());//value getting from db
            return “ideas”;
            }

            now how can I display the topic in the drop down in the jsp page? Is this the correct way?

          • g00glen00b

            If you add something to the model, then you can use in the JSP like this:

            topic.name

            , this would access the property “name” of the topic object.

  • shikhar srivastava

    Hey I got an exception saying :
    Error during WebSocket handshake: Unexpected response code: 404
    Please guide me what to do as I am not able to debug it! Although I get that I need to configure ports for communication but how and where?

    • g00glen00b

      Normally you don’t have to configure extra ports, since the handshake itself happens through the HTTP protocol and should utilize the same port you’re using to run your application.
      Maybe your context root is different than mine, If you look at line 57 of controllers.js you may notice that it uses /spring-live-updates as context root, if you don’t have that, change that line: https://github.com/g00glen00b/spring-websockets/blob/master/src/main/webapp/app/controllers.js#L57

  • Chris Jansen

    One question – you are sending a new Java date to the websocket. Am I correct in saying this Date is not used in the AngularJs code?

    • g00glen00b

      You’re correct, I’m not using that. You could send anything you like. At a customer we’re giving each client a generated UUID and send that UUID along with the WebSockets.
      By doing that, the client can validate if he is the origin of the change in model and so he doesn’t have to reload the model.

  • Chris Jansen

    Great article.

    In part 3, I think it’d be helpful to elaborate more on how to access the console (am assuming you are using Firebug). Other thing is when I run my app in Tomcat 7, I receive the following errors continuously in the Firebug console. Live updates aren’t happening either:

    Opening Web Socket…stomp.min.js (line 8)
    GET http://localhost:8080/spring-live-updates/notify/info 404 Not Found
    3mssockjs.min.js (line 27)
    Opening Web Socket…stomp.min.js (line 8)
    GET http://localhost:8080/spring-live-updates/notify/info
    404 Not Found
    4mssockjs.min.js (line 27)
    Opening Web Socket…

    Any help would be appreciated!!

    • g00glen00b

      Do you get errors in your Tomcat log files?

      • Chris Jansen

        I have no errors in my Tomcat logs. The app seems to run fine, minus the live updates.

        I am using: Firefox 29.0.1, jdk1.7.0_40 (x64), Tomcat 7.0.53/Tomcat 8.0.4, stompjs 0.3.4, stomp-websocket 2.3.1.

        I’ve also disabled my antivirus protection and my firewall without luck.

        • g00glen00b

          Are you using the code from Github? If you made any modifications or wrote it by yourself by following this tutorial, could you send me your code then?

          • Chris Jansen

            Yes, I am. I haven’t modified the code. I am getting the errors mentioned after building and running the code. Are you using http://localhost:8080 to access the web page? Can you try running it by running against the latest Javascript libraries (by doing a fresh Bower install) and see if it runs for you?

          • g00glen00b

            I just tested the app out again (from scratch) and it seems to work fine. Specs:
            – Java: 1.7.0_55
            – Tomcat: 8.0.3
            Tested on Google Chrome v34 and Firefox v29 and it still works. Weird stuff…

          • Chris Jansen

            I found out what it was!!!! I was able to run in Eclipse. I was not able to run in Intellij (which is what I originally tried to do).

            The problem was that by default, Intellij removes the context when it deploys to Tomcat. In Intellij, i was able to view the app on:http://localhost:8080 hwever, websocket communication wasn’t working.

            On Eclipse, the context is retained and the app is deployed to:http://localhost:8080/spring-live-updates/. Everything (including the websockets) works fine.

            I went back into Intellij and specified that the app context needs to be /spring-live-updates and now it too is working fine.

            Might want to put somewhere on page 3 of this article to access the app by navigating the browser to:http://localhost:8080/spring-live-updates

          • Boris Zubchenko

            Solution (for IntellijIdea + Tomcat):
            https://github.com/g00glen00b/spring-websockets/blob/master/src/main/webapp/app/controllers.js#L57

            Change this line to:
            $scope.socket.client = new SockJS(‘/notify’);

            It works for me!

  • Sergio

    Really good article! I would like to know if you could use this concept to notify not all the clients but certain clients! Suppose you would like to create a chat room and there are different rooms, How could I notify only some clients that are registered to that room! I would like to avoid the broadcast! Thank you!

    • g00glen00b

      If you really want a chat room, then you probably want to send all data through websockets (in this example I only use it to notify that there are changes, the actual data is served through REST).

      If you’re going for full websockets you can use @MessageMapping(“/chatroom/{room}”) (similar to how @RequestMapping works) and then you can use @DestinationVariable(“room”) to retrieve the room.
      Then you just send a message back to “/chatroom/” + room and then all messages only sent to people who are subscribing to the topic for that room (so make sure each client subscribes to the topic /chatroom/{room} and send messages through it as well.

      • Sergio

        I will try. I believe that the websocket has to contain the data related to a message with their payload. This concept to know which client subscribe to a room could be also used for different scenarios, like a game between two people. I wrote a my sample game that allows two people to play but they have to play from the same pc. My idea was able them to play to the game remotely. If you want I could share with you by bitbucket repo. I putted in bitbucket because it’s an experiment and I am not enough proud of my job.

        • g00glen00b

          Sounds interesting, I also tried to write a “platform game” once and even wrote a tutorial about it on this blog (though it’s in the early period of my blog and it’s written in Dutch). The only thing they could do is run around though (with multiple players using WebSockets).

          The project itself uses Node.js in the back-end though and Dojo (a JavaScript framework) in the front-end. Here’s a screenshot of it: http://i1.wp.com/g00glen00b.be/wp-content/uploads/2013/03/example-game.png

          • Sergio

            I added you on linkedin thanks. Could I share with you my code or you are not interested? Does your example with node.js is on github?

          • g00glen00b

            You can send me the code, but I don’t have the time right now to check it out.
            The code of my example is not on Github (none of my early examples are on Github). But the code can be found here: http://g00glen00b.be/wp-content/uploads/2013/03/game-server.tar.gz

          • Sergio

            I added you on linkedin, could you send me with a linkedin message your email address? I believe that I can send the invite to you. I have also a CRUD example that uses Spring Data Rest and you can write less code for CRUD. It could be an enhancement of this example for the backend 🙂

  • Kencrisjohn de Guzman

    Wow. This post is what I actually looking for. Thanks.

    • g00glen00b

      Great! If there are other things you’re looking for/interested in (and they have something to do with web development), let me know!

  • Mark Cuypers

    It would be interesting to see the 3rth part also using Dojo. Could we expect this on your blog anytime soon?

    • g00glen00b

      Hmm, I might try that but I’m not sure if it’s going to work. I’m using the STOMP protocol on top of WebSockets. There’s a small library that can be used and allows you to put a wrapper around a SockJS client, however, it may or may not work using Dojo WebSockets.
      But it’s certainly an interesting case to test.