« Back

Trying the AngularJS with Liferay

Company Blogs February 3, 2014 By Sampsa Sohlman Staff

One of the current front-end trends is seems to be AngularJS. Last week we had HelsinkiJS Meetup and again there were couple presentations talking about AngularJS. Our Developer Unconfrence at Berlin we had an unconfrence discussion about AngularJS. So AngularJS is for sure the framework that Liferay developers are keen to use.
 
I decided to take a look AngularJS and how to use that in portlet development. Using any framework with portlets means that they should behave well in portlet world. So the first task is to see if AngularJS is up to it. Behaving well in portlet world means that the framework has to have tools so it can respect the portal environment. As a normal web application, front end developer has total freedom to create what they want, but when they are stepping into portal world, their creation has to fit in limitation of the portlet. 
 
 
As most of you already know, portlet's UI limitation is rendering area, which means that portlet should always stick to its area. See the image. Traditional portlet developers we have tools as <portlet:namespace/> to achieve this. So I want to test if AngularJS can achieve this.
 
By looking AngularJS tutorial I see that AngularJS application contains modules, which have controllers. AngularJS tutorial shows, how bind module to view:
 
<html ng-app="myapp">
..
</html>
 
By looking this, my first impression is that AngularJS is not portlet ready since portlet does not own the page. As I study further reading it is clear ng-app does not have to be bound to html tag, but it can be bound to any tag. Next question can you have multiple tags with ng-app attributes same time at page as we can have multiple portlets and also multiple portlet instances. Quick proof of concept proofs other vice, so there can be only one ng-app attribute per page. So AngularJS seems to be failing the portlet world, but I decided to dig deeper and after further study I did find out that ng-app can be replaced by API call:
angular.bootstrap(<dom element>,<list of modules>);

Finally, I could confirm that with this API call it is possible have multiple AngularJS modules at one page and be bound to multiple dom elements.

This did lead me to integrate this Liferay.Portlet.ready(..) event and I did result following framework (angular-portlet.js):

(function(Liferay, angular) {
   if (angular.portlet)
      return;

   angular.portlet = {};

   var angularPortlets = {};

   angular.portlet.add = function(pluginName, portletName, angularFunction) {
      var portletId = "_WAR_" + pluginName.replace(/[_]|[-]/g, "");

      portletId = portletName.replace(/[_]|[-]/g, "") + portletId;
      angularPortlets[portletId] = angularFunction;
   };

   Liferay.Portlet.ready(function(portletInstanceId, node) {
      var portletId = portletInstanceId.replace(/[_]INSTANCE[_].+/g, "");

      if (angularPortlets[portletId]) {
         angular.bootstrap(node.getDOMNode(), angularPortlets[portletId](
            portletInstanceId, node.getDOMNode()));
      }
   });
})(Liferay, angular);

The framework is using plugin name + portlet name to register AngularJS modules to specific portlet. During the Liferay.portlet.ready event the module is bound to the portlet's dom element.

Following example demonstrate how this is done:

(function(Liferay, angular) {
   angular.portlet.add("poc-angular-portlet", "poc-angular-portlet",
      function() {
         var myModule = angular.module("myModule", []);

         myModule.controller("MyController", function($scope) {
            $scope.mythings = [ {
               name : "Thing 1"
            }, {
               name : "Thing 2",
            } ];

            $scope.add = function() {
               $scope.mythings.push({name: $scope.newThing.name});
            };

            $scope.remove = function(index) {
               $scope.mythings.splice(index, 1);
            };
         });

         return [ myModule.name ];
      });
})(Liferay, angular);
Here you can see that code is same as AngularJS JavaScript except you just have to wrap it to function and that to angular.portlet.add(..) with plugin name and portlet-name.
 
and correseponding html:
<div ng-controller="MyController">
    <h1>My Things</h1>
    <input ng-model="newThing.name"/>
    <button ng-click="add();">Add</button>
    <div ng-repeat="mything in mythings">
        <span>{{mything.name}}</span>
        <button ng-click="remove($index);">Remove</button>
    </div>
</div>
As you can see that there is no <portlet:namespace/> tags in HTML, since AngularJS only is scanning markup inside bootstrap element. 
As you can see portlet works well also as instantiable and this seems to be a good start to AngularJS portlet development.
 
Example app:
 
Thanks to make this possible:
EDIT 11th May 2014: It seems that Liferay js minifier does not like angularjs and as on my development portal it is disabled, so it did not appeared to me. Thanks for Miika Alonen for finding this.
 
You can disable minifier from portal-ext.properties
minifier.enabled=false
 
