Dependency Injection: ElementRef

We use dependency injection daily to inject services in our Angular “objects,” such as components, directives, pipes, and other services.

Angular does offer other kinds of injectables, though, and one of those is the ElementRef. Its primary purpose is to give us direct access to the native DOM element of a component or a directive.

Here’s a code example (you can test it in Stackblitz here):

The above code changes the background color of the HTML element to red. It’s important to note that the above implementation is not ideal. We should use Angular data bindings for such a use case, which are a lot more readable (code example in Stackblitz here):

The Angular team doesn’t recommend using this API either:

So when should we use ElementRef? Just like markForCheck, it’s a last resort option, and we should use it when everything else fails. The only use case I would consider for ElementRef is when I have to interact with a non-Angular 3rd-party library, such as a chart or a map library, after carefully checking that Angular wrappers are not available on npm (or such wrappers do not pass my tests for good, reliable dependencies)

RxJs eslint plugin for Angular

Last month, I discussed eslint and how to use that tool to parse your code and receive feedback on best practices and possible improvements.

Today, I want to mention an eslint plugin explicitly written for RxJs with Angular: eslint-plugin-rxjs-angular.

This plugin adds three possible validation rules:

All rules are optional, and it doesn’t make sense to use all of them at once because these best practices are contradictory in the sense that the goal is for you to choose one of these three approaches and be 100% consistent with it:

  1. The first rule will enforce that you always use the async pipe in your components.
  2. The second rule doesn’t care about the async pipe but wants to ensure you unsubscribe on destroy.
  3. The third rule is the most specific, as it enforces that you always use a Subject with takeUntil to unsubscribe in your components.

I’d suggest using the first rule only because we covered before that the async pipe is the safest and most performant option. And remember that you can always use the async pipe. There are no excuses!

Special Angular Event Next Week

If you follow the Angular team on social media (Youtube channel or Twitter/X), you probably saw that the team scheduled a special live event for next week. The team went as far as changing their logo on these channels to tease us:

In the past few months, we have all noticed what has become known as the renaissance of the framework, with new features such as standalone components, Signals or the new control flow API that is happening next week with the release of Angular 17.

All these changes embody the spirit of making Angular easier to learn by removing the need to know about some of the most difficult concepts (ngModules, RxJs, change detection, etc.) when getting started. All I can say is that it’s going to be a great week to be an Angular developer!

Click here to get notified when the live event starts on Youtube.

Async pipe, Signals, and Change Detection

Yesterday, we covered what markForCheck() can do in terms of change detection and why it could be needed at times.

Regarding best practices, I often mention using the async pipe or Signals to improve change detection and performance in our apps. Why is that? That’s because both the async pipe and Signals have some extra code to help Angular detect changes.

Here’s what we can find in the source code of the async pipe:

The last line of code indicates that whenever an async pipe receives a new value, it marks the corresponding view for check, triggering the need for change detection. That’s the reason why the async pipe works well even when used in an OnPush component: Its internals override the behavior of whatever change detection strategy we use.

Signals do something similar by marking their consumers (or “subscribers,” in a sense) as dirty when the Signal value changes:

As a result, and back to the point I was making yesterday, Instead of trying to use markForCheck() directly in our code, we should rely on the tools designed to do it automatically and in an optimized fashion for us.

Conclusion: Always use the async pipe with RxJs (you can always do so) or use Signals for an even better experience.

ChangeDetectorRef.markForCheck()

The following method call is either something you’re either very familiar with or something you’ve never heard about: ChangeDetectorRef.markForCheck()

If you’ve never heard about it, congratulations! It means you’ve been using Angular in a way that doesn’t require you to mess with change detection, and that’s an excellent thing.

On the other end of the spectrum, if you see that method in many places in your code, your architecture might have a problem. Why is that? As we covered before, there are two types of change detection: Default and onPush.

When we use onPush, we’re telling Angular: “This is a component that relies on @Input values to display its data, so don’t bother re-rendering that component when @Input do not change”.

The thing is, sometimes people start using onPush full of good intentions. Then, they start injecting services into their component, and when the data in those services change, the component does not re-render… So, instead of disabling ChangeDetection.OnPush (or figuring out a better way to keep their component as a presentation component), they go to StackOverflow, where people say: Just use ChangeDetectorRef.markForCheck(), and problem solved! Here’s a typical screenshot of such a response:

That’s because ChangeDetectorRef.markForCheck() does exactly that: It’s a way to tell Angular that something changed in our component, so Angular must check and re-render that component as soon as possible. As you can guess, this is meant to be an exception and not the rule.

