How to develop Salesforce Visualforce apps using AngularJS ?

How to develop Salesforce Visualforce apps using AngularJS ?

If you need to display Salesforce data to people other than those that have Salesforce accounts, Visualforce pages is the best way to go. It allows users to view, edit, or even add data without exposing the data to third party systems. For developers creating Visualforce apps is way easier than creating ad-hoc third party web apps that call upon Salesforce data.

Seeing the popularity of Visualforce, Salesforce started expanding upon the product and within few years of its launch we saw support for jQuery, brand new REST API, and ForceTK; a JS proxy calling library. However, the game changer for Salesforce and JavaScript development was when Salesforce allowed JavaScript remoting for Apex Controllers. JS remoting allowed you to access server side apex controller methods through JS directly. Along with Remote Objects, this allowed more flexibility and dynamic usage of Visualforce pages resulting in an even greater adoption of the tool. More and more organizations were depending upon Visualforce pages to display data to clients, partners, and other users now. This called for a more appealing UI for the Visualforce pages and frontend JS framework and libraries were naturally called into the fray. AngularJS being the most popular one was naturally the favorite for the task of creating eye candy dynamic Visualforce pages.

In our previous post we talked about why exactly AngularJS is great for creating Visualforce pages. In this post, we will talk about the HOWS. I am assuming that you have some general understanding of AngularJS framework, Salesforce Visualforce pages, and have experience with JavaScript.

For more info on any topic check out these links, they may help.
AngularJShttps://docs.angularjs.org/tutorial
Visualforcehttps://developer.salesforce.com/trailhead/module/visualforce_fundamentals

How to use AngularJS in Visualforce?

JavaScript saw a renewed interest partially because of the success of its frameworks and partially because of greater need for a more visually appealing mobile friendly single page application. In Visualforce pages, the backend part is not that flexible but that’s because of the level of security that comes with Visualforce. The frontend part, on the other hand, is all yours. So frontend frameworks like AngularJS are greatly preferred to create organized, structured applications having good response times.

Now considering that each Visualforce app involves around CRUD (create, read, update, delete) operations on Salesforce data, there are three prevalent approaches for fetching and binding data from Salesforce to Visualforce controllers.

Creating JavaScript Remote objects and using it with Visualforce,
Using JSON,
And using ForceTK libraries along with AngularJS libraries.

The differences are subtle in small apps but as the complexity of your app grows, the difference would be more prominent.

The beauty of Visualforce pages is that you can start coding from the word go without opening or uploading multiple files to a server or such. So here I am assuming that you have an understanding about VF pages and can create a standalone page in the SFDC setup.

In this example, we are going to fetch a list of ‘Contacts’ saved in Salesforce in a tabular form and even add a simple search function highlighting two-way data binding of AngularJS.

Let’s take a look at the main Visualforce page code :

