Using AngularJS with Liferay
Asked Answered
H

3

7

I would use global AngularJS with Liferay Portal. Because, like the devise of AngularJS:

Why AngularJS? HTML is great for declaring static documents, but it falters when we try to use it for declaring dynamic views in web-applications. AngularJS lets you extend HTML vocabulary for your application. The resulting environment is extraordinarily expressive, readable, and quick to develop.

I would simple use the declarative syntax of html by developing of Liferay-Theme and Portlets.

For this requirement I have created new Liferay-Theme and customized a little bit the portal_normal.vm:

<!DOCTYPE html>

<#include init />

<html ... ng-app="liferay">
<head>
        ...
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.8/angular.min.js"></script>
    <script type="text/javascript" src="${javascript_folder}/my.js" charset="utf-8"></script>
</head>
<body class="${css_class}">
    <div ng-controller="LiferayCtrl">

here my.js:

angular.module('liferay', [])

.controller('LiferayCtrl', function($scope) {
    console.log("---==== Init AngularJS ====---");
    $scope.Liferay = Liferay;
});

and I can extend the controller, like getting of Liferay-Site name.

What's all this in aid of?

Hereby I can simple access Liferay JavaScript values & functions over declarative html syntax, without direct JavaScript function calling, like AngularJS way.

E.g. now it is possible to get values and functions of Liferay JavaScript by declarative html code, like here, for getting current url in web content display:

Liferay current URL: {{Liferay.currentURL}}

enter image description here

However, my questions are:

  • Which side-effects could happen by using AngularJS global in Liferay?
  • Could it get performance issues?
  • Conflicts with other JavaSripts e.g. Alloy?
  • Using of AngularJS inside of portlets?
Hutment answered 15/1, 2014 at 14:50 Comment(0)
D
19

First of all: nice idea using AngularJS on the portal itself. Until now we are using AngularJS only for portlets – I’ll explain later why and how.

Possible side-effects and conflicts

There are not more side-effects using AngularJS as with other JavaScript libraries or frameworks. Liferay is shipped with AlloyUi (http://alloyui.com/). This JavaScript library is based on YUI (http://yuilibrary.com/) written by yahoo. YUI uses a different namespace than jQuery, so you can use jQuery along with AlloyUI. And if you are able to use jQuery without any side-effects you may use AngularJS even more, because AngularJS provides a subset of jQuery.

All you have to make sure is that you include jQuery before AngularJS if you need the full jQuery not only the jqLite implementation of AngularJS. This can easily be done by changing the portal_normal.vm in your theme:

<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>$the_title - $company_name</title>
    <script type="text/javascript" src="$javascript_folder/jquery-2.0.3.min.js" charset="utf-8"></script>
    <script type="text/javascript" src="$javascript_folder/angular-1.2.7.min.js" charset="utf-8"></script>
    $theme.include($top_head_include)
</head>

If you include the JavaScript files on the theme level you are able to use different versions for each theme. This is very handy if you have more than one portal instance and you don’t have the chance to use the same version in each instance.

Performance

It depends. One has to know, that Angular will parse the whole web page if you put your ng-app directive on the html element, as in your example. If your page is very big and has a lot of content it could be a performance problem. So it is better put the ngApp directive somewhat down in your page and initialize the ngApp manually by:

$().ready(function() {
    angular.bootstrap("css-selector", ['yourApp']);
});

Using AngularJS inside portlets

There are two ways to achieve this.

1) On big ngApp as in your example. You have to know, that you can’t nest ngApps. So every portlet must be part of your app. With this approach you will lost the possibility that a portlet can provide an individual part to your page. All portlets need to know what the name of your ngApp is and must extend this ngApp. You may not use these portlets without changes in other portal server if they use another ngApp name or no one. The advantage is you can share the $rootScope in all of your portlets and the portlets may communicate with each other in angular ways (e.g. $emit, $on, shared servcies, ...).

2) Every portlet comes with its own ngApp. In this case there are no dependencies. Every portlet may instantiate its own ngApp by angular.bootstrap. Furthermore every ngApp is created only for a small part of the web page and only if it’s really needed.

In both ways you should avoid using routing. Because only one routeProvider can be used per page.

We have decided to use the second approach so that the portlets keep individual and are not depending on a global ngApp.

Helpful hints – I hope

Portlet configuration

You know you can configure your portlets. So the question comes up: “how do i provide my portlet preferences to my angular portlet?” For sure you could make an http request to get them. But this is not sufficient, because you make an unneeded call. Your portlets are usually java server pages so it would be nice to include the preferences during generation time on the server and provide these information to your angular app. But how?

In the doView method of your portlet you may create a JSON-object and store it under a key in your RenderRequest:

public void doView(RenderRequest renderRequest, RenderResponse renderResponse) throws IOException, PortletException {
    PortletPreferences prefs = renderRequest.getPreferences();

    Map<String, Object> values = new HashMap<String, Object>();
    values.put(key, value);

    renderRequest.setAttribute("config", new ObjectMapper().writeValueAsString(values));

    super.doView(renderRequest, renderResponse);
}

In your jsp you are able to access this config attribute:

<div class="hidden" portlet-config>${config}</div>

portlet-config is a directive that parses the JSON and stores the information in a service instance:

