In the “Leveraging Salesforce Using Spring Boot” article, I navigated the course for introducing a Spring Boot service that would leverage the well-established Salesforce RESTful API. The goal of this service was to act as a middleware layer; that way, clients not written in Salesforce could retrieve and update contact data stored in Salesforce. This backend service implemented its own caching layer to provide a faster response time and also cut down on the number of times Salesforce needed to be called.
In “Leveraging Salesforce Using a Client Written In Svelte,” I introduced a simple client written in Svelte, which made it possible to update the Salesforce data using an inline editor—again, without actually using the Salesforce client.
The “Leveraging Salesforce Using a Client Written In Vue.js” article introduced a client application using the Vue.js framework to further interact with the Spring Boot service. Using Vue.js, the resulting client was not only able to read data from Salesforce but also processed and displayed updates made to the Salesforce data via a server-sent events (SSE) implementation.
The fourth article in the series, “Leveraging Salesforce Using Mobile Applications Written (Once) In React Native,” introduced native mobile applications for Android and iOS devices leveraging a single source codebase written in React Native. This new client offering allowed senior executives to monitor the progress of the Salesforce data.
In this article, we will use the Angular framework to complete a new feature request. In this example, the same contact data from Salesforce will be utilized in a drop-down list to avoid having a different source of contact data.
Revisiting the Example Use Case
Let’s briefly recap our example use case: The Business Call Center is about to launch a major marketing campaign. However, they recently discovered that the title generated for the list of contacts was incorrect approximately 90% of the time.
A team of interns has been updating contacts using the Svelte client, and the managerial team has been using the Vue.js client to monitor the updates as they occur, complete with server-sent events appearing as toast messages. Executives have been monitoring the progress using their Android and iOS devices from native clients which have been deployed.
The feature team responsible for the new widget product line has realized that they too can benefit from the contact information stored in Salesforce. The following requirements have been added to the widget application:
- The new widget form requires a contact field.
- The selection options for the contact field will be a drop-down list.
- The source data will be a dynamic list of contacts from Salesforce.
- Each selection option will present the contact’s full name with their title in parentheses (if available).
- As changes are made to contacts in Salesforce, the list of contact selection options should automatically update (including the title value).
The feature team for the widget product line is planning to start this work right away and should have everything they need based upon the results of prior work completed for this series.
As a point of reference, below is a link to the Spring Boot service which has been used for this entire series:
https://gitlab.com/johnjvester/salesforce-integration-service
Why Angular?
Angular is a TypeScript-based web client framework that is led by Google and fueled by a very large open-source community. Of all the frameworks I have used in this series, Angular is certainly the largest—almost to the point where it may be better to call it a platform.
Some benefits of using Angular are noted below:
- Designed to handle enterprise web applications, accommodating next-generation design patterns, including progressive web applications.
- Continues to evolve with a dedicated open-source community. This leads to an impressive bug/resolution time frame and a large library of third-party solutions or dependencies which can be added to any modern Angular project.
- Supported by Google, serving as the primary web-client framework for technology powerhouses that include Gmail, Microsoft Office, PayPal, Upwork (freelance program), and Samsung.
Personally, I have been involved with enterprise web applications running in both AngularJS and Angular since early 2014. While Angular is designed to handle large-scale applications, I have been equally successful in using the same framework for small and simple applications.
Getting Started with Angular
For this article, I decided to step outside of my comfort zone by giving version 12 of Angular a try. (As a point of reference, I last used version 9 for the fitness application I wrote for my sister-in-law in my “Using Heroku to Quickly Build a Multi-Tenant SaaS Product” series last year.)
Since my Angular command-line interface (CLI) was still on version 9, I needed to use the following command to upgrade to version 12 of the Angular CLI:
npm install -g @angular/cli
First-time users can use this same command as well.
Issuing an ng version
command provided me with the following results:
_ _ ____ _ ___
/ \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _|
/ △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | |
/ ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | |
/_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___|
|___/
Angular CLI: 12.2.0
Node: 14.15.5
Package Manager: npm 6.14.11
OS: darwin x64
Angular:
...
Package Version
------------------------------------------------------
@angular-devkit/architect 0.1202.0 (cli-only)
@angular-devkit/core 12.2.0 (cli-only)
@angular-devkit/schematics 12.2.0 (cli-only)
@schematics/angular 12.2.0 (cli-only)
To create a new Angular 12 application, I issued the following Angular CLI command:
ng new salesforce-integration-angular
The Angular CLI created the application in a new folder called salesforce-integration-angular. Below is a summary of some of the command’s output:
? Would you like to add Angular routing? No
? Which stylesheet format would you like to use? CSS
...
✔ Packages installed successfully.
At this point, I used the ng serve
command to show the newly created Angular application:
Of course, there is not a lot there, but at least the Angular application started in a matter of seconds.
Adding Some Dependencies
The requirements for the widget application contained an interesting business rule. To avoid having to scroll back to the top of the page, I captured the information below:
As changes are made to contacts in Salesforce, the list of contact selection options should automatically update (including the title value).
This requirement translates to the widget application having to maintain the current state of the contact objects. Meaning, the contact list information always needs to be current.
In the “Leveraging Salesforce Using a Client Written In Vue.js” article, the Spring Boot service was updated to broadcast SSEs as changes were made to the state of the contacts stored in Salesforce. The Angular application will also need to listen to those same SSEs.
However, with the Angular application, I decided to use @ngrx/store, which is a Redux-inspired global state management for Angular applications—powered by RxJS. What this means is that I will wire the SSEs from Spring Boot to maintain the state of Salesforce contacts inside the browser. Then, the widget component can employ a reactive design to always have the latest changes—without having to make another call to the Spring Boot service.
Adding the ngrx/store dependency to the Angular 12 application required a simple command:
npm install @ngrx/store --save
With this single dependency added to the project, I can focus on creating the widget component in Angular.
Creating the Widget Component
For this simple example, launching the Angular 12 application will present a new widget form. Because we’re keeping things simple, the form will look like this:
The model field will be a freeform text field and the contact field will contain a dynamic list of Salesforce contacts, kept up to date via the SSE listeners communicating with NgRx.
Creating a new component using the Angular CLI requires the following command:
ng generate component widget
The Angular CLI responds with the following status updates:
CREATE src/app/widget/widget.component.css (0 bytes)
CREATE src/app/widget/widget.component.html (21 bytes)
CREATE src/app/widget/widget.component.spec.ts (626 bytes)
CREATE src/app/widget/widget.component.ts (275 bytes)
UPDATE src/app/app.module.ts (727 bytes)
As a result, a widget component has been created and is ready to use in the /src/app/widget
folder of the Angular 12 application.
Configuring NgRx
Rather than include all of the necessary information around configuring NgRx here, the following link to the NgRx website provides a great amount of detail:
https://ngrx.io/guide/store#ngrxstore
Part of this documentation includes the following diagram:
At a high level, we’ll add the following elements to the Angular 12 application:
- ContactService: to make API calls to Spring Boot, allowing for us to receive contacts.
- EventService: to make the connection to the SSE URI running in the Spring Boot service.
- ContactEffect: to register event listeners and load the original contacts from the Spring Boot service.
- ContactAction: to describe the events that are dispatched from the ContactService.
- ContactReducer: to ensure the state changes are being processed.
- ContactSelector: to select and derive contact information from the store.
- WidgetComponent: to listen to the ContactAction and receive data from the ContactSelector.
Let’s dive into the code and see how this looks in TypeScript.
ContactService
The ContactService handles making basic API calls to the Spring Boot service that has been used throughout this series. The Angular 12 client will simply make a call to the /contacts URI and return an “observable” containing a list of contact objects:
export class ContactService {
constructor(private http: HttpClient) { }
getContacts(): Observable<Contact[]> {
return this.http.get<Contact[]>( environment.api + '/contacts')
.pipe(
retry(1),
catchError(this.handleError)
)
}
}
EventService
The EventService establishes a connection to the URI in Spring Boot which is broadcasting SSE updates. I added the getServerSentEvent()
method to make the connection:
getServerSentEvent(): Observable<any> {
return Observable.create((observer: { next: (arg0: any) => void; error: (arg0: any) => void; }) => {
const eventSource = this.setSource();
eventSource.onmessage = event => {
this.zone.run(() => {
observer.next(event);
});
};
eventSource.onerror = error => {
this.zone.run(() => {
observer.error(error);
});
};
});
}
When a SSE arrives, the _onMessage()
method is called:
private _onMessage(e: MessageEvent): void {
const message = JSON.parse(e.data);
if (message) {
this.dispatchActionInNgZone(processSseUpdate(message));
}
}
This in turn dispatches the action into the NgZone:
private dispatchActionInNgZone(action: Action): void {
this.zone.run(() => this.store.dispatch(action));
}
ContactEffect
The ContactEffect registers an event listener to the EventService, updates contacts from SSE messages that are received, and loads the original contacts from the Spring Boot service.
registerEventListener$ = createEffect(
() =>
this.actions$.pipe(
ofType(ContactActionTypes.AllContactsLoaded),
tap(action => {
this.eventListenerService.register();
}),
repeat()
),
{ dispatch: false }
);
updateContactFromSSE$ = createEffect(() =>
this.actions$.pipe(
ofType(processSseUpdate),
map( payload => {
const anyContact:any = (payload as any);
const contact = (anyContact as Contact);
const updatedAction:Update<Contact> = {
id: contact.id,
changes: { ...contact }
};
return new ContactUpdated({contact: updatedAction});
})
)
);
loadAllContacts$ = this.actions$.pipe(
ofType<AllContactsRequested>(ContactActionTypes.AllContactsRequested),
mergeMap(() => this.contactService.getContacts()),
map(contacts => { new AllContactsLoaded({ contacts })} )
);
ContactAction
The ContactAction describes the events that are dispatched from the ContactService.
export enum ContactActionTypes {
AllContactsRequested = '[Contact API] All Contacts Requested',
AllContactsLoaded = '[Contact API] All Contacts Loaded',
ContactUpdated = '[Contact API] Contact Updated'
}
export class AllContactsRequested implements Action {
readonly type = ContactActionTypes.AllContactsRequested;
}
export class AllContactsLoaded implements Action {
readonly type = ContactActionTypes.AllContactsLoaded;
constructor(public payload: { contacts: Contact[] }) { }
}
export class ContactUpdated implements Action {
readonly type = ContactActionTypes.ContactUpdated;
constructor(public payload: { contact: Update<Contact> }) { }
}
export type ContactActions = AllContactsRequested | AllContactsLoaded | ContactUpdated;
ContactReducer
The ContactReducer makes sure the state changes are processed.
export function contactReducer(state = initialContactsState, action: ContactActions): ContactsState {
switch(action.type) {
case ContactActionTypes.AllContactsLoaded:
return adapter.setAll(action.payload.contacts, {...state, allContactsLoaded: true });
case ContactActionTypes.ContactUpdated:
return adapter.updateOne(action.payload.contact, state);
default: {
return state;
}
}
}
WidgetComponent
Finally, the WidgetComponent leverages all the NgRx state-management elements to provide a dynamic and self-updating list of contact information from Salesforce via the Spring Boot service and SSE URI.
The ngOnInit()
method connects to the NgRx store, then receives the original list of contacts:
ngOnInit(): void {
this.widget.model = "Some Model Description Goes Here";
this.contactService.getContacts().subscribe((data) => {
this.store.dispatch(new AllContactsLoaded({ contacts: data }));
this.loading = false;
});
}
With the NgRx implementation in place, updates will be processed as they are received from the Spring Boot service over the SSE URI.
To make sure the Widget component is displayed when Angular starts, I reduced the app.component.html file to a single line:
<widget></widget>
Using the Angular Application
Using ng serve from the Angular CLI, we start up the Angular 12 application with all of the above changes in place.
This displays the widget form, showing the contact data from Salesforce in the drop-down list:
Using the following cURL command, I updated the title for Rose Gonzales from “SVP, Procurement” to “SVP, Information Technology.”
curl --location --request PATCH 'http://localhost:9999/contacts/0035e000008eXq0AAE' \
--header 'Content-Type: application/json' \
--data-raw '{
"Title": "SVP, Information Technology"
}'
The PATCH command resulted in an HTTP status code of 202 (Accepted) and returned the following payload:
{
"attributes": {
"type": "Contact",
"url": "/services/data/v52.0/sobjects/Contact/0035e000008eXq0AAE"
},
"id": "0035e000008eXq0AAE",
"Name": "Rose Gonzalez",
"Title": "SVP, Information Technology",
"Department": "Procurement"
}
Without making any changes to the widget form, the drop-down list options now appear like this:
Notice how the contact title changed automatically for Rose Gonzalez.
Conclusion
Starting in 2021, I have been trying to live by the following mission statement, which I feel can apply to any IT professional:
“Focus your time on delivering features/functionality which extends the value of your intellectual property. Leverage frameworks, products, and services for everything else.”
- J. Vester
In this article, I created a widgets component using Angular 12 which included a drop-down list of contacts populated with data from within the Salesforce implementation. I added NgRx state-management functionality to listen for SSE messages from Spring Boot in order to keep the list of contacts current. In this case, I leveraged the powerful NgRx state-management framework to do the work for me—requiring very few changes to the widget component at all.
Similar to my experiences with Svelte, Vue.js, and React Native, the time to create a ready-to-deploy component was very fast, measured in minutes rather than hours. Just like in all of this series’ articles, we have been able to use Salesforce without actually using a Salesforce client.
Of course, a production-ready scenario would require some additional work to prepare this application for “prime time” use.
If you are interested in the source code used for the Angular client, simply navigate to the following repository on GitLab:
https://gitlab.com/johnjvester/salesforce-integration-angular
In the next article of this series, I plan to turn things 180 degrees and use Lightning Web Components (LWC) outside the Salesforce ecosystem.
Have a really great day!