Thinking About Lightning Application Architecture: Part 1

One of the things that really surprised me when I started poking at custom Lightning Components/Apps was the fact that I truly had to handle navigation from scratch. As a developer, we’re taught to never re-invent the wheel and to write code that is re-usable up front. Over the next few posts, I hope to discuss what I’ve found, what I’ve tried and what I’ve ultimately come up with when facing these challenges.

When I started out, I knew that navigation was something that I didn’t want to have to solve every time I set out to write an application. I knew I’d need a core group of components and events that would work in concert with one another and I knew that I’d have to keep these components generic enough that they could be included in any apps that I wrote. What I’ve come up with thus far appears to be working and I’ve done a demo for a couple different people and haven’t had any major “gotchas” pointed out to me yet. I’m still trying to get the ear of some other folks that I know are much more into Lightning than I am just to level set my expectations and find out if I’m really off in the weeds or not.

Before I get into the nitty-gritty, the back story here is that I had a customer approach me about creating a Visualforce page for a very complex custom object in their org. They wanted to be able to use the page in both the browser and on Salesforce1. If this were a simple object, this could have been simpler I imagine, however there are numerous dependent picklists that forced the original VF page to be spread out amongst several other clicks/refreshes, etc. This was not going to translate well to Salesforce1. Also, we wanted to avoid two different processes, one for mobile and one for the web. I made a couple of suggestions and one of them was to create a custom app that could be “created” via App Builder for use with Salesforce1 and an app that could be accessed via the web interface. (I’ve since done some playing with lightning out and prefer going that route for the web interface as it feels more native to the Salesforce experience).

However, budgeting for such an endeavor was a challenge because I’d never done anything like this before. I’ve been through the trailhead modules, taken a HOT at Dreamforce and toyed with it a bit on my own but only using rather simple examples. This was a chance to step back and tackle a “real world” scenario. I dove in far enough to realize, as some of my past blog posts have eluded to, that Lightning really is a “framework” in the most true sense of the word. There are just enough features there for you to work with, but you are left to your own devices to get things off the ground. Therefore, I started an example project in a dev org and this is what I’ll be going over in the coming posts.

Since #TahoeDreamin had just ended, and Apex & The Limits had been asked to travel to #SEDreamin — conferences were stuck on my mind. So my sample application was a very basic conference management application, its not fully baked and I plan to write more, but currently its just venue’s and conferences because…well consulting and billable hours take precedence. (Hey kids gotta eat man!)

The actual components are simple enough that I could use pre-built items for display, etc but I wanted to see how the system would behave with completely custom pages/forms, etc. since my clients’ application was not going to be able to take advantage of things like default page layout, record pages and what have you.

Rather than discuss the ins and outs of creating my custom objects, I’ll just tell you what I have and leave you to your devices to create them (or something similar) if you’d like to follow along. We have a Conference object and a Venue object. Each object has its own information, like addresses, start dates, end dates etc. Again since that’s not at all what this post is about, I don’t want to spend too much time here. Let’s get to thinking about the structure of the application since we are trying to be generic anyway.

Navigation:
I wanted to be able to navigate to a list of conferences or a list of venues so I knew I would need a basic component to display these links to me. I knew I would need custom components to display these lists and informational pages. What I didn’t know was how to navigate between the two. My intuition told me that selecting a record would need to fire some sort of event that I could handle and initialize/load the appropriate component. I had been googling and not found a standard, built-in navigation method. I did find e.force:navigateToSObject and other similar events, but those only work in Salesforce1 (according to the docs) & I needed this to work in the web interface as well. On top of that, I needed a component to fire that event anyway. I knew I needed to fire a custom event and it was looking like I would also need a component to fire it.

However, that’s only part of the problem. Even if I managed to fire this custom event, how were people swapping components in and out. And then I came across this. Basically, I needed to create a container component that would house my custom components and swap them in and out of there.

The last piece I needed was some generic code to be able to handle my custom event, get the name of the component I intend to load and have my container component’s controller load that component. I created a “Base Component” that will be my container and a matching controller that will initialize my entire app (based on a parameter, but more on that later) and also handle my “navigation” by swapping out custom components as signified by my custom event. That looks something like this (I’m using the Lightning Design System to style things):

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes" controller="BaseComponentController">
	
	<aura:attribute name="appName" type="String" />
	<aura:attribute name="activeComponent" type="String" />

	<aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
	<aura:handler event="c:NavigateToComponent" action="{!c.loadComponent}" />

	<div class="slds" style="padding: 10px;">
		{!v.body}
	</div>

</aura:component>

And my Base Component’s controller (NOTE: much of this code should be moved to the helper class, I just haven’t gotten there yet):

({
    doInit : function(component, event, helper) {
            //ignore this bit for now, more on that later
            var action = component.get("c.getDefaultComponent");
            action.setParams({
            	"appName" : component.get("v.appName")
            });

            action.setCallback(this, function(response) {
            	var state = response.getState();
            	if(state === "SUCCESS") {
            		//load new component
            		var defaultCmp = response.getReturnValue();
            		$A.createComponent(
            			defaultCmp,
            			{},
            			function(newComponent) {
            				component.set("v.body", newComponent);
            			}
            		)
            	}
            });

            $A.enqueueAction(action);
    },

    //this is the custom swapping of components piece...
    loadComponent : function(component, event, helper) {
        var newCmp = event.getParam("targetCmp");
        var attrs = event.getParam("attributeList");

        $A.createComponent(
			newCmp,
			attrs,
			function(newComponent) {
				component.set("v.body", newComponent);
			}
		)
    }
})

The “magic” here lies in my controller’s loadComponent function. You will notice that my Base Component is handling a custom event called “navigateToComponent” — this event gets fired by another custom component (we’ll discuss that in my follow up post), my base component will handle that event and inspect it for the name of the component I’d like to load as well as any custom attributes my new component will need, such as the ID of an object I’m trying to load into that component. This function calls $A.createComponent which loads an instance of my component, and swap it into place where my Base Component has {!v.body} specified.

So now “theoretically” (this is all just my observations, but it is indeed working) I can fire my custom event, pass in the name of any component and its attributes and load it without my Base Component having to know anything about the component it is loading nor its data. If I use this base component in all my future apps (along with my custom event), navigation is solved for me.

That’s plenty of writing for me, and reading for you at the moment I think. Hopefully next week, I’ll have the custom event queued up for you :)

:wq!