Converting your web app to Single-Page with AngularJS

Alexandre SimõesSoftware Architect
CERTIFIED EXPERT
Published:
You have this traditional web application that refreshes the whole page each time you navigate and you think it's a good time to convert it to a Single-Page approach. What should you know in advance? Read on, I'll guide you through it.

Introduction


You have your nice classic multi-page web application and you want to move to the new Single-Page (SPA) framework. I am assuming here that you've chosen AngularJS 1.x or AngularJS 2.x as your SPA framework but, if not it's not a big deal. Pretty much everything will still apply as long as it's MV* oriented.

Ideally, at this point, you should be asking yourself:

  • What skills do I need to have in the team?
  • What should I be aware?
  • From where should I start?
  • What would be the best approach?
  • How do I estimate the necessary time?
I’ve been there, several times, and because of that I’ve decided to share my experience and hopefully save someone else’s time and patience.
 

Train your team


If I would have to choose the most important thing in order to succeed in this migration process I would say: 

Train your team!!.

Really, I can’t stress this enough.

If your team is not completely aligned regarding knowledge and processes, this will fail, badly. The core knowledge needed is not only on AngularJS (or any other framework); you need to have a deep and solid understanding of JavaScript itself. Here’s my preferred list of resources I usually propose to my teams and trainees:
 

Books


And, of course, EE will be here to back you up. Personally I keep and eye on the AngularJS topic itself so chances are that I might be the one to help you out.

Define a strategy


In everything we do we should define a strategy, even if it’s a shallow one; something is always better than nothing.

It's very important that you don’t start changing code all over the place otherwise you’ll end up with an uncontrolled number of regressions and frustration will kill you.

Do things incrementally and you’ll even be able to keep adding features to your app as you perform this migration process. This is a very important selling point when you need to do this migration as part of a technical task and the business sees no real added value upfront.

It’s also very important that you don’t block the delivery of new features. You must keep delivering added value to your application alongside with your technical debt tasks. Of course, there’s always a bit of a compromise here, and you won’t be able to fit as many features in your sprint but don’t stop it completely.
 

Here’s the approach I like to follow:
 

Phase 1: Split the controllers


One good way is to keep the single-page paradigm aside at first and migrate the views, one by one to Angular. Doing it like this, the pages will keep being delivered by as usual from server-side but all the dynamic elements will be rendered with Angular. The site will keep on working and some views will be Angular others are legacy code and changing views will still trigger a full page refresh.


MVC in the back-end?


During this process, you’ll be “forced” to clean-up your server-side controllers and make new ones that return JSON instead of HTML. These will be the AJAX endpoints of your Angular service that will handle all the $http requests. You’ll end up with one controller method to deliver the HTML view and a bunch of other methods that implement the necessary CRUD. I advise to prefix all the CRUD routes with /api/ as this will help you isolate the routes that return JSON from the ones that return HTML.

No pressure, take your time. Meanwhile, new screens and improvements that you have in the pipeline should be done directly in Angular.


No MVC in the back-end?


If you’re not using MVC on the back-end, the process should be more or less the same as described above anyway with the exception that you might want to use another technology to handle your AJAX requests. Here are some options depending on your server-side technology:

 
The list goes on, and each platform has multiple options; choose your own!

Phase 2: Start managing state with Angular


This part will take you some time to figure out and debug.

Basically, you’ll need to move all the user states to Angular, more specifically into a separate Angular service/factory that will be used by all Angular controllers. It’s this mechanism that will allow you later to hit F5 in the browser and still get the needed logged in user information and permissions. You achieve this by adding a controller on the server that you can do an AJAX call and retrieve this information.


Phase 3: Add ng-route


Once all the views are converted and are able to manage their states, it’s time to add ng-route to the mix. It’s not mandatory, but this should ideally be a one-shot mission for all views. If all the above is completed, it won’t take you that long.

What you need to do here is to drop all the controller methods that render HTML leaving only one that delivers the main page. Move all your views routing logic to Angular leaving only the API routes on the back-end (if you prefix them like I said above, they will be easy to identify).


Phase 4: The login page


In all this, I never mentioned the login page; that was on purpose.

I leave this part to the end because having to do a full page refresh every time you login/logout will be useful to clean up some nasty global and badly written JavaScript you might have.

Moving the login page into the single-page framework means that you don’t do a form post to login, you do it by an  AJAX request and you need to populate your previously prepared state service (Phase 2). You also have to clean this informantion once you logout or you will get a 401 from the server and redirect to the login view. 

The pitfalls list


During the process described above, you’ll face a lot of challenges and each time I had to do this exercise I kept a list of the problems I encountered and how I solved them. Below you’ll find this list.
 

PF1: Converting the page JavaScript file into a controller


Of course, you’re already using JavaScript in your site, so what should you do with it?

If you already use RequireJS or any other AMD mechanism then it’s easy because you already have your dependencies and encapsulation figured out. Each RequireJS module represents either a Controller or a Factory or Service in the Angular world so all you need is to slightly change the way you declare it.

On the other hand, if you didn’t pay the appropriate attention to the way you handle your JavaScript then this will be a harder task. The time you’ll spend migrating each page into an AngularJS view will greatly depend on how well structured your current JavaScript is. 

PF2: Splitting code into factories


All reusable code should, or better, must be moved into a Factory or Directive. Good examples of this are states or data-related methods or code for popups that are used in multiple views. 

PF3: Think when it’s a good idea to split functionalities into several modules


