ACA Blog

ACA Blog

June 2020
« May    


How to pass Liferay ThemeDisplay in Angular apps

Koen OlaertsKoen Olaerts

For a recent project, we developed an Angular application that uses Liferay DXP as the back-end. In this Angular application, we regularly send requests into the Portal back-end for User information, Journal Articles and so on. Mostly, this works great because we’ve defined several custom REST endpoints in DXP that allow us to make use of the Liferay services and other nifty Liferay stuff such as the Liferay Themedisplay. Schematically, it looks like this:

Schematic representation of an Angular application that uses Liferay DXP as the back end

However, at one point we encountered an issue where one of our endpoints did not return the expected Journal Articles and instead threw an exception underneath. In this blog post, we’ll explain why that occurred and how we fixed it.

Fixing the Audience Targeting rules

After a short investigation, we determined that the Audience Targeting rules were not being applied properly to the Journal Articles. The underlying cause was that several Audience Targeting rules make use of the Liferay ThemeDisplay object. This is one of the standard Liferay objects available in each portlet request as an attribute under the logical name themeDisplay. You can retrieve it by using the following code:

I mentioned before we make use of REST endpoints and thus not of portlet requests. These REST requests are sent from our Angular front-end to custom endpoints in a Liferay environment, so-called controllers. Below is an example to retrieve some news articles. The first block of code is taken from our Angular service that makes the request. The second block of code is from the Java controller which responds to the request and returns news articles.

The problem is that REST requests don't carry a Liferay ThemeDisplay object.The problem is that these REST requests don’t carry a ThemeDisplay object. Because of this, the list of userSegments in the example above cannot be constructed correctly. After all, the rules can’t properly determine whether or not a user belongs to a certain segment. So some way or another, we needed to pass the ThemeDisplay object from the front-end to the back-end through the custom REST requests.

We contacted Liferay support using a LESA ticket to get this straightened out. With their help, it became clear we needed to add the ThemeDisplay object manually to the REST request. Following the standard Liferay JSON WS documentation, it should be possible as follows:

The solution is always in the code

As is mostly the case when in doubt with how to achieve something in Liferay Themedisplay: check the source code!As is mostly the case when in doubt with how to achieve something in Liferay: check the source code! In the default Liferay JSON WS, the ThemeDisplay object is actually passed as a form parameter from the front-end to the back-end. This also requisites that the request is actually a POST request. In the back-end, there are subsequent servlet filters that operate on these requests. One of these filters transforms the themeDisplay JSON string into a Java object and adds it as a request attribute. In other words: we needed to make adjustments in both ends.

Adjustments in the front-end

There are several ways to pass the ThemeDisplay object from the front-end. To prevent much changes to our existing code base and because it is a fast & easy manner, we chose to pass the ThemeDisplay object as a header in the REST requests. This is very convenient because we can now add a plain JavaScript object, just as in the Liferay JSON WS examples. So we only need to construct a JSON string with all the necessary fields and their values.

You can make use of the Liferay.ThemeDisplay object which Liferay provides if you are still in a Liferay environment. However, you cannot use this object itself as the value. It consists of functions and if those are parsed to a string, they just become null. But you can make use of the Liferay.ThemeDisplay object to populate your own object. Check out the following examples.

To limit to duplication of code we also made use of an Interceptor. This is an Angular component that will inspect every HTTP request and potentially transform it. As the ThemeDisplay object isn’t required for each and every request but solely to those where it’s actually necessary for our implementation, we added a small url validation.

So in our Angular app we added below themedisplay.interceptor.ts component:

Fixing the back-end with JSONFactoryUtil

When the ThemeDisplay is passed as a string as in above example, it enters the back-end as a header on the HttpServletRequest. There are two issues that need to be solved here:

Thankfully this can be resolved without much effort. Get the String header from the request, convert it to a ThemeDisplay object and add it back to the request under the correct attribute. The conversion seems to be the most complex issue. Luckily, Liferay has a lot of useful utilities and in this case it would be the JSONFactoryUtil. Using this utility, you can easily transform a JSON String to a certain typed object. The method below executes all these actions at once:

The isEmpty check in above code is necessary to prevent any NullPointerExceptions in case there isn’t be a themeDisplay header present. We used this method in a custom ServletFilter so it can be executed on all requests without changing any of the existing controllers. By providing url-patterns in the @Component definition, it is also possible to just change those requests deemed necessary:

There it is… the solution!

By performing the necessary front- and back-end changes, we can now make proper use of Audience Targeting rules in the controllers for our Angular application. In the front-end, we intercept the desired REST requests and added a header with the ThemeDisplay (JSON) String. Just adding the necessary fields for the Audience Targeting rules to this object is enough.

In the back-end, we retrieve this String from the headers, deserialize it to an actual ThemeDisplay object and add it to the request attributes under the WebKeys.THEME_DISPLAY name. All this occurs in a ServletFilter that only responds to the desired URL patterns.

At the moment, it’s not possible to directly pass the entire Liferay.ThemeDisplay object that Liferay provides OOTB. This object does not contain any field with values but is constructed out of functions, so we need to construct the ThemeDisplay object ourselves. By using an Angular Interceptor and a Liferay Servlet Filter, any request can be updated transparently without touching too much code.

We hope we’ve helped you out! If you have any additional questions or remarks, feel free to comment below. 😊