Threaded Replies Author Date
This is great stuff and I hope that more people... Wolfgang Kubens February 3, 2014 5:07 AM
Thanks, I did fix the link. Sampsa Sohlman February 3, 2014 5:24 AM
Did you also try navigation inside portlets? I... Jan Eerdekens February 3, 2014 8:07 AM
No I have not. I'm pretty new with AngularJS. I... Sampsa Sohlman February 3, 2014 9:01 AM
Hi Sampsa, last month my colleague show me the... Alessandro Aglietti February 3, 2014 1:45 PM
Hi Alessandro, I actually saw this... Sampsa Sohlman February 3, 2014 11:10 PM
I actually tried it months back and I was able... Bart Simpson February 3, 2014 9:55 PM
I was able to get routes working partially. I... Jan Eerdekens February 4, 2014 12:26 AM
Hi, I tried your solution but getting... sanket upadhyay February 27, 2014 1:41 AM
Hi Sanket, Did you tried the WAR file or the... Sampsa Sohlman February 27, 2014 1:50 AM
No I tried the github version on lifray 6.0.6. ... sanket upadhyay February 27, 2014 2:10 AM
I have not tried that on this on 6.0.6 at all. ... Sampsa Sohlman February 27, 2014 2:42 AM
Its a good starter and I like how you integrate... Moayad Abu Jaber April 7, 2014 2:23 AM
Actually, the Angular portlet callback is... Sampsa Sohlman April 8, 2014 1:58 AM
Thanks you very much for hint me some good points. Moayad Abu Jaber April 9, 2014 1:16 AM
[...] sampsa.sohlman: blog & github project... Anonymous October 1, 2014 6:36 AM
[...]... Anonymous December 1, 2014 12:09 AM
[...] wich tells to bower to download the... Anonymous December 8, 2014 2:10 PM
[...] Using the webservices These are pretty... Anonymous August 3, 2015 6:40 AM
Hi, Sampsa, Thank you for this blog! I have a... Robert Chen October 12, 2015 5:43 PM
Hi Robert, It is probably some sort of naming... Jan Eerdekens October 13, 2015 2:03 AM
@Robert I don't know :) .. This was a PoC and I... Sampsa Sohlman October 13, 2015 3:32 AM
@Sampsa: thx! Never been so nervous before,... Jan Eerdekens October 13, 2015 3:55 AM
Thank you, Jan and Sampsa, for your responses! ... Robert Chen October 13, 2015 1:41 PM
Hi Sampsa, I tried your sample. When I deploy... Sindhu h May 30, 2016 5:56 AM

This is great stuff and I hope that more people will get in and share their experience here too. By the way your github link isn't correct: https://github.com/sammso/poc-angular-portlet
Posted on 2/3/14 5:07 AM.
Thanks, I did fix the link.
Posted on 2/3/14 5:24 AM in reply to Wolfgang Kubens.
Did you also try navigation inside portlets? I got to the same point as you, instanciable portlets, with custom bootstrapping, but can get the default Angular routing stuff to work correctly as it modifies the URL.
Posted on 2/3/14 8:07 AM.
No I have not. I'm pretty new with AngularJS. I have not tried routes yet, but to make them work properly state should be save somehow if some other portlet is rendering the page, so it is more than just replacing render area with new html.
Posted on 2/3/14 9:01 AM in reply to Jan Eerdekens.
Hi Sampsa,
last month my colleague show me the follow github project that integrate angular and liferay with a fancy strategy.

Take a look!
https://github.com/dmitri-carpov/angularjs-portlet
Posted on 2/3/14 1:45 PM.
I actually tried it months back and I was able to get the routes working. However I did it with 6.1 (when Angular was quite new), will update and check if the same works with 6.2
Posted on 2/3/14 9:55 PM.
Hi Alessandro,

I actually saw this implementation, when I was looking AngluarJS and the idea of that seems to be all the portlet's would share same module8s) and there would be controller per portlet. I felt that it was intrusive to portal. I wanted to also know if I could create instantiable portlets with AngularJS. One of things I have not checked, what if there two angularjs.js references on page, by two portlet plugins which are introducing those? Can AngularJS handle that nicely and take only first one to use and ignore second one. Should the Angular to be integrated somehow AlloyUI's dynamic component loading and is it possible?
Posted on 2/3/14 11:10 PM in reply to Alessandro Aglietti.
I was able to get routes working partially. I made a simple, instanceable, master/detail portlet and placed it 2 times on a page. I was able to navigate separately to a detail page in both portlets, but when I wanted to go back, this only worked in one of the portlets and the second portlet wouldn't return to the main page. Even when I made the routes unique by adding the portlet instance id to it, the navigation still didn't work correctly in some cases.

I'm also working on 6.1, so I would be very interested in trying out your code if it is available somewhere?
Posted on 2/4/14 12:26 AM in reply to Bart Simpson.
sanket upadhyay
Hi,
I tried your solution but getting "ReferenceError: angular is not defined" in firebug.
Please check this out.
Thanks
Posted on 2/27/14 1:41 AM.
Hi Sanket,

Did you tried the WAR file or the version from github. I just tried the WAR file with 6.2.10 and it did work and also with Firefox 26
Posted on 2/27/14 1:50 AM in reply to sanket upadhyay.
sanket upadhyay
No I tried the github version on lifray 6.0.6.
And now I am getting the following error TypeError: g.getDOMNode is not a function both in war version and github version.
Posted on 2/27/14 2:10 AM in reply to Sampsa Sohlman.
I have not tried that on this on 6.0.6 at all.

So it seems that node does not have that method and that is difference between 6.0.6 and 6.2 API.

