Best practices to know in Angular
Here we have gathered a list of best practices to follow if you want to make a good application with clean and maintenable codebase. It's also important to know the bad practices to avoid first but then the list of good practices will help you to bring you application to the next level of quality.
- adopt a strict and smart naming conventions for files and folders*, for example create folders:
containers, components, models, mocks, configs, services, factories, utils, guards, pipes...in each feature module. And then postfix each file in these folders with the name of the folder in the singular.
- group model files because usually those are simple and easy to read so it's better to avoid creating one file per model, instead group models by types of models. For instance for a product feature:
product.payload.model, like that it's easy to find a type of model and read all the models associated in one file.
- use of base components and base services in order to facilitate development and maintenance (migration, testing/mocking...), it is preferable to centralize these dependencies using wrapper, example of base components:
BasePresentationalComponent, BaseContainerComponent, BaseModuleComponent. Exemple of base services:
BaseNavigationService(for proxying all the route navigation, manage back),
BaseHttpService(for proxying all the http requests),
BaseUiService: (for proxying all the ui interactions such as modal, toaster...)
BaseStoreService(for proxying all the state management services).
- manage race conditions for asynchronous operations such as http requests, indeed user can click multiple times on the exact same button or another similar button in a list and in the meantime the network can be slow so the request will not complete fast enough and this will trigger unexpected behavior on the app.
- create your presentational components with a polymorphic implementation in order to be able to reuse the same components but with a different interface.
- avoid duplicating the objects in the store and prefer the use of reference identifier and selectors in order to have a lighter and more robust store, indeed the risk of desynchronization increases if you have several times the same object has different places. It is also recommended to keep your state structure flat for instance you can do that using the lib called normalizr.
- use the programming pattern called "strategy pattern" to avoid ending up with switch cases all over the application. It is therefore recommended to use inheritance for components but also for services.
- usage of
feature togglein environment files or from a configuration endpoint of your API in order to be able to enable or disable a new features in each environment, it can be very useful if a feature is buggy or not finished.
- usage of
templatewith team best practices and todo for the
gitlab MRs+ the
jira tickets. Use as much as possible generic model for the processing like that the team will be used and will naturally start to follow those best practices.
- improve the testability by making a backdoor to test all the different conditional templates in the UI.
- test the screen with any kind of data, for instance a short text and a long text in order to check the layout responsiveness.
- extract the conditional templates logic in a component function, for instance if you have to display a text depending on many conditions then make a
switch(true)function to handle this. In some case if the template is big enough then you can use a
ngSwitchor a library for dynamic component in order to render different components depending on the condition.
- instead of using ::ng-deep for overriding the child components style from the parent component a better approach is to make use of CSS variables, it's a cleaner, maintainable and scalable approach.
- generate DTOs that matches 100% the back-end service, ideally generate it automatically from the swagger api and never touch it to be able to regenerate automatically in the future. Then create manually a front-end model for the service output that will probably extend the DTO and format some fields or even add additional useful fields on the front-end. Like that your DTO will always match the backend and your model will always be available to add additional new fields on the front-end without messing up the DTO with optional fields. In addition to that you should use read only on all the fields or a global deep read only in order to make sure your data is immutable.