Saturday, 23 February 2013

Integration between PrimeFaces and AngularJS

Introduction

In one of my previous blog items, I explained the similarity between JSF and AngularJS.  But you can go even a step further.  It is possible to create a single page app where you combine both frameworks and let them work together.
Not that this will be an immediate production situation, but it gave me the opportunity to explore the very nice JavaScript support of PrimeFaces.

Overview

This is how the application looks like
primeFacesAngular
On the top, you have the AngularJS application. It retrieves the list of persons from a REST service that receive the data from a CDI injected service.
Clicking on the edit button, calls a Java method on a JSF Managed bean. This can be achieved by using the remoteCommand tag of PrimeFaces.
This click also triggers a partial refresh of the JSF part and shows the person information to update. The update button posts the person information to the server. Within the actionListener, we can define the JavaScript method that needs to be executed when the AJAX call is finished by using the RequestContext class of PrimeFaces.
This JavaScript method calls an AngularJS scope function that reads the person list again from the server.  We receive now the updated information and thus the circle is round.

PrimeFaces for JavaScript

From browser to server.

PrimeFaces has the remoteCommand tag which enables executing backing bean methods and do partial update triggered by custom client side script. In our example it is defined as follows
 
<p:remoteCommand name="selectPersonJSF" actionListener="#{personView.selectPerson}" update="selectedPerson" id="cmd"/>

For the client side aspect, the name attribute is the important part. It defines the name of the global function that results in calling the Java method indicated by the actionListener attribute.

Since it is a global function, we can call it without any problem from a function within an AngularJS scope, like this.
 
 $scope.selectPerson = function (person) {
        selectPersonJSF([
            {name: 'selectedPersonId', value: person.id}
        ]);
    };

We can even supply some parameters, in our case the id of the selected person. For the retrieval of the parameter, we have to do some special lookup in the request parameters.
 
 public void selectPerson(ActionEvent actionEvent) {
        Map<String, String> parameterMap = FacesContext.getCurrentInstance().getExternalContext()
                .getRequestParameterMap();
        long selectedId = Long.valueOf(parameterMap.get("selectedPersonId"));
        selectedPerson = personService.getPersonById(selectedId);
    }

The last piece of the puzzle is the update attribute which defines which JSF part must be rerendered.

So basically, the remoteCommand executes an AJAX request, like the commandButton. You can post data to the server and rerender a part of the page. But is not triggered by an element in the page but by a JavaScript call.

From server to Browser


When we have edited the person information, we click on the update button. It is an AJAX post to the server and we execute an actionListener on the JSF side. We can also update the information but we now need a way to trigger an update within the AngularJS application so that the new information is shown on the screen.

PrimeFaces has also for this kind of requirements a solution. There exists the class RequestContext where we can define the javaScript function that must be executed when the AJAX response is processed by the browser.

This is the actionListener implementation behind the update button
 
 public void updatePerson() {
        personService.updatePerson(selectedPerson);
        selectedPerson = null;
        RequestContext requestContext = RequestContext.getCurrentInstance();
        requestContext.execute("updatePersonList()");
    }

Now we have already a giant step towards a solution for our problem.  We are able to execute some global JavaScript function after the AJAX request/response associated with the commandButton is finished.

Calling a function within an AngularJS scope is not that difficult, you find it very quick when you google for it


function updatePersonList() {
        var _scope  = angular.element($('#panelTitle')).scope();
        _scope.updateList();
}

panelTitle is the element, in my case the DIV, where we have defined the ng-controller directive.  This is also the element where the AngularJS scope is defined. This is the function definition:
 
 $scope.updateList = function () {
        $scope.$apply( function() {
            $scope.data = PersonList.query();
        });
    };

$scope.$apply is needed to trigger an update of the page as a result of updated data.  This is needed because we update the data outside the normal processing flow of AngularJS.

Conclusion


Probably, there will never be a production app that needs the combination of JSF/PrimeFaces and AngularJS.

For me, it was the opportunity to learn the possiblities of the JavaScript features provided by PrimeFaces and the integretion of AngularJS with another trigger.

The code can be found here and it will run on a JBoss 7.1 application server.

3 comments:

  1. Some time ago I implemented a similar functionality in AngularFaces. There's a dedicated component, a:sync, allowing you to exchange data between client and server. I didn't try it yet, but it ought to be possible to write a nice AngularPrime application with decent JSF support using AngularFaces as a bridge between the two worlds. Have a look at my web site, http://www.beyondjava.net/blog/angularfaces-synchronize-angular-models-jsf-beans/, to learn more.

    Regards,
    Stephan

    ReplyDelete
    Replies
    1. Hi Stephan,

      I think also that those kind of things are possible. I'm experimenting already with integrating AngularPrime with CDI beans (through REST) but i'll give the a:sync component a try in the near future.

      Thanks for the comment.
      Regards
      Rudy

      Delete
  2. Hi please let me know how to convert following code in Angularfaces.

    ReplyDelete