I’ve been using Angular since version 2, and it has come a long way since those days to what it is right now. I’ve worked on various Angular projects over the years, yet I keep finding new things. It goes to say how massive the framework is. Here are some things I wish I had known about Angular when I started so you don’t have to learn it the hard way.

I’ve had my fair share of mistakes when it comes to structuring the application. As you follow tutorials, you’re guided through where you should put your files and which modules the components or services belong to. However, when you venture beyond the tutorial, you sometimes end up with a structure that doesn’t scale well. This could lead to issues down the road.

Below are some mistakes I’ve made that came back and bit me.

The release of Standalone Components in Angular 14 makes NgModules no longer a requirement when creating components. You can choose not to use modules for your components, directives, and pipes. However, you could still follow the folder structure outlined below, omitting the module files.

Initially, I put all the components into the default module you get when creating a new Angular app. As the application grew, I ended up with a lot of components in the same module. They were separate components and didn’t have any need to be in the same module.

Split your components into separate modules, so you can import and load only the required modules. The common approach is to divide your application into the following modules:

Dividing the application into separate modules helps partition your application into smaller, more focused areas. It creates clear boundaries between the different types of modules and each feature module. This separation helps maintain and scale the application as different teams can work on separate parts with a lower risk of breaking another part of the application.

This applies to all types of files in an Angular app. I’ve let my service and component files grow beyond their scope, which made them difficult to work with. The general rule is to keep each component/service/pipe/directive performing a specific set of tasks. If a component is trying to do more than what it was initially made for, it might be worth refactoring and splitting it into several smaller components. This will make testing and maintenance a lot easier.

You’ve probably used the ng serve command either directly in your command line or through a script in your package.json file. This is one of Angular CLI’s commands. However, the CLI comes with more handy commands that can speed up your development especially when it comes to initializing and scaffolding.

Initially, I did most of these manually as I didn’t understand how to use the CLI except for starting and stopping the local server. I would create component files manually, add the boilerplate code, and add them to the right modules. This was okay for smaller projects but became a tedious task as the project grew. That’s when I learned how to use the CLI and use it to automate most of the manual work I do. For example, instead of creating all the boilerplate for a card component, the following command will create them for you:

You can use the CLI by installing it globally via npm using the command below:

To view the available commands, execute the code below:

As I was learning Angular, I tried to keep my project neat by putting all the services into a services folder, models in a models folder, and so on. However, after some time, I end up with a growing list of import statements like this:

Now the import statements above can be re-written as:

An added benefit of using path aliases is that it allows you to move your files around without having to update your imports. You’d have to update them if you were using relative paths.

Now, update the tsconfig.json to point to the index.ts file instead of the asterisk (*).

The import statements can now be further simplified into:

I started by learning JavaScript, so I wasn’t used to the type system and the other features that TypeScript offers. My exposure to TypeScript was through Angular, and it was overwhelming to learn both a new language (although it’s a superset of JavaScript, some differences trip me up every time) and a new framework. I often find TypeScript slowing me down instead of helping me with the development. I avoided using TypeScript features and overused the any type in my project.

Performance is crucial for applications, and Angular provides various ways to optimize your applications. This is often a problem that you won’t run into at the beginning as you are probably working with small data sets and a limited number of components. However, as your application grows and the number of components being rendered grows and becomes increasingly complex, you’ll start to notice some performance degradation. These performance degradations are usually in the form of slowness in the app: slow to respond, load, or render and stuttering in the UI.

Identifying the source of these problems is an adventure on its own. I’ve found that most of the performance issues I’ve run into in the applications are UI related (this doesn’t mean that other parts of the application don’t affect performance). This is especially prominent when rendering components in a loop and updating an already rendered component. This usually causes a flash in the component when the components are updated.

Under the hood, when a change occurs in these types of components, Angular needs to remove all the DOM elements associated with the data and re-create them with the updated data. That is a lot of DOM manipulations that are expensive.

The ngFor directive needs to uniquely identify items in the iterable to correctly perform DOM updates when items in the iterable are reordered, new items are added, or existing items are removed. For these scenarios, it is desirable only to update the elements affected by the change to make the updates more efficient. The trackBy function lets you pass in a unique identifier to identify each component generated in the loop, allowing Angular to update only the elements affected by the change.

Let’s look at an example of a regular ngFor that creates a new div for each entry in the users array.

Keeping most of the code the same, we can help Angular keep track of the items in the template by adding the trackBy function and assigning it to a function that returns the unique identifier for each entry in the array (in our case, the user’s id).

Data transformations are inevitable as you render data in your templates. My initial approach to this was to:

Angular provides built-in pipes for common data transformations such as internationalization, date, currency, decimals, percentage, and upper and lower case strings. In addition, Angular also lets you create custom pipes that can be reused throughout your application.

The data transformation above can be re-written using a pipe as follows:

The pipe can then be used in the template by using the pipe (|) character followed by the pipe name.

Angular applications are made up of a tree of components that rely on their change detectors to keep the view and their corresponding models in sync. When Angular detects a change in the model, it immediately updates the view by walking down the tree of change detectors to determine if any of them have changed. If the change detector detects the change, it will re-render the component and update the DOM with the latest changes.

In addition to the reduced number of change detection cycles and its performance boost, the restrictions imposed by using the OnPush change detection strategy also make you architect your app better by pushing you to create more modular components that utilize one of the three recommended ways mentioned above to update the DOM.

RxJS is a part of Angular that has been largely unexplored for me as I was learning the framework. I’ve avoided using it unless I had to. It was a new concept, and I found it quite hard to wrap my head around it. I’ve worked with JavaScript Promises, but observables and streams are a new paradigm for me.

After working for a while with Angular, I eventually took the time to learn and understand RxJS and try to use them in my projects. It wasn’t long before I realized the numerous benefits of RxJS that I’ve been missing out on all this time. RxJS, with its large collection of chainable operators, excels in handling async tasks.

The example below shows a memory leak introduced by listening to the route params observable. Every new instance of MyComponent creates a new subscription which will continue to run even after the component is destroyed.

As mentioned above, you can fix the memory leak by either calling unsubscribe or using the takeUntil operator.

Another common source of memory leaks is event listeners that aren’t unregistered when no longer used. For example, the scroll event listener in the code below gets instantiated on every new instance of MyComponent and continuously runs even after the component is destroyed unless you unregister it.

To fix this and stop listening to the event after the component is destroyed, assign it to a variable and unregister the listener on the ngOnDestroy lifecycle method.

There is no correct solution for state management as every project’s requirements are different. Luckily, there are a few state management libraries for Angular that offer different features. These are a few of the commonly used state management libraries in the Angular ecosystem:

If you’ve just started to learn Angular and it hasn’t quite clicked yet, be patient! It will eventually start to make sense, and you’ll see what the framework has to offer. I hope my personal experience can help you accelerate your learning and avoid the mistakes I’ve made.