Intro
A lot of developers will probably know this feeling: you've just returned from a conference and seen lots of exciting new technologies and now you can't wait to try them out! For me one such moment and technology was Devoxx 2013 and seeing an AngularJS talk by Igor Minar and Misko Hevery. The speed and ease of development appealed very much to me, but I was a bit hesitant because of the language choice: Javascript. Javascript and me have been a bit of reluctant friends that tolerate each other, but don't need to spend a lot of time together (a bit like Adam and Jamie of Mythbusters fame). I usually try to hide all Javascript complexity behind the frameworks I use, e.g. JSF/Primefaces. Nonetheless I did see and love the inherit challenge of trying to use a new framework and see how it can be used in what I do on a daily basis: customizing and writing portlets to use in a Liferay portal. So for this blog post I forced myself to do the following:

Lots & lots of Javascript
Not alone
- pablorodriguez: video & github project
- EsupPortail: github
- nonblocking: blog & github project
- sampsa.sohlman: blog & github project
- StackOverflow: question & question
function bootstrap(id, portletId) {var module = angular.module(id);// add controllers, etc... to modulemodule.controller("MainCtrl", ['$scope', function($scope) {// do stuff}]);angular.bootstrap(document.getElementById(id),[id]);}
From your JSP page you can simply call this as follows:
<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %><%@ taglib uri="http://liferay.com/tld/aui" prefix="aui" %><portlet:defineObjects /><div id="<portlet:namespace />main" ng-controller="MainCtrl" ng-cloak></div><aui:script>bootstrap('<portlet:namespace />main', '<portlet:namespace />');</aui:script>
Bootstrapping AngularJS like this allowed me to create a simple, instanceable, Hello World! AngularJS portlet that you could add multiple times to the same page and that would work independent of each other.

From here to there
The next problem is the one I knew beforehand would cause the most problems: navigating between pages in an AngularJS app. The problem here is that AngularJS assumes control over the URL, but in a portal this is a big no-no. Everything you try to do with URLs needs to be done by asking the portal to create a portal URL for your specific portlet and action. If you don't do this and mess with the URL yourself you might at first think everything works as expected, especially with a single AngularJS portlet on the page, but with multiple you'll quickly see things will start to go wrong.
- AngularJS HTML5 mode
- Angular ui-router named views & UI-Router Extras (by christopherthielen)
- Angular-Multi-View (by vincentdieltiens)
- dotJEM Angular Routing
- angular-route-segment
I still think that one of these should work and I probably made one or more mistakes while trying them out, but due to time constraints I opted to go for a simple, albeit hacky, solution: using an internal page variable combined with the AngularJS ng-include attribute. The reason I settled on this is that portlets are meant to be relatively small pieces of functionality that can be used in combination with each other (possibly using inter portlet communication) to provide larger functionality. This means there will usually only be limited navigation needed in a portlet between only a small number of pages, which lets us get away with this hack without compromising the normal AngularJS workings and development speed too much.
<div id="<portlet:namespace />main" ng-controller="MainCtrl" ng-cloak><div ng-include src="page"></div></div>
In your Javascript code you only need to make sure that this page field on your model is initialized on time with your starting page and changed on navigation actions. We can easily keep partial HTML pieces as separate files in our source, by placing them in the webapp/partials directory of our Maven project, and reference them using a portlet resource URL. Constructing such a resource URL can be done using the liferay-portlet-url Javascript service that Liferay provides.
var resourceURL = Liferay.PortletURL.createRenderURL();resourceURL.setPortletId(pid);resourceURL.setPortletMode('view');resourceURL.setWindowState('exclusive');resourceURL.setParameter('jspPage', '/partials/list.html');$scope.page = resourceURL.toString();
This code can be easily moved to an AngularJS service and switching pages is as simple as calling a function using the ng-click attribute, calling the service with the correct parameters and assigning the result to the page field on the model. You can find the complete source code for this in the example portlet on GitHub.
<aui:script use="liferay-portlet-url,aui-base">
No REST for the wicked
Now that we are able to have multiple AngularJS portlets, with navigation, on a page, the next step is to try and work with REST (or REST like) services. These could be REST services that Liferay provides or simple services your portlet provides and that AngularJS can consume.
Liferay.Service('/bookmarksentry/get-group-entries',{groupId: Liferay.ThemeDisplay.getScopeGroupId(),start: -1,end: -1},function(obj) {// do something with result object});
<aui:script use="liferay-portlet-url,liferay-service,aui-base">
public class AngularPortlet extends MVCPortlet {public void serveResource(ResourceRequest resourceRequest, ResourceResponse resourceResponse) throws IOException, PortletException {String resourceId = resourceRequest.getResourceID();try {// check resourceID to see what code to execute, possibly using a parameter and return a JSON resultString paramValue = resourceRequest.getParameter("paramName");Gson gson = new Gson();String json = gson.toJson(result);resourceResponse.getWriter().print(json);} catch (Exception e) {LOGGER.error("Problem calling resource serving method for '" + resourceId + "'", e);throw new PortletException(e);}}}
var url = Liferay.PortletURL.createResourceURL();url.setResourceId('myResourceId');url.setPortletId(pid);url.setParameter("paramName", "paramValue");$http.get(url.toString()).success(function(data, status, headers, config) {// do something with the result data});
You promise?
When trying out these various REST services I quickly ran into problems regarding the asynchronous nature of Javascript and AngularJS. This is something that Primefaces has shielded me from most of the time, but that immediately turned out to be something that I needed to think about and apply rigorously in an AngularJS based portlet. Luckily this is a known 'problem' that has an elegant solution: promises. This means you just need to write and call your AngularJS service/factory in a certain way and all the necessary async magic will happen. For this we'll revisit the code we used to get bookmarks, this time also wrapped in a nice AngularJS factory in a separate Javascript file:
'use strict';angular.module("app.factories", []).factory('bookmarkFactory', function($q) {var getBookmarks = function() {var deferred = $q.defer();Liferay.Service('/bookmarksentry/get-group-entries',{groupId: Liferay.ThemeDisplay.getScopeGroupId(),start: -1,end: -1},function(obj) {deferred.resolve(obj);});return deferred.promise;};return {getBookmarks: getBookmarks};});
Using this pattern, creating a deferred result and returning a promise to it from our method, we've made are call asynchronous. Now we just need to call it correctly from our controller using the then syntax:
var module = angular.module(id, ["app.factories"]);module.controller("MainCtrl", ['$scope', 'bookmarkFactory',function($scope, bookmarkFactory) {bookmarkFactory.getBookmarks().then(function(data) {$scope.model.bookmarks = data;});}]);
Lost in translation
Now that the most important points have been tackled I wanted to move on to something that is pretty important in the country where I'm from: i18n. In Belgium we have 3 official languages, Dutch, French & German, and usually English is also thrown into the mix for good measure. So I wanted to find out to add i18n to my AngularJS portlet and while doing so see if I could make it into a custom directive. The current version of this simple directive uses an attribute and only allows retrieving a key without providing and replacing parameters in the value.
module.directive('i18n', function() {return {restrict: 'A',link: function(scope, element, attributes) {var message = Liferay.Language.get(attributes["i18n"]);element.html(message);}}});
The directive above uses the Liferay.Language Javascript module to retrieve the value of the given resource key from the portlets language bundles and sets this as the value of the tag on which the directive is used. To be able to use this Liferay Javascript module we'll again need to add something to the use attribute of the aui:script tag to make sure it is loaded and available: liferay-language. Once we have this directive using it in our HTML partials is pretty simple:
<h2 i18n="title"></h2>
This piece of HTML, containing our custom i18n directive, will try to retrieve the value of the title key from the portlet's resource bundles that are defined in the portlet.xml. The important word in the previous sentence is try, because it seems there's a couple of bugs in Liferay (LPS-16513 & LPS-14664) which causes the Liferay.Language Javascript module to not use the portlet's resource bundles, but only the global ones. Luckily there is a simple hack to will allow us to still make this work: add a liferay-hook.xml file to the portlet and use it to configure Liferay to extend its own language bundles with our portlet's.
<?xml version="1.0"?><!DOCTYPE hook PUBLIC "-//Liferay//DTD Hook 6.2.0//EN" "http://www.liferay.com/dtd/liferay-hook_6_2_0.dtd"><hook><language-properties>Language.properties</language-properties></hook>
Can you hear me?
After taking all the previous hurdles you end up with a pretty usable portlet already, but the last thing I wanted to try was to get AngularJS portlets to talk to each other using IPC (Inter Portlet Communication) or something similar. As AngularJS is a Javascript framework the obvious choice would be to use the Liferay Javascript event system: Liferay.on & Liferay.fire. Integrating this in my controller turned out to be not as straightforward as I expected, but once I threw promises and $timeout into the mix I got the result I expected at the beginning. To fire an event just use the following in your code:
Liferay.fire('reloadBookmarks', { portletId: $scope.portletId });
You can use any event name you like, reloadBookmarks in our case. or even had multiple events. We also pass in the portletId ourself as this doesn't seem to be in the event data by default anymore and it can be useful to filter out unwanted events on the portlet that is the originator of the event. This event data can be basically any Javascript/JSON object you want.
Liferay.on('reloadBookmarks', function(event) {if (event.portletId != $scope.portletId) {$timeout(function() {bookmarkFactory.getBookmarks().then(function(bookmarks) {$scope.model.bookmarks = bookmarks;});});}});
Conclusion
It took some time and a lot of Googling and trying out stuff, but in the end I can say I got AngularJS to work pretty good in a portal environment. It is usable, but for someone that is used to spending most of his time in Java and not Javascript it was quite a challenge, especially when something fails. The error messages coming from Javascript aren't always as clear as I'd want them to be and the IDE support (code colouring, code completion, debugging, ...) isn't up to what I'm used to when writing JSF/Spring based portlets. But I think the following image expresses my feelings pretty good:

More blogs on Liferay and Java via http://blogs.aca-it.be.

