Building Angular Applications on Salesforce
Some technologies were just meant to be together — HTML and CSS, Node and Single Page Applications, and of course Angular and Salesforce. What? You’ve never thought of using a Single Page Application framework as the front-end experience for your Customer Relationship Management system? Well, allow me to take you on a journey of tweaks, tools, and tricks that will get your Angular app running inside a Salesforce VisualForce page.
Prerequisites
This guide assumes that you want to build an Angular app (Version 2.0 or later) and you have Salesforce org (version 39.0 or later) to deploy it to. It further assumes a basic working knowledge of Angular 2+ and Salesforce development.
Step 1 : Speaking in Salesforce
Before we can do anything really interesting we need to get our Angular app running ‘in’ Salesforce. What this means in practice is that we’re going to create a VisualForce page, but instead of populating it with VisualForce components, we will use it to load our Angular application. This part takes the longest to set up, but once we have the basics it will be easy to add features like static assets, deep-link routing, and of course access to your Salesforce data!
Let’s assume you already have an Angular CLI project that compiles (using the command ng new is sufficient for this) First we need to take one of the outputs of our Angular build: the index.html file, and translate it into a VisualForce page. That means going from this:
To this:
You’ll notice a couple of key differences:
- The html tags are replaced with apex:page tags.
- The doctype pre-processing instruction is removed.
- Tags that normally don’t need a closing tag like base, meta and link now require a closing tag.
- References to static files including Javascript, css and even icon files use a strange-looking syntax.
The first three are just requirements for how a VisualForce page is defined. The last point has to do with how Salesforce presents links to static resources. As part of our deployment we are going to be bundling the output of the Angular build into a Salesforce Static Resource. Static resources are accessed on a VisualForce page by using the macro:
{!URLFOR($Resource.[bundle name], ‘[path to file within bundle]’)}
When Salesforce serves a VisualForce page, it resolves all references in the above style on your page so that by the time it reaches your browser, the macro has been replaced with a valid URL. It’s important to note that this replacement happens only on the VisualForce page, not in any Javascript loaded by it.
We’re going to have to perform this transformation every time we modify our Angular app, so let’s automate it! I’ve written a python script for this but you can use the tool of your choice:
Here I have an argument to the script that allows me to specify the static resource bundle name. So calling this script from the root of my Angular project directory after running ‘ng build’ will produce the required VisualForce page:
Step 2 : ‘Hello Salesforce-gular’
Now that we at least understand how a VisualForce page running Angular is supposed to look, let’s deploy it! To do this we’re going to use the Force.com Migration tool, which is an ANT-based tool for pushing and pulling Salesforce artifacts to an organization. So if you haven’t already, download and install Apache ANT and get the ANT Salesforce jar file (https://developer.salesforce.com/docs/atlas.en-us.daas.meta/daas/forcemigrationtool_install.htm)
Let’s get a little more organized. Make a subdirectory inside your Angular project and populate it as follows:
src-dev-deploy is where we will set up our ‘Salesforce project’ in a layout the Force.com migration tool can understand. ant-salesforce.jar comes from the Force.com migration tool.
package.xml is metadata for our project and looks like:
dev.sf contains your Salesforce Organization credentials and should not be added to Git. Remember to add dev.sf to your .gitignore!
build.xml is the ANT script that will take our Salesforce Project and deploy it to an organization:
All that’s left to do is populate the Salesforce project using the output of our Angular build. We’ve already done half of this in visualforce_transform.py. Let’s add a couple more things to finish it off:
- Accept the name for the VisualForce page we will deploy to.
- Accept the directory where the project is deployed.
- Create a metadata template file next to the created VisualForce page.
- Zip up the Angular build and create a Static Resource bundle to be deployed.
We’re now finally ready to package and deploy to our Salesforce organization! Running the following commands from the root of your Angular project will make your application available at: [Your Org URL]/apex/sftestpage
Step 3 : Who needs a backend? I have Salesforce!
None of this would be very useful if we couldn’t actually present or interact with any of the data in our CRM, right? To create a bridge between Angular and the world of Salesforce APEX we’re going to use something called a VisualForce controller. A controller is simply an APEX class that exposes methods that a VisualForce page can call. Using a controller has two advantages:
- Calls to an APEX controller do not count against REST call limits for your organization.
- Salesforce automatically generates a Javascript wrapper for every controller method for you!
To start, go to the Salesforce developer console and create a new class named ‘sftestcontroller’. Copy and paste the following code and save the file:
Now we need update our Angular app so that it gets a reference to the Salesforce-provided controller wrapper:
We also need to update the visualforce_transform script to inject the right controller name in place of the variable $SFCONTROLLER. This lets you de-couple your Angular code from whatever naming changes happen in Salesforce:
Now we can add a TypeScript interface that reflects how we call a controller method, and call our first controller method in an Angular component!
Remember that to see changes in your page you’ll have to run ng build and the ant deploy command again. Already sick of that workflow? Keep reading, Step 6 focuses on making your iteration much easier😀
As a side note, in the GitHub repo linked at the end of this post, I show one way you can wrap controller calls into Observables which tend to play nicer with Angular compared to using callbacks.
Step 4 : Why can’t I see my cat GIFs?
You’ve probably noticed by now that any images you’re trying to show using <img> tags with paths like “assets/awesome_cat.gif” aren’t showing up. Recall that all of the static assets of the Angular build need to be accessed with a fully qualified url that is generated with the URLFOR macro. Okay, so we’ll just change all our <img> tags to look like this right?
<img src=”{!URLFOR($Resource.sftestassets, ‘assets/awesome_cat.gif’)}”>
Unfortunately, that doesn’t work. The reason is that Salesforce only processes this macro on the VisualForce page itself, not in Javascript that it loads, which is where all our Angular HTML templates are coming from. Luckily, the logic is fairly simple to replicate. We simply need to get the base URL for our resource bundle by using the macro somewhere in our index.html (which gets translated into the VisualForce page) and assign it to a global variable. At that point it’s easy to make an Angular pipe that appends this global variable to the start of any resource paths.
First, the modification to index.html:
Next, a slight change to visualforce_transform.py:
Finally, the Angular pipe, and example of how it is used:
One thing to note is that image paths in CSS (for example, in a ‘background’ property) do not need to be updated. This is because the browser looks for files referenced in CSS relative to where the stylesheet was loaded from, not where the HTML document was loaded from.
Step 5 : We need to go deeper.
Astute observers may have noticed by now that as they navigate through Angular pages, the URL in the browser bar is clobbering the base of the VisualForce page url (/apex/sftestpage). If you try to copy and paste one of these URLs, you’l just get a big ugly ‘URL no longer exists’ page back from Salesforce. This is not an ideal experience for a web application. So how do we support Angular deep links on Salesforce?
- It is necessary to switch from HTML push state routing (the default for Angular 2+) back to hash-based routing. This is because Salesforce does not know that our Angular / VisualForce hybrid handles any routing beyond the deployment URL. Thus the only way to make Salesforce ignore the Angular part of the route and pass it to us is to use a hash based url like apex/sftestpage#path/to/my/angular/route.
- When trying to access a Salesforce URL that contains an Angular route after the hash and the user is not logged in, Salesforce will remember the requested URL (including the hash segment) and forward to that location after logging in, which is exactly what you would want from a deep-linking system.
The HashLocationStrategy included with Angular doesn’t actually produce valid URLs for use with Salesforce deep linking. The reason is that it adds a trailing slash before the hash which Salesforce will not accept. We need to use a modified version that fixes this, and prepends the apex and page name segments:
We again need to modify index.html and visualforce_transform.py to make that page root variable available:
Don’t forget to replace the built-in location strategy by adding a { provides: } block in your main module!
Note that lazy loading routes is not possible to do with an Angular / Salesforce deployment. This is because the ‘deployUrl’ argument to ‘ng build’ needs to be specified at compile time, and we don’t know the deployment URL for the static resources until after the application is deployed. Hence Angular will try to load the chunk Javascript files relative to the index.html file and fail.
Step 6 : “But, I miss ng serve…” — Improving Developer Workflow
So you’ve got an Angular app running in Salesforce with access to all the power of the platform, which all the front-end features you would normally have (with some compromises). There’s one glaring exception, the oh-so-time-saving command ng serve doesn’t work for us any more. This, of course, is due to both the need to transform the index file and deploy it with a new static resource bundle every time our app changes. We need to define a new ideal (or as close as we can get) workflow:
- File(s) that are part of the Angular project are modified.
- Angular project is automatically rebuilt.
- Then the index.html file is automatically transformed and the Angular build is packaged.
- Finally the force.com migration tool is run automatically to deploy the new version of the page and static resources.
How can we accomplish this? The first two steps are simple, all we need is a separate terminal running ng build with the “--watch” flag. Like with ng serve, the contents of the /dist folder will be re-populated every time a source file changes. To orchestrate the last two steps, we’re going to use an old front-end developer’s friend called Gulp.
The following snippet shows a Gulp script that when run, will watch the /dist directory for changes, and on detecting those changes it will first call our visualforce_transform script and on success of that, will run the ant task necessary to deploy to Salesforce:
Add the following packages to you package.json dev dependencies and run npm install:
To kick off this parallel process, just run the command gulp from the root of the project. This leads to combined time of about 20–60 seconds (depending on your upload speed to Salesforce) between when you save a source file and when you can see the results in Salesforce. It’s no ng serve, but it’s better than nothing!
Conclusion, and the finished product
Using Angular instead of VisualForce components directly is a great option for cross-functional teams that have front-end developers that don’t necessarily have knowledge of Salesforce development, allowing Salesforce experts to focus on data modeling and APEX code. It’s also a good option when you desire a totally custom experience for the users of your Salesforce organization.
A working demonstration of this idea can be found at https://github.com/arutnik/salesforce-angular-demo . All that’s needed to try it out is your own Salesforce developer organization.