<apex:page showHeader="false" Controller="ContactsController">
   <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js">
   </script>
   <link rel="stylesheet"  href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" />
   
   <script type="text/javascript">
   var app = angular.module('MyApp',[]);  
   
   app.factory('VFRemotingFactory',function($q,$rootScope){  
       var factory = {};  
       factory.getData = function(searchText){  
           var deferred = $q.defer();  
           GetAllContactsByFilter(function(result){  
               $rootScope.$apply(function(){  
                   deferred.resolve(result);  
               });  
           }, searchText);  
           return deferred.promise;  
       }  
       return factory;  
   });
   
   function GetAllContactsByFilter(callback, searchText){  
       if(searchText == undefined)
       {
           searchText = '';
       }
       Visualforce.remoting.Manager.invokeAction(  
           '{!$RemoteAction.ContactsController.GetAllContactsByFilter}', searchText,
           callback,  
           {escape: false}  
       );
   }
   app.controller('myController',function($scope,VFRemotingFactory){  
       $scope.mcm = {};
       
       $scope.getFilteredData = function($event){
           if($scope.mcm.searchText.length > 1)
           {
               var searchTxt = $scope.mcm.searchText;
               VFRemotingFactory.getData(searchTxt).then(function(result){  
                   $scope.ContactData = result;  
               });
           }
           else
           {
               var searchTxt = $scope.mcm.searchText;
               VFRemotingFactory.getData().then(function(result){  
                   $scope.ContactData = result;  
               });
           }
           
       };
               	$scope.Prafull = {};        
       VFRemotingFactory.getData().then(function(result){  
           $scope.ContactData = result;  
       });  
   });
   </script>
      
           <div ng-app="MyApp">
               <div ng-controller="myController">
                   <label>Search: <input ng-model="mcm.searchText" ng-keyup="getFilteredData($event)"/></label>
                   <table class="table">
                       <thead>
                           <tr>
                               <th>First Name</th>
                               <th>Last Name</th>
                               <th>Phone</th>
                               <th>Email</th>
                               <th>Title</th>
                               <th>Account Name</th>
                           </tr>
                       </thead>
                       <tbody>
                           <tr ng-repeat="contactVar in ContactData">
                               <td>{{contactVar.FirstName}}</td>
                               <td>{{contactVar.LastName}}</td>
                               <td>{{contactVar.Phone}}</td>
                               <td>{{contactVar.Email}}</td>
                               <td>{{contactVar.Title}}</td>
                               <td>{{contactVar.Account.Name}}</td>
                           </tr>
                       </tbody>
                   </table>
               </div>
           </div>
           <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
               	<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
</apex:page>

Now let’s analyse this code. We started off by including a custom Salesforce apex
controller<https://developer.salesforce.com/docs/atlas.en-us.pages.meta/pages/pages_controller_custom.htm> for pulling contacts that we named very ingeniously “ContactsController”. This custom controller is also very simple.

public class ContactsController {
   @RemoteAction
   public static List<Contact> GetAllContactsByFilter(String searchText)
   {
       String searchString = '%' + searchText + '%';
       List<Contact> contactList = [SELECT FirstName, LastName, Phone, Email, Title, Account.Name FROM Contact where FirstName like :searchString];
       return contactList;
   }
}

As you may have guessed we are using JS Remoting to access to pass on our data.

We start off by creating our first AngularJS module that answers to name ‘MyApp’, var app = angular.module(‘MyApp’,[]). This app is referenced in the main body div through

<div ng-app="MyApp">

In our main body, we have created an input box that defines what we are going to display. By default, the content is ‘’ i.e. blank which triggers the app to display all contacts. As we start typing in the search box, it starts to filter out data based on the value in search box, in real-time, in the same page, without refreshing. This is the magic of two-way data binding.

Now from here on now we started to complicate things a little. The main angular controller that we again ingeniously named “myController” is the brains behind the module’s (or app) operations and we have included it in our nested div of the body.

We then created a module factory that invokes “GetAllContactsByFilter” function which in turn invokes Visualforce remote objects and our custom Visualforce controller ContactsController. This factory returns the list of contacts based on the search text, which by default is blank.

We created a scope object named ‘mcm’ that contains the model data that we input through Search input field. The $scope is the main magician that binds view with model.

The ng function ng-keyup=”getFilteredData($event) that we referenced in search input, triggers a new event whenever a key is entered in the search box. Triggering of this event results in automatic changing of model and in our example, automatic changing of view as well. You can restrict changing of view through a button.

Our AngularJS controller ‘myController’, triggers fetching of data based on the input value. For blanks it fetches all data so does it for typing only single alphabet. When the input string’s length becomes greater than one it filters out the data based on input string. The fetched data is stored in scope object ‘$scope.ContactData’ which in turn passes on data to ‘contactVar’ that finally displays data.

To summarize how AngularJS helped most in this really small and basic example:

  • The ng-keyup automatically trigger events based on user input. A custom function for this is tricky at best.
  • The two way data binding using custom code is full day work at best. We did it in 1 hour.
  • ng-repeat, the small function used to populate the table, a custom code for it will take at least 3-4 hours. We did in blink.
  • To add automatic filters we just have to add a line in the ng-repeat reference. For example, if we have to filter by an account we just have to add ng-repeat=”contactVar in ContactData | filter:account”. (Assuming account is the predefined variable).

