This is a simple introduction to the best practices you can apply to your Angular project. Note that these best practices can be applied to other languages, but our focus in this article will be Angular with TypeScript.
What are, in fact, best practices and readable code? This is not a science and my opinion can be different from yours. The aim of this article is to provide you with information and some tips which allow for deciding the best approach in each scenario. These tips had a lot of good feedback from conferences, colleagues and clients. Thus, I hope I can help you.
Let´s start with a question that you should answer with total sincerity. Do your code reviews look like this?
If so, how do you make your code easier to read?
Simple! You only need to follow some advices that this article will give you. Again, this is the way I work and I love it. You can have a different opinion than me and be totally valid as well. So, let´s really start this time 😊
Today, the Angular CLI is the best way to build Angular Applications. To install globally run the follow command:
The CLI has scaffolding (aka schematics) tools for creating new projects, and generating new code for you, but this isn´t the main benefit. The main benefit of the CLI is the way it automates the build pipeline for both live development with ng serve, as well as for production code that you would ship down to browsers with ng build -prod.
After this first advice of a tool that in my opinion is indispensable, let´s start the topics that show how important the LIFT principle is.
This principle helps you to find code quickly. So, if you don´t use this principle, ask yourself: How quickly can you open and work in all of the files that you need to do some features? My advice: Respect and use LIFT.
It´s very important that you give good names to methods, variables and parameters.
This example of code shows that it´s hard to read and understand this method’s behavior. But if you look to the next example you note that it´s easier.
5 seconds rule
If you can´t understand in 5 seconds you probably need a refactor. Relax, I know that you are thinking that I am crazy, but when I say 5 seconds is a figure of speech. The goal is understanding that your code needs to be easy to get for everyone.
Organization for readability
- The most important stuff goes first
- Properties followed by methods
- Grouped and sorted
- Consistent naming and spelling matter
- One item per file
- One file must have only one component and the same is valid for services or directives.
- Single Principle Responsibility
- Single class or module should only have a single responsibility
- Symbol naming
- Properties and methods must be camel case (e.g. currentUser)
- Classes (components, services, directives…) must be upper camel case, called Pascal case (e.g. UserComponent)
- Components and services should have the respective suffix on the name
- External imports come first
- Give a blank line before the import files from own app
- This give us an easy way to identify the external and own files
- Small Functions
- Small functions are better to read and faster to understand the purpose. If your function has more than 10 lines you need to ask yourself if it isn’t better to break it into smaller functions. This might be hard to do, but unfortunately in the real world we see a lot of huge functions that are hard to read due to the size and complexity. In my opinion this is an important practice that everyone should follow.
Provide clarity through code first
- Self-describing code
- Replace magic strings with constants (code reuse)
- Explain in code, not in comments
- Comments must be readable and maintained
- Avoid comments when:
- Explains “What” the code does
- Outdated and incorrect (wrong comments are worse than no comments)
- Having a well named function
- THE CODE NEVER LIES, COMMENTS SOMETIMES DO
- Use comments when:
- Explain “Why” you do that
- Explaining consequences
- API Docs
Angular components best practice
- Prefixing your components is recommended. If you use Angular CLI to create the component, by default the CLI will prefix your component like this: selector: ‘app-component-name’. You can decide if you let the ‘app’ as prefix or change to the name of the project; or if you are using a Feature module section, you can use the name of each feature module to distinguish each component by prefix. For example, if your project name is ‘DIGITALSTORE’ you can put ‘ds’ as prefix. (‘ds-component-name’).
- Separate the HTML, CSS and TypeScript files is also a good practice. This allows us to have more organized and readable files.
- You can declare the input decorator inside of component decorator, near the selector, template, etc. But this is not advisable. The good practice here is to declare inside of the class and at the beginning like this:
- For the output decorator the rule is exactly the same.
- Delegating complex logic to services
- We want our components to be as simple as possible. This means if our component needs to do some complex logic we need to decide if that logic belongs to the component or not. If we are talking about one or two lines of logic, maybe it´s ok to leave it in the component. In my opinion the components don´t need to have the responsibility to do some of the logic. We can leave this to a service, so that our component works like a postman who receives a package and knows it has to send it to someone without needing to know the content of the package. This is a final decision that you need to make in your own app.
- Component member sequence
- Public methods must be declared before private ones. This is easier to read or find, because in this way we can prevent private methods lost in the middle of lots of public methods. Note that by default all methods are public, so to declare private methods you need to write the private keyword before the name of the method.
Implementing life cycle hook interface
We must declare the life cycle hook interfaces to alert us if we are not using the interface that we declare. For example, when we use the ngOnInit if we declare the implementation like this:
then, if we do not create this we will receive an alert telling us that we are missing the ngOnInit:
Angular Services Best Practices
- Make services as injectable
- This is necessary only when a service injects another service, but is recommended use every time, because you never know when the service will need to inject another one and it will be hard to remember that we decided not to use the injectable decorator. Also, you never know who the next developer will be (might actually just be you).
- We can use the @Inject inside of the constructor and remove the global @Injectable but this is not recommended unless its needed. For example, if the service that you are injecting is not using the service datatype as this service. This requirement is rare and usually we will not need this. The recommendation is to use @Injectable always. Also, we will need more code, because we will need to do this for all the parameters.
- Using services for data retrieval
- Like we talked above, in this case you should use the Single Principle Responsibility. Our component must call a service to get some data. For example, we can call directly an API in the component… ok the code is simple, but to respect this principle it´s a best practice to put this logic in a service. The service can call an API, localStorage or also a dummy structure that helps us during development but for our component this should be transparent. Tomorrow, we change the call on our service and for the component everything remains the same. The component shouldn´t have to think how to get the data and it shouldn´t have to know if the data comes from an API or localStorage. The responsibility of the component is only to know that he had the necessity to call the service and only that. The service has the duty to know where and how to get the data. So, please, avoid the temptation to call the API directly on your component.
Concluding, to improve the performance of your app I recommend that you consider the use of Ahead-of-time compilation, lazy loading and paying attention to bundle sizes. In my next articles I will go deeper into these, so stay tuned!
by André, Web Developer and Angular enthusiast @ Lisbon Nearshore