ngionic

Me and my team at Comland are developing a hybrid mobile app (with Ionic) that basically uses SQLite as a data source to build a totally dynamic form that users then fill out. Forms are build out of sections and sections are build out of fields. Each field is a separate custom directive (like input, dropdown, etc.) that receives the setup object (ui.config) on init. The directives are totally reusable and they carry all the information to render (template, logic, validation, special functions, etc.).

But… Some of the forms that we build with this app can easily reach 100 + fields per page so we often encounter a frozen UI. Our code that outputs the form was organized in three ng-repeat loops in the form itself and we tried to replace them with quick-ng-repeat, use bindeonce and built in ::, but no serious improvement was made. UI still stucked. So there was a huge need for optimizing AngularJS app.

From the start our concept of building the form was something like this:

I was looking for a way to render Angular templates before they are compiled. Our ng-repeats that build the template layout and logic were slow, so I was thinking that the only thing that would boost things up is to create a directive that builds the dynamic HTML and then calls $compile on it.

So I came up with a clever solution…

Precompile static templates

First of all each field directive uses its own template to render the directive. Whenever we start to output the form we have a convenient loading screen, where we prepare everything that is necessary for the form to function properly. This process takes about 3-4 seconds to complete. So this stage of the process is ideal to add some other logic to it, as the user is totally cool to waiting a few seconds before the form displays.

So the first optimization was, to use $http  to load all the field templates into Angular $templateCache  and also calling the $compile  on the template html, but without the scope parameter. This actually produces a compiled template which I then store into a JS object (aka. “the cache”) under the key of template name. See the sample below.

Then the magic comes in… Inside my directives I reuse my pre-compiled template, clone the element and apply the directive scope to it. This renders the directive without having to recompile it every time. Instead the compiled template is just cloned and reused every time. This technique alone improved the overall performance of the form UI for about 50%. See the code sample below.

This improved the app performance significantly (for about 50%), but the UI was still frozen for 1500ms every time a large form was loaded. So I started looking at the ng-repeats . As I said we had three of them to display the form.

What I didn’t say it that after the three ng-repeat  loops we also had a custom directive that accepted field ui.config and then loaded the required directive for this field inside. But this happened in a separate digest loop . So every time we rendered a single field a new digest loop was run. This created an interesting profile in Chrome Timeline and Profile. In fact there were digest cycles and link functions everywhere, when the UI got stuck.

Reusing Dynamic AngularJS Templates

The solution was to apply the above logic to a larger and totally dynamic content of our form templates. One thing that is always true for my app is that form definition stays the same while the user is filling it out. So it is build dynamically, but its definition is always the same. I used this fact to modify custom directive that was responsible for calling field directives dynamically. I moved all three ng-repeat  loops inside this directive, so this time the directive would receive the definition for the entire form, instead of just field and it creates a dynamic HTML for that specific form. Once again I run $compile on the form and save the output to an object.

Form templates don’t have to be cloned as only one form of the same type exists in any given moment, so I can just show and hide it using ng-if .

This boosted the performance for another 49%. The UI does not freeze anymore and the app runs great.

I can’t show the code for the last part, as I would then be required to hack the shit out of you 😀 Just kidding! It’s part of the app core, so it’s a deep secret.

Thanks for reading.

P.S.: I also recommend reading this awesome SlideShare presentation.