Adding Angular 2 to an existing ADF application
Reality sometimes can be a bit surprising. The desire to build a new module using Angular 2 on top of an existing ADF 12.1.3 application for example probably wouldn’t be the first thing you would expect. But hey, with AMIS’ strong background in Oracle technology and a group of skilled WebDev consultants, why not?
What?
Recently I got involved in a project, or rather, product, that has been developed over the past decade or so. It’s a financial planning tool, up until now completely built in Oracle ADF. If the combination of crunching numbers and ADF doesn’t get your motor running, don’t panic. In came a request to add a new module, a sort of dashboard if you like. The purpose of this dashboard is to make data glance-able and provide some geolocation data. The numbers contained in this product are all about development (the sort that’s done with bricks and mortar, rather than IDE’s and compilers). So it kind of makes sense to show where this development is happening, doesn’t it?
Even though at this moment there are no intentions to completely rewrite the existing application into a modern JS SPA, there was a desire to have some experience with this kind of technology. Knowledge and experience that could be used for future reference, upcoming projects etc. So that’s where the wish to use the latest and greatest Angular 2 (currently in release candidate 1 as of this writing) framework came from.
Another requirement: the solution to be built should be able to run on the current WebLogic / ADF 12.1.3 environment without installing any extra stuff and use the existing database connections.
Why?
Even without hacking your webcam, I can see a big question mark in your eyes. Why would anyone possibly want to combine ADF and Angular 2? If starting from scratch on an all new project, you probably wouldn’t. But what about all those existing applications? There are a number of reasons why one would want to mix in a new technology to co-exist with the current stack.
One particular reason that comes to mind could be the desire to create a fully custom UI with great flexibility and usability. Web components and Google’s Material Design for instance can be great ways to achieve just that.
How?
As explained by my colleague Cindy Berghuizen, we decided to use Polymer in conjunction with Angular 2. If you are short on time and don’t want to read that post (again), the main reason for doing so is the fact Polymer has some great UI components whereas Angular 2 -being in its early days- is still lacking a bit in this regard. Google’s Material Design for Angular 2 is currently in alpha phase, and the feature set isn’t quite there yet.
The dashboard we’re talking about today uses a couple of Paper elements to spice up the UI. Data is loaded through a restful service built on top of the ADF business components. Unfortunately, upgrading to ADF 12.2.x was not an option, so exposing ADF BCs as a restful webservice doesn’t come out of the box. Instead, this is accomplished by utilizing JAX-RS annotations, like so:
package nl.amis.ngadf.rest.model; import java.math.BigDecimal; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import oracle.jbo.ApplicationModule; import oracle.jbo.Key; import oracle.jbo.Row; import oracle.jbo.ViewObject; import oracle.jbo.client.Configuration; @Path("projects") @Consumes("application/json") @Produces("application/json") public class ProjectsResource { private static final String amDef = "nl.amis.ngadf.model.DemoAM"; private static final String config = "DemoAMLocal"; public ProjectsResource() {} @GET @Path("/{id}") public Project getById(@PathParam("id") Integer id) { ApplicationModule am = Configuration.createRootApplicationModule(amDef, config); ViewObject vo = am.findViewObject("PjtVO"); Project project = null; Key key = new Key(new Object[] { id }); Row[] rowsFound = vo.findByKey(key, 1); if (rowsFound != null && rowsFound.length > 0) { Row row = rowsFound[0]; project = new Project(); project.setId((Long) row.getAttribute("Id")); project.setNaam((String) row.getAttribute("Name")); project.setOrganisatieonderdeel((String) row.getAttribute("DeptName")); project.setPath((String) row.getAttribute("Coordinates")); } Configuration.releaseRootApplicationModule(am, true); return project; } @GET public Projects findAll() { ApplicationModule am = Configuration.createRootApplicationModule(amDef, config); ViewObject vo = am.findViewObject("PjtVO"); vo.executeQuery(); Projects projects = new Projects(); while (vo.hasNext()) { Row row = vo.next(); Project project = new Project(); project.setId((Long) row.getAttribute("Id")); project.setName((String) row.getAttribute("Name")); project.setDepartement((String) row.getAttribute("DeptName")); project.setPath((String) row.getAttribute("Coordinates")); projects.addProjects(project); } Configuration.releaseRootApplicationModule(am, true); return projects; } }
Source snippet 1: ProjectsResource.java, building block for a JAX-RS webservice
What this does is get hold of a view object through ADF’s application module, store the row (in this case the database view only returns one row) into a POJO called Project, adds it to POJO called Projects (which contains a List<Project>) and return it as a JSON service. Project and Projects both are annotated with JAX-RS annotations @XmlRootElement and @XmlElement.
To be able to access this restful service, you have to configure the Java EE Application and set a useful web context root.
If you are, like I was, testing the Angular application on a different webserver (in my case, localhost:8080 instead of localhost:7101), one thing to note is that you have to be sure you configure CORS (Cross-Origin-Resource-Filters). Otherwise your Angular application won’t be allowed to access the restful service, technically running on another server which is potentially evil.
Now we have a restful service running on our weblogic server, CORS configured and ready to rock and roll, we only need something to consume the service. Enter ProjectService.ts. This Angular2 service is responsible for loading all projects from the restful service created before. The service exposes a RxJS Observable. Everyone who wants to know something about the latest projects can subscribe to the Observable. The observer design pattern provides a mechanism for push-based notification.
downloadProjectDetails(id: number) { this._activityIndicatorService.setLoadingIndicatorFin(true); let url:string = 'http://localhost:7101/service/resources/projectdetails/' + id; let projectDetails:ProjectDetails; this.http.request(url) .subscribe( data => { this.data = data.json(); projectDetails = new ProjectDetails(this.data); }, err => { console.error('Error in Get project details: ' + err); this._activityIndicatorService.setLoadingIndicatorFin(false); }, () => { this._activityIndicatorService.setLoadingIndicatorFin(false); this.currentProjectDetails.next(projectDetails); } ); }
What about that other requirement? Of course, consisting of HTML, (client side) Javascript and CSS, there is no reason this application wouldn’t be able to be deployed on the weblogic server. But just how to go about it?
The solution is to create a new project within your ADF application. Include your Angular application in a directory, let’s call it ‘dashboard’. Then, create a weblogic.xml file (File, New, Weblogic Deployment Descriptor). The final step is to configure Directories (‘Virtual Directory Mappings’) inside the deployment descriptor file. Point the VDM to the ‘dashboard’ directory and you are ready to deploy.
Your Angular front-end should then be reachable on http://[weblogic-ip-address]/dashboard, presuming you have an index.html file residing in the root.