ACA Blog

ACA Blog


July 2017
M T W T F S S
« May    
 12
3456789
10111213141516
17181920212223
24252627282930
31  

Categories


More Angular Adventures in Liferay Land 16 Mar 2015

Jan EerdekensJan Eerdekens

Last year I spent a lot of time looking into how AngularJS could be used in a portal environment, more specifically Liferay. That research culminated in a pretty technical and quite lengthy blog post: Angular Adventures in Liferay Land.

The problem with a blog post that you invest such an amount of time in, is that it always remains in the back of your head, especially if there were some things that you couldn’t quite get to work like you wanted to. So like a lot of developers I keep tinkering with the portlet, trying to make it better, trying to solve some problems that weren’t solved to my liking.

iakiq

So after a couple of months I ended up with a better portlet and enough material for a new blog post. In this second Angular/Liferay blog post I will address a couple of things:

You can still find the example portlet on Github: angular-portlet

From here to there: the right way

During the testing I did for the previous post I wasn’t able to get the widely used Angular UI Router module to work. I looked at a number of alternative modules, but no matter what I tried, I couldn’t get any of them to work. So in the end I had to resort to a very ugly workaround that (mis)used ng-include to provide a rudimentary page switching capability. While it works and kinda does the job for a small portlet, it didn’t feel right. During a consulting job at a customer that wanted to look into using AngularJS in a portal environment, the routing problem was one of the things we looked into together and managed to get working this time.

To start off you need to download the UI Router JS file, add it to the project and load it using the liferay-portlet.xml:

After this it takes a couple of small, but important, changes to get similar UI Router code that I tried during the first tests, to work this time. Like I expected previously the UI Router $locationProvider needs to be put into HTML5 mode so it doesn’t try to mess around with our precious portal URLs. This mode makes sure it doesn’t try to change the URL, but it will still add a # to the URL sometimes. To counter this you also need to slightly tweak the $urlRouterProvider so it uses ‘/’ as the otherwise option, something you also need to set on the url property of your base state (but not on the others):

After these changes you’ll be then able to use a pretty default $stateProvider definition, where we use the templateUrlfield to define the page that is linked to the state. These changes alone still won’t make the routing work. We still need another small change to Liferay to make the HTML5 mode work as this needs a correctly set base href in the HTML. This can be achieved in multiple ways in Liferay: a JSP override hook, a theme, … . To keep everything nicely encapsulated in this portlet, I’ve chosen to use a simple JSP override in the portlet itself and not in a separate hook.

An override of /html/common/themes/top_head.jsp will enable you to add the required <base href=’/’> tag on a global level to the portal HTML code:

Just add this file to your portlet, with the correct subdirectories, in /src/main/webapp/custom_jsps and configure this directory in your liferay-hook.xml:

With all this set up we now just need one more small change to tie it all together. The state configuration we currently have still doesn’t take into account the fact that we’re on a portal and need to use special URLs. Luckily the UI Router module has got us covered and provides a nice event, $stateChangeStart, that we can use to mess around with the URLs. We’ll use this event to detect if the normal URL, used in the state, has already been adapted for portal use or not. If not we’ll create a correct portal render URL based on the given template URL.

lines

Now all the backend routing machinery is correctly set up, you can use the normal UI Router stuff on the frontend. You’ll need to add the ui-view attribute to your base div in view.jsp:

and then use the ui-sref attribute on your anchor tags to navigate:

Found in translation

In the previous post this section was called Lost in translation and as yuchi pointed out in a Github issue he created for the example portlet Liferay.Language.get is synchronous and should be avoided when possible. So it seemed I was still a little bit lost in translation and needed to find a better solution. I think I might have found one in the Angular Translate module made by Pascal Precht. This looked like a great and more importantly configurable module that I might be able to get to work in a portal context.

To start off you need to download the Angular Translate JS file (and also the loader-url file), add it to the project and load it using the liferay-portlet.xml:

By default it tries to find JSON files, that contain the translation key/value pairs, with certain URLs. So you can probably guess that by default this won’t work. Luckily the module has all kinds of extensibility features built in and with a custom UrlLoader I was able to convince it to use my own custom language bundles:

As you can see we just configure the $translateProvider to load the translations from a custom resource URL and use the current Liferay language ID as the preferred language. I also had to transform my Liferay URL factory, from the first version of the portlet, to a provider because you can’t use factories/services in an Angular config section.