The main reason why we would use ChangeDetectorRef.markForCheck() is if we integrate a non-Angular friendly 3rd party library that interacts with the DOM and changes values that Angular cannot see because Angular doesn’t manage that DOM. In other scenarios, we should avoid using it and think about a better architecture.

Perf and template syntax – Example 3

Yesterday, we looked at the pros and cons of our second code example.

Today, let’s cover the pros and cons of our third and final example:

Example #3 – Signal

<div *ngFor="let data of dataSignal()">{{data.name}}</div>

This last example is very interesting for a few reasons. First, if you follow Angular best practices, you might be screaming that we’re calling a method in a template, and you would discard that code immediately.

But the thing is, this is a Signal, and Signals are different. It’s perfectly fine to call a Signal in a template, and as a matter of fact, this is the way to do it so Angular knows where Signals are read and can then optimize DOM updates based on that information.

Even better, when we use a signal with ngFor, the subscriber to that signal is actually a view (an instance of ng-template) and not the entire component. When Signal-based components become part of the framework, Angular will be able to re-render just that view instead of the entire HTML template of that component.

What are the cons, then?

The only downside of Signals is that they require Angular v16+, so you’ll need to upgrade your apps to use them. The other downside is that the full benefits of improved change detection are not there yet and won’t land in v17 either, but the Angular team is making steady progress, and some early parts of Signal-based components were released in version 16.2.

Other than that, Signals are the way to go. if you’re just getting started with Angular and building a new app, I believe you should use Signals as much as possible.

Perf and template syntax – Example 2

Yesterday, we looked at the pros and cons of our first code example.

Today, let’s cover the pros and cons of our second example:

Example #2 – Observable

<div *ngFor="let data of getData() | async">{{data.name}}</div>

This code is calling a method in a template, too, and that’s a lot worse than in example #1. Here’s why: If the getData() method returns an Observable by calling httpClient.get() (or a service that makes that same call) then the async pipe subscribes to it and receives the data, which triggers change detection, so Angular calls getData() again, gets a new Observable, the async pipe subscribes to it, and there you have an infinite loop of HTTP requests that will bring down your browser for sure, and possibly your server.

How to fix it?

<div *ngFor="let data of data$ | async">

Store the Observable in a variable instead of calling a method, and the problem is solved. You can assign that Observable in the constructor of your component as follows:

constructor(service: DataService) {  
   this.data$ = servie.getData();
}Code language: JavaScript (javascript)

At this point the code is pretty much optimal because:

The only con is the async pipe syntax and working with Observables, which is often considered the most complex part of the Angular learning curve.

Perf and template syntax – Example 1

Yesterday, I sent you 3 different examples of Angular binding syntaxes and asked you to take a look at them and identify the pros and cons of each one.

Today, let’s cover the pros and cons of our first example:

Example #1 – Array of data

<div *ngFor="let data of getData()">

The only pro of this example is the simplicity of the syntax. Other than that, the syntax uses one of the anti-patterns covered earlier in this newsletter: Calling a method in a template. The problem is that this method will be called a lot more than you’d think, and if that code happened to create a new array every single time, it could be really bad in terms of performance, as Angular would have to re-render the entire list pretty much every time the user does anything in the application.

What can we do to improve it?

  1. Use a variable instead of a method – this fixes the performance issue right away:

    <div *ngFor="let data of dataList">
  2. Use a trackBy function to avoid useless re-renders. By telling Angular what’s the unique ID of each list item, Angular would be less confused, but it’s a sub-par solution:

    <div *ngFor="let data of getData(); trackBy: trackById">

Conclusion

If you want to iterate over data that’s in your component (not an observable or a Signal), then use a variable instead of calling a method. Easy enough.

Performance and template syntax

Today, I’d like to challenge you with a small exercise. Take a minute to examine the three following code examples and decide which option performs best. Note how similar those three options are from a syntax standpoint, yet they yield very different results performance-wise:

Example #1 – Array of data

<div *ngFor="let data of getData()">{{data.name}}</div>

Example #2 – Observable

<div *ngFor="let data of getData() | async">{{data.name}}</div>

Example #3 – Signal

<div *ngFor="let data of dataSignal()">{{data.name}}</div>

Feel free to list the pros and cons of each approach. You can even respond to that email with your answers if you want. Tomorrow, I’ll give you my insights.

Angular Change Detection Illustrated

I see a lot of confusion around Angular change detection. I compared the OnPush and Default strategies in a past post, but let’s illustrate them with visuals.

Default Change Detection

An event happening anywhere in the DOM tree will have Angular check the entire tree for changes:

OnPush Change Detection

If a hierarchy of components is using OnPush, then only that branch on OnPush components will be checked for changes:

Signal Change Detection

This is the future of Angular. When using Signals, only the views of components that use that Signal will get updated, making it the best and most accurate option: