Initially, I must commend the use of AngularJS within the portal itself, which is a novel idea. So far, our utilization of AngularJS has been limited to portlets - I will elaborate further on why and how.
Potential Ramifications and Clashes
When employing AngularJS, there are no more side effects compared to other JavaScript libraries or frameworks. Liferay comes equipped with AlloyUi (), a JavaScript library rooted in YUI () developed by Yahoo. YUI uses a distinct namespace from jQuery, enabling simultaneous usage of jQuery alongside AlloyUI. Therefore, if you can implement jQuery without any complications, incorporating AngularJS should pose no issues since AngularJS offers a subset of jQuery functionalities.
The only consideration is ensuring that jQuery is included before AngularJS, especially if you require the complete jQuery functionality rather than just the jqLite implementation provided by AngularJS. This adjustment can easily be made by modifying 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>
By integrating JavaScript files at the theme level, you have the flexibility to employ various versions for each theme. This feature proves advantageous when managing multiple portal instances where using the same version across all instances may not be feasible.
Performance Considerations
Performance outcomes vary depending on several factors. It is crucial to note that Angular will parse the entire web page if the ng-app
directive is placed on the html element, as demonstrated in your example. A large webpage with extensive content could potentially lead to performance issues. To mitigate this, it is advisable to place the ngApp directive lower on the page and initialize the app manually using:
$().ready(function() {
angular.bootstrap("css-selector", ['yourApp']);
});
Integration of AngularJS within Portlets
There are two methods to achieve this integration.
1) Employing one major ngApp akin to your example. However, nesting ngApps is not supported, necessitating every portlet to be part of the main app. With this approach, individual portlets lose the ability to offer unique contributions to the page. Furthermore, all portlets must align with the designated ngApp name and extend it. Modification is required for portlets when transitioning to alternative portal servers utilizing different ngApp names or none at all. The advantage lies in the ability to share $rootScope
among all portlets, facilitating inter-portlet communication through Angular mechanisms like $emit
, $on
, shared services, etc.
2) Each portlet features its own ngApp setup. This strategy eliminates dependencies between portlets, allowing each to instantiate its ngApp using angular.bootstrap
. Additionally, each ngApp is created solely for a specific segment of the webpage when necessary.
In either scenario, it is advisable to avoid using routing since only one routeProvider
per page is permissible.
We opted for the second method to maintain portlet independence without relying on a global ngApp.
Useful Suggestions
Portlet Configuration
For configuring portlets, considering how to pass portlet preferences to the angular portlet arises. While fetching preferences via HTTP request is an option, it involves unnecessary calls. Since portlets typically operate as Java server pages, embedding preferences during server-side generation and making these details available to the angular app would be more efficient.
Inside the doView
method of your portlet, construct a JSON object and store it under a key in the 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, access this config attribute:
<div class="hidden" portlet-config>${config}</div>
A custom portletConfig
directive parses the JSON and stores 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;
});
}
};
}])
Subsequently, inject the portletConfigService
into controllers, services, factories, or other components to utilize the configuration parameter: portletConfigService.key
.
Language Properties
Dealing with language properties presents another challenge, particularly in accessing them within the Angular JS app without redundancy in source code. We addressed this issue by generating a service and directive to fetch language properties based on the user's current language and integrate them seamlessly 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+'???';
};
}]);
Utilize these translations as both a service and filter within your application. For instance,
<div>{{'title' | translate}}</div>
will display 'Titel' on the client side.
This information aims to address your queries effectively and provide valuable insights for seamless implementation.