Unless otherwise specified, developers should follow the indications included on those documents.
Most rules are enforced with ESLint and won't be mentioned in this document, make sure to integrate a linter in your development environment.
Consistent coding style is important in any development project, and particularly when many developers are involved. A standard style helps to ensure that the code is easier to read and understand, which helps overall quality.
Abstract goals we strive for:
- tool friendliness
Note that much of the existing code may not follow all of these guidelines — we continue to upgrade this code when we see it.
Disabling ESLint rules
In some situations, it may be necessary to disable ESLint rules using inline comments. Although this is discouraged, it is allowed on certain use-cases.
Most of the time, however, this could be solved by refactoring code. So think twice before disabling a rule.
Warnings should be treated with the same severity as errors, even if they are allowed by the linter. The reasoning behind this is that warnings are useful when new rules are introduced that affect existing code, but new code should always conform to the rules or explicitly disable them.
Using async / await
Using async/await is encouraged, but it shouldn't be mixed with .then/.catch/.finally. Using both can make code difficult to understand. As a rule of thumb, there should only be one style in a given function.
Async/await is syntactic sugar for Promises, so it should always be possible to avoid using .then/.catch/.finally.
To prevent making asynchronous operations difficult to spot, using await should be limited to simple statements such as one liners, assignments and if guards with a single condition.
Using if guards is encouraged to reduce indentation levels. They should handle edge cases and leave the main indentation level for normal code.
More concretely, when an if block contains a non-conditional ending statement, it should not chain other blocks.
Avoid abusing user-defined type guards
User-defined type guards are an advanced TypeScript feature that can be very useful for working in heavily typed applications. However, they can also be confusing for newcomers, so they should only be used when they are really necessary.
An alternative to defining type guards is to use built-in operations to perform type checks. For example, the following code would also take advantage of TypeScript inference:
In some situations, the only solution is to use type assertions. Although this approach is simpler to understand and more straightforward, it is dangerous because we lose inference checks:
The spread operator is allowed, but it's recommended to include a comment explaining what it is doing to make the code easier to understand. You can also replace it for simpler alternatives.
It is encouraged to use string interpolation using backticks if it makes the code more readable.
Avoid declaring variables using commas
In order to have cleaner diffs, it is not allowed to declare variables using commas. This also results in a better alignment of variable names, making the code more readable.
Avoiding having too many optional arguments
In some situations, functions end up having a lot of optional arguments and this results in unreadable code and a cumbersome developer experience (having to pass multiple null or undefined values).
When these situations arise, a good approach to solve it is using an options object instead.
As a rule of thumb, when a method has more than two optional arguments that are not required to be used together, use an options object (better naming can be used for each particular scenario).
Using declaration files
Declaration files can be very useful in TypeScript and it is encouraged to use them when appropriate. But it's not recommended to abuse them either, here's some situations when it may be a good idea to use them:
- Declaring types for external dependencies — Libraries that don't include their own declarations and are missing from DefinitelyTyped (the packages you find under
- Global declarations and extensions — Any variable you add to the window object can be declared by extending the
Windowinterface. The same idea applies for extending external dependencies.
- Local declarations — Sometimes, it may be useful to create a dedicated declaration file when source files are growing too large. But this technique should not be used to substitute proper code organisation.
In contrast, constants that are private or protected should be declared as static readonly class properties. Also, avoid calling them using
this.CONSTANT form (given that they are static members):
Avoid calling methods in templates
Method calls should be avoided in template rendering, this includes structural directives such as
Angular templates can be rendered very often, and calling methods on every render could cause some unintended performance issues. For that reason, it is usually safer to rely on values rather than methods.
In some situations, a simple method that only returns a value would be acceptable, but it opens the door to become an issue if the method is refactored to do something more complicated. That's why it is discouraged to use methods altogether.
Of course, this doesn't mean that you can't use any methods on a template. Not every method used on a template is called in every render.
For example, using methods in event handlers is fine:
A warning about using getters
However, Angular doesn't include a built-in pattern for these situations, so these properties should be managed as part of the logic for the component.
Be careful when using getters, which may give the wrong impression that a method is not being called:
Even if this looks like using a property in the template, it is still calling a method in every render.
Maximise the number of attributes per line
There is a maximum line length of 140 characters for templates. Whenever that length is surpassed, the attributes should be distributed in multiple lines trying to reduce the number of total lines, instead of dedicating one line per attribute.
If you are using VSCode, this should be done automatically on every save with the configuration that ships with the app.
Avoid default exports
Using default exports should be avoided for Angular applications because they cause issues with AOT compiler. Technically only components have this problem, but in order to avoid the mental load of thinking about this every time, we disallow it altogether.
Declaring page modules
When creating a page component, it should be declared in the feature's lazy modules. Exceptionally, pages that are used by more than one module can create a page module; but this module should only declare components, it shouldn't include any routing functionality.