SPA Day

Picture yourself at a spa.  You’re relaxing in a nice hot tub, letting the worries of the week soak away into the scalding water.  Your phone rings – a flare of stress and responsibility is fired off into the night.  Do you really want to get out and answer that call?

Now, I’ve never been to a spa, but I have been in a couple hot tubs in my day and the idea of popping out into the cold just to answer a call is not appealing.  But, metaphorically speaking, this is exactly what I’d made our Angular application do.  It never complained, but I still feel bad- it wasn’t very thoughtful of me!

Let me explain- the spa in this case is an acronym – Single Page Application.  You may be familiar with this term- it’s an app that basically lives on the client/browser.  The bulk of the app’s structure and logic/functionality is downloaded up front as a bundle of Javascript, then network calls (think AJAX) are used to get data as needed.  A different model from the traditional request-response website, where each page is fully fetched, data and all, from the server.  You trade off a possibly slower initial load time (though that’s becoming less of an issue with tools like the Angular CLI) for a lightning fast app after that first load because you rarely have to go back to the server for more info- it’s all there in your bundle.

So, the metaphor does kind of work.  I’m the application, the hot tub is your browser, and once it’s loaded, why make it leave?  Of course, sometimes it has to make an important call.  If anything is going to be saved to a database or read from a database you’ll have to make that network call.  But the more you can minimize these, the faster your app will be.

On many views of this SPA, we show a grid of data.  That’s a trip to the database- no way out of that one.  There’s also an “edit” link on each row of the grid so a user can update the details contained within.  Initially, we had these going to the database to get the details of that row.  But that’s the old way of thinking!  That trip isn’t necessary to get anything to be displayed on the screen- it’s just to get data that’s technically already available (in the array of objects that’s fetched to populate the main grid).  All we have to do is pass it along to the individual view.

For our Angular application, I initially thought this might be an option on the Router class.  You can pass params to a child route, but that doesn’t really work in this case.  The data needed by the edit screen (child route) can be quite a few properties long, and passing all that in the query string didn’t seem like a great idea.  But this (sharing data between two components) seems like exactly the kind of thing services were made to do!

In the grid component’s template, there is an “edit” button.  When clicked, it fires a method on the component’s class to navigate to the edit component route and originally just passed the id/key of that item.  Now it’s been updated to pass the key and the object itself- the info from that row in the grid.  This object is passed to a service that has some other utility functions on it, but the one we need in this case is super simple:

setCurrentEditObj(obj) {
    this.currentEditObj = obj;
}

The currentEditObj is a class variable that’s just used to hold the object we’re about to edit.  This simple method updates that object any time the “edit” button is clicked on a grid.

In the edit component’s class, we subscribe to the route’s params observable.  This fires off info when the route resolves and gives back any params passed on the route.  In this case, it’s usually just the id/key of whatever is being edited.  In the original version, that id is then used in a getItem method- which just does an http call to the database to get the relevant info.  We inserted a check in that observable:

ngOnInit() {
    this.subscription = this._route.params.subscribe((params: Params) => {
        const currentObj = this.setupService.currentEditObj;
        if(currentObj) {
            this.countryName = currentObj['country_name'];
            this.id = currentObj['country_key'];
            this.setFormFieldDefaults(currentObj);
        } else {
            this.id = params['id'];
            this.getItem(this.id);
        }
    });
}

We wait for the route to resolve and check if the service has a currentEditObj available.  If so, we set the class variables (countryName and id for this view, which just edits a list of countries), and populate the form (setFormFieldDefaults just loops through the object, checks to be sure the object’s property exists on the form, and sets the initial value).  No database trip- our app gets to stay in the warm hot tub that is your browser!

But there’s one check you should do.  You might have noticed the “else” branch in the code above.  The internet isn’t perfect- your connection might be dropped and a full page refresh might be required.  A user might accidentally refresh the screen as well.  If that happens, it can cause issues for a single page application, because the object we are editing is now stored in the code on your browser.  If that browser refreshes, that info is lost.

As an aside – this kind of relates to the notion of offline availability.  Some really cool new techniques are being explored in this area using service workers– these can sit on your browser and respond to requests as if they were a server when a server isn’t available, but will be much faster (as they don’t actually have to travel anywhere) and be available if you lose your connection.  Very cool stuff, but not widely supported at this point.

Instead, we just include a simple check.  If that currentObj doesn’t exist, fall back to the old method and use the id from the url (which will still be available, as it’s stored in the url string) to go to the database and get the necessary info (getItem just does that, then calls the setFormFieldDefaults method).  The only time this should be called is if there’s a refresh or dropped connection, but it works nicely- the data is still available, and the extra cost of a database call isn’t really felt, as the entire application needs to be re-fetched anyway.

Does it save a lot of time?  Probably not- none of the objects we are fetching are huge.  But it is a saved network call, which definitely conforms to the spirit of a single page application.

Should I have thought of this originally?  Probably- it really makes more sense within the spirit of a single page application.  Anywhere you make an HTTP call should be examined and evaluated.  Does this call need to be made?  Do I already have access to this info?  Am I making my app leave the relaxing waters of your browser to make that call?  Let the app relax!

Advertisements