One of the services I often perform is to spend a few hours doing a high-level code review of an Ionic codebase, and then writing up any advice or recommendations I have based on what I see. Most of the time, the request comes from people who have built an Ionic application but the performance isn’t where they want it to be.
I have written and talked quite a bit about Ionic performance in the past, and to get a general sense of my view on Ionic’s performance I would recommend reading: Ionic Framework Is Fast (But Your Code Might Not Be). The key point is that for most cases, Ionic is more than enough to build a high performance application with smooth interactions.
The “problem” is that it is relatively easy to build applications with Ionic, but a high level of skill and knowledge of the underlying technologies is required to do it well, and so it is common to see poor-performing applications built with Ionic. Ionic has a low barrier to entry, but expertise is still required to build good applications. That’s not to say that the opportunity for poor performance is exclusive to Ionic and other hybrid approaches, there are plenty of poor-performing native applications.
The reason that I am writing this article, is because I have done quite a few of these code reviews now, and there are some common issues that seem to pop up in a lot of the codebases I review.
The Problem
The key issue I think that a lot of the codebases I review face are that some of the fundamentals of a well designed Ionic application are missing, and that either creates or masks the underlying issues contributing to poor performance.
The analogy I use is that of a messy room that smells bad. The main motivation might be to get rid of the smell, but that is difficult to do whilst the room is a mess. The mess in the room might not be contributing to the smell at all, but it will make finding the source of the smell difficult. Or perhaps there is no main source of the smell, maybe all the mess in the room just smells a little bit, and all of it together is creating a big stink.
Until the codebase itself is improved from an architectural/organisational/maintainability perspective, trying to diagnose and fix performance issues is likely either going to be a difficult or futile excercise. Finding a mouldy sandwich in an otherwise clean room is much easier.
Recommendations
In the remainder of this article, I am going to list some recommendations to address the common issues that I see. These issues are not always necessarily contributors to poor performance themselves, but can end up creating that “messy room” scenario.
The points I will be listing also aren’t all-encompassing of the best practices for building Ionic applications, rather, these are specifically to address the things that I see happening most commonly.
Keep pages light
The “page” components in our applications are those that we are using to display a “view” like the home page, or a login page, or a profile page. There is a tendency for people to add all of the functionality required to make that page function into the page component itself. This can lead to bloated page components that are hard to follow and perform work that should be performed more modularly.
Instead of putting all of the code into the page itself, the page should only handle basic functionality like handling event bindings in the template (e.g. when the user clicks this button, trigger this function). Any complex “work” that needs to be done should generally be separated out into its own provider/service and then the page can interact with that. This keeps the pages light, and it will also help prevent needlessly implementing the same functionality in multiple places.
Take a login page as an example. I’m just going to make up a silly example here, but hopefully it gets the point across. You might want to add everything you need to handle the authentication process on the login page, and what ends up being creating is something that looks like this:
BAD:
public username;public password;private authToken;private apiKey;private isLoggedIn;private loggingIn;private jwt;private rememberMe;// even more class member variables go herelogin(){}checkPassword(){}storeToken(){}someOtherMethod(){}yetAnotherMethod(){}// even more methods go here
Instead of doing all of this in the login page, you should instead move all of this authentication “work” into its own provider/service/singleton. You can then just make a simple call to that service from the login page, e.g:
GOOD:
public username: string;public password: string;login(){this.authService.authenticate(this.username,this.password).subscribe((res)=>{// do something})}
Not only does this clean up the login page a great deal, but it also means that any of our pages that need to make use of any of the “authentication” functionality can do so through a single service. That means a bunch of other pages might also now be much lighter, and if there is an issue with authentication we can focus on debugging just one file rather than trying to trace what is happening through multiple different pages.
Prioritise simplicity
A lot of the time I will see code that is overly complex. Again, it might not be an issue in itself that is contributing to poor performance, but it increases the complexity of the code overall. If complex code is used all of the time where something simpler would suffice, it can lead to huge files that are hard to follow and work with.
Being able to read through some code and build a mental model of it helps a great deal when trying to understand/optimise your code. If you have methods spanning across 800 lines of code, it is going to be much harder to work with. If you just use if/else
statements and for
loops you can probably achieve most of what you need to do, but it is going to lead to uglier code.
For example, let’s say we are trying to remove all posts from an array that don’t have josh
as the author. We could do something like this:
for(let i=0; i <this.posts.length; i++){if(this.posts[i].author !=="josh"){this.posts.splice(i,1);}}
or you could do something that looks much simpler and cleaner like this:
this.posts.filter((post)=>{return post.author ==="josh";});
This particular example doesn’t result in a massive saving in terms of lines of code, but it is much easier to understand. With the second example, it is easier to see at a glance what we are doing. The first example isn’t too bad, but if you start piling a bunch of solutions like that together things can start to get really messy - especially when we start dealing with loops within loops.
I also see similar issues in complexity in templates where there is complicated structures of grids and lots of manual positioning, where perhaps some simple CSS flexbox
layout could achieve the same results in far less code. In the case of your template, this might even lead to actual performance improvements. A complicated DOM structure (e.g. lots of nodes, lots of nesting) can be a bottleneck for performance in web applications.
Making the code simpler is going to require the knowledge of how to make it simpler, and that is just something that is going to come with experience. But, if you find yourself writing code that seems complicated, see if you can start finding ways to simplify it. I find that I rarely run into situations with Ionic where I need to write code that is large/ugly/complex, even for advanced functionality.
Do it right from the beginning
It is so much easier to keep things clean in the first place than it is to tidy it up afterward. That is probably true of most things, but it is definitely true for code.
A poorly designed and messy codebase will likely just breed more poor design and code. A structured, clean, and well thought out codebase is much more likely to remain that way. Avoid the temptation to rush through just building out the “prototype” as quickly as you can, because often that prototype ends up being iterated upon and becoming your final product. At that point, the rushed and poorly designed code is so baked in that “fixing” it is usually a bigger task than just starting from scratch.
Understand how the browser works
Keeping in line with the idea of doing things right from the beginning, I think perhaps the most important things to keep in mind as you are building is the browser rendering process and the event loop.
This is something that might take a bit to understand (and it is not necessary to understand it completely), but it is so fundamental to creating well-performing web applications that it should be something you keep in mind for every bit of code that you are writing. There are ways to do the exact same thing in JavaScript, except coding it one way will result in smooth 60fps performance, and the other will slow your interface to a janky mess.
For an example of this, take a look at this video: https://www.youtube.com/watch?v=t_mo0QCbRG4.
Never use hacky solutions
Another common issue with problematic code bases is that often “hacky” solutions will be used - either because of a lack of knowledge on how to implement the solution properly or to cut corners/save time.
This creates a snowball effect that will really come back to bite you later. If you solve one thing with a hack, then any behaviour arising from that is probably going to be solved with a similar hacky solution. Eventually, you have hacks upon hacks upon hacks - you don’t even know how the codebase works anymore, and it will break for reasons that are almost impossible to debug.
This means you probably shouldn’t be manually just nudging things a few pixels around to deal with layout issues, e.g:
position: relative;right: 3px;top: 2px;
and you should be really careful whenever you think you need to use setTimeout
:
// Give everything a bit of time to loadsetTimeout(()=>{// now do the thing that wasn't working},3000);
There are legitimate uses for setTimeout
, but if you find yourself reaching to setTimeout
to “fix” a little issue you are having, think about it five times first and then probably don’t do it. It can probably be solved with a better understanding of asynchronous code.
If you ever find yourself thinking you will just fix something quickly now with this little work around and you’ll come back to it later to implement a “real fix”, just do it correctly right away. If you don’t know how to do it correctly, then spend time researching it. Chances are that the solution you implement now will be the one that stays in the application.
Use types
If you are building Ionic applications, then you probably have TypeScript and types at your disposal. I won’t go into how types work and the benefits in this article, but for a bit of an introduction to why you might want to use them with Ionic you can take a look at this video: Using Interfaces in Ionic.
Types required just a little bit of extra effort to add to your code once you understand them, and they help a great deal in increasing code quality and preventing bugs/issues before they happen.
Use automated tests
I think that creating automated tests, and especially using a methodology like Test Driven Development (TDD), is one of the ultimate ways to create a well-designed/well-structured/stable/maintainable codebase. Not only do tests provide confidence that your code is working as expect, but using a rigorous methodology like Test Driven Development will kind of force you to write good code. It is much easier to test good code, so if your code is structured poorly you are probably going to run into a lot of difficulty creating tests until the code is designed better.
When using a TDD approach, it also forces you to slow down and consider what you are building and how you are building it. It can be difficult to learn to write tests, it is going to take longer to build applications this way, but the end result is a much more stable product that will save you more time in the long run. Even if you aren’t creating particularly good tests, even the act of trying to do it will likely result in better overall code.
For an introduction to basic testing concepts, you can take a look at this article: The Basics of Unit Testing.
Summary
As I mentioned before, this is not meant to be an exhaustive list of how you should build your Ionic applications. These are just recommendations to issues that I regularly see when reviewing Ionic codebases.
A lot of the advice I am giving relies on the developer knowing how to do things “better”, and if you don’t have that level of experience yet it is going to be hard to know what to do. Hopefully, this can serve to at least identify some things to be thinking about, and highlight what you might need to learn more about.