An Even Simpler Approach

Now in the previous example, we have needlessly complicated things through module factory and Visualforce remoting. This use case can be achieved through a simpler code. Checkout the following code:

<apex:page standardStylesheets="false" sidebar="false"
   showHeader="false" applyBodyTag="false" applyHtmlTag="false"
   docType="html-5.0" controller="AngularDemoController">
<html lang="en" ng-app="demoApp">
<head>
   <meta charset="utf-8"/>
   <meta name="viewport" content="width=device-width, initial-scale=1"/>
   <title>Angular Demo</title>
   <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css"/>
   <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.min.js"></script>
   <script>
     // define the app
     var demoApp = angular.module('demoApp', []);
     // add the controller
     demoApp.controller('DemoCtrl', function ($scope) {
         $scope.account = {!account}
         $scope.contacts = {!contacts}
     });
   </script>
</head>
<body class="container" ng-controller="DemoCtrl">
   <h1 style="color:Green">{{account.Name}}</h1>
   <p class="lead" style="color:Gray">
   {{account.BillingStreet}}<br/>
   {{account.BillingCity}}, {{account.BillingState}}
   {{account.BillingPostalCode}}
   </p>
    <b>Search</b>&nbsp;&nbsp;&nbsp;<input ng-model="query" /><br/><br/>
    
    
   <table class="table table-bordered">
     <tr>
       <th>Name</th>
       <th>Email</th>
       <th>Id</th>
     </tr>
     <tr ng-repeat="contact in contacts | filter:query">          
       <td>{{contact.Name}}</td>
       <td>{{contact.Email}}</td>
       <td>{{contact.Id}}</td>
     </tr>
   </table>
</body>
</html>
</apex:page>

Here is the code for controller:

global with sharing class AngularDemoController {
   // hardcode an account id for demo purposes
   static String accountId = '00128000003u3uK';
 
   global static String getAccount() {
       return JSON.serialize([select name, billingstreet,
           billingcity, billingstate, billingpostalcode
           from account where id = :accountId][0]);
   }    
 
   global static String getContacts() {
       return JSON.serialize([select id, name, email
           from contact where accountId = :accountId]);
   }
}

Here we are using the JSON approach, and using Salesforce’s inbuilt features like ‘!account’ and ‘!contact’ that automatically lookout for getAccount variable and method called in a custom controller. In this example, we have created an app to fetch contacts of an account whose ID we have hardcoded (I was a little lazy there).

Here we see the true meaning of Salesforce AngularJS integration.

Salesforce Lightning vs AngularJS

Salesforce launched a set of prebuilt UI elements as part of its Lightning platform. This bridges the gap that was leftwas by previous Visualforce in-built components especially in the field of UI. It allows you to create reusable components and has many features similar to AngularJS like tables, accordions, and other UI elements. Now the question comes whether you should go with Lightning for your new app forgoing Angular, or should you forget lightning for now and focus on Angular and other frameworks, or even create using both? The answer is complicated and to be frank, it requires some in-depth research. However, as of now AngularJS has no shortcomings and we can create anything using Angular, whereas the same cannot be said for Lightning. The best approach is to analyze both by use case and capabilities. And it’s not very difficult or problematic to use both.

References: salesforce.com, github.com, docs.angularjs.org

The following two tabs change content below.
Pratyush Kumar

Pratyush Kumar

Co-Founder & President at Algoworks, Open-Source | Salesforce | ECM
Pratyush is Co-Founder and President at Algoworks. He is responsible for managing, growing open source technologies team and has spearheaded more than 200 projects in Salesforce CRM alone. He provides consulting and advisory to clients looking for services relating to CRM(Customer Relationship Management) and ECM(Enterprise Content Management). In the past, Pratyush has held consulting roles with various global technology leaders, such as Globallogic & HCL in India. He holds an Engineering graduate degree from Indian Institute of Technology, Roorkee.
Pratyush Kumar

Latest posts by Pratyush Kumar (see all)

Pratyush KumarHow to develop Salesforce Visualforce apps using AngularJS ?