.factory('portletConfigService', function(){ return {}})

.directive('portletConfig', ['portletConfigService', function(portletConfigService) {
    return {
        restrict: 'A',
        compile: function(elem, attrs) {
            var config = angular.fromJson(elem.text());
            angular.forEach(config, function(value, key){
                portletConfigService[key] = value;
            });
        }
    };
}])

Now you can inject the portletConfigService to every controller, service, factory or whatever and make use of the config parameter: portletConfigService.key.

Language properties

Another problem we came around were the language properties. Normally you would define them in a language.properties file and make use of them in your jsp. For example through fmt:message tags. But you also want to be able to access them in your angular js app. And what you absolutely not want is to have them twice in your sources. We have solved this problem by a generated service and a directive that reads the properties for the current users language and creates the angular stuff in a jsp :

<%@page contentType="text/javascript; charset=UTF-8" %>
<%@ page import="java.util.ResourceBundle" %>
<%@ page import="java.util.Locale" %>
<%@ page import="java.util.Enumeration" %>
<%@ page import="org.apache.commons.lang.StringEscapeUtils" %>
angular.module('translations', [])
.factory('translations', function(){ return {
<%
    ResourceBundle labels =  ResourceBundle.getBundle("language", request.getLocale());
    Enumeration<String> keys = labels.getKeys();
    while(keys.hasMoreElements()){
        String key = keys.nextElement();
        out.write("\""+key+"\":\""+StringEscapeUtils.escapeJavaScript(labels.getString(key))+"\"");
        if(keys.hasMoreElements()){
            out.write(",\n");
        }
    }
%>
}}) 

.filter('mpbtranslate', ['translations', function(translations) { 
    return function(input) { 
        var translation = translations[input];
        return translation? translation: '???'+input+'???'; 
    }; 
}]);

You can now use these translations as a service and as a filter. For example <div>{{'title' | translate}}</div> will for example be evaluated to ‘Titel’ on the client side.

I hope you got some useful information regarding your concerns.

Davidadavidde answered 20/1, 2014 at 14:30 Comment(1)
What's about a using theme for developing SPA page inside Liferay?Schwa
A
1

Quite a broad range of questions, let me try to answer to the best of my knowledge.

Side effects for having multiple JS libraries (e.g. AlloyUI and AngularJS) are typically:

  • higher load times
  • possible conflicts

AlloyUI is quite good in namespacing (due to the work of the underlying YUI), so the odds that there will be actual conflicts is quite low. However, nobody will guarantee you that there never will be conflicts, you'll have to test this yourself. (I believe you're pretty safe in this case)

Higher load times might be what you ask when you're asking for performance issues. My counter question to this is: What performance are you interested in:

  • Sub-second load times on first visit (will benefit from fewer files being requested per page)
  • Sub-second load times on repeat visit (can be mitigated through aggressive caching of JS and other secondary referenced files)
  • Time until the page is fully displayed (e.g. when JS is used to render the page after it has been fully loaded)
  • Development time/performance, largely depending on your (team's) experience with the technology in use
  • Number of concurrent users your server can handle (this might be influenced by the number of requests that you will fire towards your server. Sometimes the use of Ajax will lower server load, sometimes it will raise the load)
  • Browser memory consumption (might be a problem in mobile devices)
  • Amount of data transferred to the browser (might be a problem in mobile devices or with low-bandwidth connections, but might be mitigated by aggressive caching for repeat visits)

To phrase it differently: With regards to performance, I'm afraid, you'll have to measure for yourself.

There's no problem that I know of using AngularJS in your portlets. Especially when your portlets can rely on the library to already being loaded (instead of loading it themselves) this should be pretty safe. One drawback that I can think of (but that's natural for all portlets) is that you're best of if all portlets rely on using the same exact version of all available libraries. This also means to test/upgrade all your portlets at the time you update the version of AngularJS that you're including in your theme.

Phew, quite a lot. I'm sorry that this could not be a more black&white answer, you'll have to judge for yourself. Hopefully this reasoning helps

Asphalt answered 16/1, 2014 at 9:5 Comment(0)
S
1

Hope this still helps; The comments mentioned before, I think, are dead on. There is probably no name spacing issues and about performance it could change from implementation to implementation.

Regarding about using AngularJS within portlets or globally here's my input:

I've seen a lot of examples using AngularJS within a portlet. I've tried some basic examples and it does leverage on how easy it is to set it up and self contained configurations.

Now, talking about using AngularJS globally. I think this is an interesting way and I'm currently trying to implement this. It is quite different since, at least my approach, will let everything about the view on AngularJS and not on the portlet. To use this with Liferay I leverage from the Delegator servlet which lets you use Liferay's API. Basically you can setup multiple URL mappings to different classes. I always return a JSON object with these calls. The basic idea would be like this: - Global AngularJS app. - Use AngularJS's templating to create the entire view. - Use Ajax calls to delegator portlet mapping to a custom class, which will return a JSON object. - Use AngularJS Controllers to get the data from the Delegator's calls.

I mentioned this is somewhat different, and that is because Liferay would basically only be a service provider and the entire rendering will be left to AngularJS. This also seems more on the AngularJS's nature.

Subliminal answered 3/3, 2015 at 18:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.