I think you can still solve this by putting break point to https://github.com/sammso/poc-angular-portlet/blob/master/src/main/webapp/js/ang­ular-portlet.js#L19 and see how to get node.

and could try change.

https://github.com/sammso/poc-angular-portlet/blob/master/src/main/webapp­/js/angular-portlet.js#L19-L20

to following:

var domNode = document.getElementById(portletInstanceId);

angular.bootstrap(domNode, angularPortlets(
portletInstanceId, domNode));

.. if that does not work change also closure definition:

(function(Liferay, angular, document) {
..
})(Liferay, angular, document);
Posted on 2/27/14 2:42 AM in reply to sanket upadhyay.
Its a good starter and I like how you integrate angular with liferay stuff. there is some question come to my mind when I think about angularJS in portlet beside what you mention it above. as what we see the structure for angularJS app is seperated with multiple javascript file, in your case put all of them in one file which also cause to the same thing when you need to used generated tags (portlet:actionURL , portlet:resourceURL ...etc) and these tags will not be available in javascript file since the javascript loading first.
Posted on 4/7/14 2:23 AM.
Actually, the Angular portlet callback is initiated after portlet is ready not earlier and
Liferay has JavaScript API which you can use for creating portlet URL's

Example:

var resourceURL = Liferay.PortletURL.createResourceURL();
resourceURL.setParameter("param-one", "param-value");
resourceURL.setPortletId(portletInstanceId);

var resourceURLLink = resourceURL.toString();

But the Liferay.PortletURL is not initialized by default so if you add:

AUI().use('liferay-portlet-url', function(A) {});

just after

function(Liferay, angular) {

portletInstanceId you can get by changing

angular.portlet.add("poc-angular-portlet", "poc-angular-portlet", function()..

to

angular.portlet.add("poc-angular-portlet", "poc-angular-portlet", function(portletInstanceId)

As this is just example.

BTW portletInstanceId is also namespace.
Posted on 4/8/14 1:58 AM in reply to Moayad Abu Jaber.
Thanks you very much for hint me some good points.
Posted on 4/9/14 1:16 AM in reply to Sampsa Sohlman.
[...] sampsa.sohlman: blog & github project [...] Read More
Posted on 10/1/14 6:36 AM.
[...] http://www.liferay.com/it/web/ssrikanthreddy1410/blog/-/blogs/connecting-to-diff­erent-database-using-liferay-service-builder... [...] Read More
Posted on 12/1/14 12:09 AM.
[...] wich tells to bower to download the needed modules (backbone and jquery) under src/main/webapp/js/. Many others have written writing what i call single portlet application, (here, here...) but they... [...] Read More
Posted on 12/8/14 2:10 PM.
[...] Using the webservices These are pretty simple in principle; Liferay still uses the dated Axis implementation of SOAP. A list of the services available from Liferay portal can be found at Download... [...] Read More
Posted on 8/3/15 6:40 AM.
Hi, Sampsa,

Thank you for this blog!
I have a question: I created a second portlet in the same package, which is a clone of your portlet -- I just changed the portlet name. I also changed the portlet name in the main.js file. But the second portlet does not display the AngularJS related content. Did I do something wrong?

Best regards,
Xinsheng Chen
Posted on 10/12/15 5:43 PM.
Hi Robert,

It is probably some sort of naming issue where something in one Angular portlet inadvertently overwrites something in another. I ran into similar issues while working on my Angular Adventures in Liferay Land posts and while preparing for my Devcon talk. The example portlets for the talk, https://github.com/planetsizebrain/angular-adventures, might be able to help you as you can put multiple of those, different ones + instances of the same, on 1 page and they should keep working.
Posted on 10/13/15 2:03 AM in reply to Robert Chen.
@Robert I don't know emoticon .. This was a PoC and I have not done since anything with Angular.

@Jan your great DevCon presentation did fill nicely gaps of this blog post.

Only thing you did use scriptlets instead of separate script file that I had here. I wanted to you about it, during DevCon, but I some reason I was too distributed emoticon
Posted on 10/13/15 3:32 AM in reply to Jan Eerdekens.
@Sampsa: thx! Never been so nervous before, glad you liked the presentation. As I mentioned in the presentation: I'm in no way, shape or form a Javascript/AngularJS expert... I just managed to get it working in a way that works, but isn't necessarily the best way. I'm sure there is room for improvement or better ways to do things.

I did indeed use scriptlets as it was an easy way to also make sure some Liferay stuff was initialized, but it does cause a little duplication and you need to use differently named bootstrap methods.

We'll have a nice chat about it the next time we see each other, which for me sadly won't be Devoxx... but there's always the next DevCon!
Posted on 10/13/15 3:55 AM in reply to Sampsa Sohlman.
Thank you, Jan and Sampsa, for your responses!

Robert Xinsheng Chen
Posted on 10/13/15 1:41 PM.
Sindhu h
Hi Sampsa,

I tried your sample. When I deploy just a war you have shared, the portlet looks fine. But if I edit or add angular variable i.e in main.js, portlet doesnt display the new ones or edited ones. Let me know what could be the issue?
Posted on 5/30/16 5:56 AM.