Using the resource URL mechanism from the first version of this portlet we can, with some classloader magic, output a JSON that represent a complete Liferay resource bundle (including extensions from hooks) for a given language:

This code uses custom @Resource and @CacheResource annotations, which aren’t specifically needed as a normalserveResource method will also do the trick, but these annotations make it just a little easier (and allow easy caching). More information about these annotations can be found in the Github code itself (in the/be/aca/liferay/angular/portlet/resource package).

With this new setup we can completely drop the custom translation directive we used in the previous version of the portlet. You now just need to add an empty translate attribute on any tag you want to translate (other ways are also possible and can be found in the Angular Translate docs). Adding this attribute to a tag will use the value between the tags as the key to translate:

The importance of being valid (in any language)

After getting routing and translations to work I discovered another piece of functionality that was missing from the example portlet: validation. After looking around for Angular validation modules I settled on the Angular auto validate module by Jon Samwell. This seemed to be a pretty non intrusive and easy to use way to add validation to an Angular app. You just need to add a novalidate and ng-submit attribute to your form, some required, ng-maxlength, etc… attributes to your input fields and you’re already done on the HTML side:

On the Javascript side of the code you need to add is also pretty minimal. Download the correct jcs-auto-validate.js file, add it to the project and load it using liferay-portlet.xml:

Now that we have working validation it would be nice if the validation messages would be correctly translated using our custom language bundles instead of the ones bundled with the Javascript module itself. Using a custom ErrorMessageResolver, that uses the Angular Translate module’s service we’ve already used in the previous section, this can be easily achieved:

You just need to add this custom resolver to the validator like this:

Breaking up

In the previous version of the portlet most of the Javascript code was in a limited number of files that, with all the additional changes from this post, would’ve gotten pretty long. To keep everything short and sweet, we need to split up the existing files into smaller ones in a way that makes them easy to manage. In the Javascript world there are tools like Grunt that can do this, but they’re not easy to use in a Maven based project. After looking around I settled on theWRO4J Maven plugin. This enables you to easily merge multiple Javascript (or CSS, …) files into one, but it can also do other stuff like minimize them etc… .

The first thing we need to do to make this work is add some stuff to the build plugins section of our pom.xml:

The first plugin will make sure non of the original, split up, JS files, will be packaged in the WAR while the second will actually merge the different files, using a wro.groovy definition file in the WEB-INF directory, and make sure they are placed in the correct location in the target directory before the actual WAR is made.

Now we just need to refer to the merged files this Maven plugin will create during packaging in the liferay-portlet.xml:

For each of the groups in the Groovy definition file we need to provide a small Init.js file that is used to define a module with a name and the [] notation (so that an empty module is declared). Below is an example for the controllers that uses app.controllers as the module name:
IInit.js

This way you can then easily define an actual controller using by just referring to the declared module name, without redeclaring it:

You can then refer to this module in the bootstrap of your Angular app as follows:

The same mechanism can be used for factories, services, directives, … .

A full Eclipse

One of the people that contacted me after the first blog post was Gregory Amerson. Greg works for Liferay and is the main developer of Liferay IDE (based on Eclipse). He tried out my portlet in Liferay IDE together with an Eclipse AngularJS plugin and encountered a couple of small problems: calling a function delete is problematic, some additional dependencies are needed for full taglib support, etc… . The most important changes discovered during his tests are merged back into this new version of the portlet. I also tried out Liferay IDE myself and I must say the HTML/JS/Angular support has been markedly improved, but in the end I still like my IntelliJ better.

Conclusion

With the routing module working now, better internationalisation, validation and JS file merging, AngularJS is starting to look more and more as a tool that a portal developer could add to his toolbelt and be productive with.

9gy89x

Blog written by Jan Eerdekens

jan2

Liferay Expert at ACA IT-Solutions

Interested in joining our team?

Interested in meeting one of our team members? Interested in joining our team?
We are always looking for new motivated professionals to join the ACA team!
Have a look at our new ACA job website: http://www.aca-it.be/jobs

we-are-hiring

Belgian Java and Liferay developer with some weird interests, eternal complainer, atheist, skeptic and geocacher.

Leave a Reply

2 Comments on "More Angular Adventures in Liferay Land 16 Mar 2015"

Notify of
avatar
Sort by:   newest | oldest | most voted
Phil
Guest

Thanks for this very nice article. Interested to know whether this approach would play happily on Liferay 7.0?

wpDiscuz