Modules in AngularJS are a great way to encapsulate logic or even to abstract enterprise level logic that can later on be reused in other applications. This way you can easily decouple dependencies and reuse the code later on. Thinking this ahead is a good idea; it won’t slow you down and will save you big refactorings later. 

PF4: Be aware of library version conflicts


I recall this problem related to jQueryUI based FileUpload widget. We were using this JQueryUI widget that needed a jQueryUI version higher than the rest of our controls.

I agree this is a bad thing to have as it is, but this was a legacy site and the client had implemented this upload functionality on a separate page and didn’t want to update the other pages. All of the sudden, we were overriding one version of JQueryUI with another and breaking the whole site.

Another similar problem I also had with another site was with JQuery itself. Different versions of JQuery were being loaded by different components. In the Multi-Page world, this is a bad practice but hardly a breaking problem, but as we moved to single-page we had a conflict because the main page itself never changes. Be aware of this and enforce one single version for your components across the whole application. 

PF5: jQuery events bound to the document


This is a JQuery best-practice that will make you lose some time. The problem is that, in a multi-page web application, each time you change page, you start on a clean plate. In a single-page scenario, the document is always the same, events bound to it will remain there even when the view changes. If we come to a view the event binds are set, let's say for a click of a button, moving to another page and coming back will make the events to be bound again resulting in multiple event handlers for the same event.
 

Example:


$(document).on('click', '#btnNext', function(){ ... });

Open in new window



Solution:
Use ng-click and handle your code in the controller. This is the correct way to do it and should always be the preferred solution.

Now there might be cases where you want to just postpone conversion to ng-click so here are some temporary solutions:

Temporary Solution 1
If the event handler can actually be bound directly to the element… do it.

Temporary Solution 2
If we need to make a delegate event handler, wrap each view with a div. Move the event handlers from the document to this wrapper div.

Temporary Solution 3
Sometimes, the same view/controller can be used in more than one parent view. A good example of this is popups and the correct solution is to wrap them in Directives but, if for some edge reason at the moment you can’t...

In this case we can’t use the main view wrapper div because it’s not always the same, so for these edge cases we remove the handler before adding it again:


var gotoNextPage = function(){ /* do your stuff */ };
                      $(document).off('click', '#btnNext', gotoNextPage);
                      $(document).on('click', '#btnNext', gotoNextPage);

Open in new window



Avoid this and any of the above temporary solutions if you can. 

PF6: jQuery based controls/widgets will be a pain in the… neck


Some of the jQuery widgets will fail to work properly, especially due to event-related problems. The ones that work won't comply to the Angular way of doing things.

Reserve substantial time for this if you use a lot of these things (honor mentions to DatePicker, Dialog, jqGrid). A lot of alternatives already exist for most of them and you’ll see that wrapping them yourself with a Directive is usually not a bad alternative.


So, before starting the migration process, identify all your controls that might need to be worked out. If you work on them before starting a page migration, you’ll feel much more confident estimating the effort. 

PF7: The Login page is still a separate page?


This will require a lot of refactoring, rewriting and rethinking. Here’s a small list of pain points:

  • Hiding the menus and everything else while you’re in the login page
  • Correctly redirecting/refreshing the view when you login/logout
  • Handling the session information when you login/logout and press F5
    • Rethink everything related to the session data that was being handled server-side like user information, authorization, contextual data, etc…
  • Security, Some views will need to be requested from the server without security validation
    • The main change in the framework will be that you really need to stop concerning about hiding pages from the user. Your main concern is to secure data, not the HTML, and data is still coming and going to the server. Secure it there!
  • Make sure that all the states are rebuilt client-side if the user presses F5.
    • This often requires a separate AJAX call to get the user extra information based on the cookie that you already have.
  • Move code into a security factory
    • Make sure all this code is “grouped” in a single reusable factory that is shared across all views.
 

PF8: Slowly start to get rid of all jQuery selectors


This is kind of the end of the line for me. When you have the view fully working but jQuery is still being used inside your Angular Controllers. Take the extra time to get rid of them. The ultimate goal is to have zero jQuery selectors inside your Controllers. Choose to either replace them with native Directives and Services or create your own.
 

Things like 
$.ajax({})

Open in new window

 or 
$('#mybutton').on('click', function(){});

Open in new window

 must be banned from your code and replaced by $http and ngClick respectively 

Final notes


I’m not addressing any migration considerations here specific to Angular 2.0 but, apart from syntax changes, the overall considerations remain the same.

This article was based on my personal experience and I’m sure many other challenges are missing here. If you happen to have something to add, please drop me a line and I’ll be happy to discuss it with you and perhaps, enrich this article.
2
10,530 Views
Alexandre SimõesSoftware Architect
CERTIFIED EXPERT

Comments (2)

Commented:
Thanks for the article. It explains quite a bit. I am new to Angular and what i understood from my basic reading is that you cannot keep some views in Angular (latest is 5 ) and have other views and controller in existing architecture. Is there a workaround to this?
Alexandre SimõesSoftware Architect
CERTIFIED EXPERT

Author

Commented:
Hi Pari,
Angular changed quite a bit since it's v1 and meanwhile I started other projects, with other techs, and haven't been hable to catch up.
All this to say that I'm not able to give you experieced advice here regarding Angular 5.

By "existing architecture" you mean AngularJS 1.* way of doing things?
If so, you might find this page very usefull: https://angular.io/guide/upgrade.

Cheers!
Alex

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.