Angular Enterprise
Best practices to know in Angular
- Courses
- 17 practices
- 5 min read
Here we have gathered a list of best practices to follow if you want to make a good application with a clean and maintainable codebase. It's also important to know the bad practices to avoid first, but then the list of good practices will help you bring your application to the next level of quality.
- adopt a strict and smart naming convention for files and folders, for example create folders:
containers, components, models, mocks, configs, services, factories, utils, guards, pipes...in each feature. Then suffix 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.entity.model+product.state.model+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 wrappers. Examples of base components:
BasePresentationalComponent, BaseContainerComponent, BaseModuleComponent. Examples of base services:BaseNavigationService(for proxying all the route navigation and managing the back navigation),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: a user can click multiple times on the exact same button or on another similar button in a list, in the meantime the network can be slow so the request will not complete fast enough and this will trigger unexpected behavior in the app. RxJS operators such as
switchMap,exhaustMaporconcatMaplet you choose explicitly how concurrent requests are handled. - 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 the same object in several places. It is also recommended to keep your state structure flat, for instance with
@ngrx/entityor thewithEntitiesfeature of the NgRx SignalStore. - use the programming pattern called "strategy pattern" to avoid ending up with switch cases all over the application. It is therefore recommended to rely on polymorphism (a common interface or abstract class with several implementations provided through dependency injection) 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 new features in each environment, it can be very useful if a feature is buggy or not finished. - usage of
templateswith the team best practices and a todo list for themerge requestsand thetickets(GitLab, GitHub, Jira...). Use generic templates as much as possible so that the team gets used to them and naturally starts 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 cases, if the template is big enough, you can use the@switchblock (ngSwitchin older versions) orNgComponentOutletin order to render different components depending on the condition. - instead of using
::ng-deepfor 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 match the back-end service 100%, ideally automatically from the OpenAPI (Swagger) specification, and never touch them to be able to regenerate them automatically in the future. Then manually create 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
readonlyon all the fields or a globalDeepReadonlytype in order to make sure your data is immutable. - set up a mock factory to use in all types of tests: unit or e2e. This mock factory is a simple function which returns a default model object and takes a single parameter to partially override it.
- prefer standalone components, the
inject()function and signals (signal,computed,input,output) for new code, and use theOnPushchange detection strategy (or zoneless change detection) everywhere to keep rendering predictable and fast. - use the built-in control flow (
@if,@forwithtrack,@switch) and@deferblocks instead of the older structural directives to get smaller bundles and better performance.