Angular Misconceptions
Tags: angular, javascript, typescript
Published: 2023-03-06 09:05:36

_Original cover photo by [Kendall Ruth](https://unsplash.com/@iamkendall) on Unsplash._

Angular is a huge framework with lots of built-in functionality and lots of features and tweaks. While the documentation and a myriad of training resources explain how it works and all of its components in great detail, there are still some misconceptions about certain aspects of Angular that are worth clearing up.

So, in this article, we will explore concepts that are sometimes misinterpreted, even by the most seasoned Angular developers.


## 1. Zone.js is doing change detection

"You need zone.js to have change detection, without it, your application will be frozen in time" - this is what often is said to newcomers in Angular, and it is *entirely* true. But... that is only true in the most basic sense. Zone.js is not doing change detection, it is just enabling it. 

Let's understand how it works in detail:

1. Angular updates the UI only if some changes have been met to the application state.
2. Angular assumes (quite correctly) that any change to the application can occur **only** when something asynchronous happens (e.g. a user clicks a button, or a network request completes, or a `setTimeout` fires, and so on).
3. Zone.js monkey-patches all the asynchronous APIs in the browser, so those can be hijacked and made to dispatch notifications
4. Angular listens to those notifications, and if something happens, it triggers a top-down change detection process, moving through components, finding out what changed, and, if needed, updating the UI.

So in essence Zone.js tells Angular "something async happened, so some app state might have changed (maybe not though)", Angular hears this, starts checking component data, and, if a change is found, updates the UI

> Note: this is an oversimplification, in reality, Angular does some other stuff during change detection runs too, but those are out of the scope of this discussion.

So, change detection is a process completely detached from Zone.js. It is possible that we have used `ChangeDetectorRef` to manually trigger change detection, like this:

```typescript
import { Component, ChangeDetectorRef, inject } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <h1>My App</h1>
    <p>Counter: {{ counter }}</p>
    <button (click)="increment()">Increment</button>
  `
})
export class AppComponent {
  private readonly cdRef = inject(ChangeDetectorRef);
  counter = 0;

  increment() {
    this.counter++;
    this.cdRef.detectChanges();
  }
}
```

Now if we go to the `angular.json` file and remove `zone.js` from the `polyfills` array, and then add `{ngZone: 'noop'}` to the `bootstrapModule` options in `main.ts`, our app will no longer ship Zone.js, but this component will continue to work as expected. Let's inspect an excerpt from the Angular's `async` pipe's source code:

```typescript

@Pipe({
  name: 'async',
  pure: false,
  standalone: true,
})
export class AsyncPipe implements OnDestroy, PipeTransform {
  private _ref: ChangeDetectorRef|null;
  private _latestValue: any = null;
  private _obj: Subscribable<any>|Promise<any>|EventEmitter<any>
   |null = null;

  constructor(private ref: ChangeDetectorRef) { }

  transform<T>(
    obj: Observable<T>|Subscribable<T>|Promise<T>|null|undefined,
  ): T|null {
    if (!this._obj) {
      if (obj) {
        this._subscribe(obj);
      }
      return this._latestValue;
    }

    if (obj !== this._obj) {
      this._dispose();
      return this.transform(obj);
    }

    return this._latestValue;
  }

  private _subscribe(
    obj: Subscribable<any>|Promise<any>|EventEmitter<any>
  ): void {
    // this method performs subscriptions to
    // Observable or Promise
    // and then calls _updateLatestValue
    // omitted for brevity
  }

  private _updateLatestValue(async: any, value: Object): void {
    if (async === this._obj) {
      this._latestValue = value;
      this._ref!.markForCheck();
    }
  }
}
```

So, as you may see, the `transform` method actually doesn't do much, it just subscribes (if not already subscribed) and then returns the latest value. The subscription is done by the `_subscribe` method, which in its turn, well, subscribes, and calls `_updateLatestValue` when a new value is emitted. And this is where the magic happens: the `_updateLatestValue` method calls `markForCheck` on `ChangeDetectorRef`. So, the `async` pipe is not using Zone.js at all and is manually triggering change detection. 

Thus, if you had a hypothetical application that only ever uses RxJS `Observable`-s with the `async` pipe, you can kinda drop `zone.js` from your app, and it will still work.

> Note: the code example from the `async` pipe is incomplete and simplified, read more [here](https://github.com/angular/angular/blob/main/packages/common/src/pipes/async_pipe.ts) 

> Yet another note: Angular is adding Signals, so in the future, this might be a whole other kind of discussion

## 2. OnPush change detection works only when inputs are updated

Over the course of my own career, I have done multiple interviews for Angular developer positions, and approximately 90% of the candidates I interviewed had a misconception that the `OnPush` change detection strategy works only when component inputs have been changed. Let's first view a simple counterexample:

{% embed https://stackblitz.com/edit/angular-x2fh8j?embed=1&file=src/child.component.ts %}

In this short example, the `text` property of the `ChildComponent` is set to "some text", and when the button is clicked, it is updated to "other text". The `ChildComponent` is using the `OnPush` change detection strategy, and has no inputs, but that does not prevent it from updating the UI when the button is clicked.

So what does `ChangeDetectionStrategy.OnPush` actually do? What it does is

1. First and foremost, it removes deep-checking on change detection, meaning if we pass inputs to a component with `OnPush` change detection, it will not readily update (it might update in the future). So to make sure updates to the UI are correct, we would need to change the reference to the input object. Let's examine this in another example:

{% embed https://stackblitz.com/edit/angular-cavicm?embed=1&file=src/main.ts %}

So, in here we have two components: `AppComponent` and `ChildComponent`, and the child one has an array as an `Input`. Now, in the parent component, there are two buttons, one is called "Push to array from parent", and the other one is "Change array reference". Now clicking the first one (and only it) will not update the UI. Clicking the second one, however, will yield immediate results. Meaning with `ChangeDetectionStrategy.OnPush` only referential equality is checked in the case of objects, and no deeper checks are being performed. 

2. In the child component, the rules are a bit different, because if we click the "Push to array" button, located *in the child component*, we would get an immediate update - that is because with `OnPush`, local events trigger a change detection cycle. 

3. Changes are not gone - now this time we can try clicking the "Push to array from parent" button, maybe even several times, and *then* clicking the "Push to array" button in the child component. Now we will see an update with several items added to the array - that is because the changes are there, just have not been detected until we triggered them from the child component.

Of course, some other nuances are present, and you can read about more of them [here](https://blog.angular-university.io/onpush-change-detection-how-it-works/). But as we have already seen, the `OnPush` change detection strategy is not limited to inputs, and it is not limited to referential equality checks either.

## 3. Calling methods in templates is a crime

We have heard it a lot: **DO NOT CALL METHOD IN ANGULAR TEMPLATES**. But why should it be like this?

Well, the main reason is that because the value of the method (whatever it returns) is not readily available, Angular has to run call the function to extract the value and see if the UI needs to be updated, which in turn might result in a costly computation. Notice the wording here: *costly* computation. So what about not-so-costly computations? Consider these two examples:

```typescript
@Component({
  template: `
    {{a + b}}
  `,
})
export class AppComponent {
  a = 1;
  b = 2;
}
```

```typescript
@Component({
  template: `
    {{sum()}}
  `,
})
export class AppComponent {
  a = 1;
  b = 2;

  sum() {
    return this.a + this.b;
  }
}
```

In both cases, we are going to get the same result, and the computation isn't really that bad. As a matter of fact, in both cases, Angular is going to perform the same computation, and the only difference is that in the second case, Angular is going to call the `sum` method, which will add some very negligible milliseconds of adding the function to the call stack and so on to the process. 

Another interesting piece of Angular trivia: some built-in properties that exist on different Angular classes are, in fact, getters (thus functions), but we keep using them in templates without any issues. Here is a fun example: `FormControl.valid`, which we might often use in a template, [is actually a getter](https://github.com/angular/angular/blob/main/packages/forms/src/model/abstract_model.ts#L483). (also all the other properties there are getters too), that just returns a simple computation result.

So what are the rules?

1. Simply one rule - avoid costly operations, iterating over arrays, calling APIs, and so on. Everything else can be fair game.

> Note: when signals are introduced and stable, the best approach would be to use a computed property to avoid **all** unnecessary function runs


## 4. Pipes should be pure

This is the continuation of the previous point: pure pipes (which all pipes are by default) will only calculate a new result when the input properties change. Impure pipes will run on every change detection cycle. But then again, the same argument can be made here: depends on the calculation. Sometimes we need impure pipes - especially when working with reactivity, and some built-in pipes are already impure, most famously the `async` pipe. So, impure pipes are **not** a bad practice.

## 5. We can use an `EventEmitter` as a `Subject`

`EventEmitter` is well known in the Angular community - after all, it is one of the first ways of communicating between components that we learn. If we look at the [source code](https://github.com/angular/angular/blob/main/packages/core/src/event_emitter.ts#L105), we can see that it extends RxJS `Subject`, and if we try, we can see that we can not only call the `emit` method on its instance but also subscribe to it and in general use it as an `Observable`. But, in reality, we shouldn't really do that. The problem is, the Angular team does not confirm that an `EventEmitter` will always be an `Observable`, so they might change this in the future. So, if you are using an `EventEmitter` as a `Subject`, you might be in for a surprise in the future. As a matter of fact, with signals being introduced, there is already an ongoing discussion of maybe changing the mechanism by which components can emit events.

## 6. We should always use Reactive Forms

Reactive Forms are pretty good - most of the functionality they provide is really helpful. But they come with a mental model that is harder than just using `NgModel` - and that can slow down development in some cases. If anything, a better approach would be to use template-driven forms for simple forms, such as ones that do not really require complex validation and save Reactive forms for really heavy cases. 

## In Conclusion

Angular is a really rich ecosystem - and there are a lot of things that we can learn from it. But, as we have seen, there are lots of conflicting ideas out there - so in this article, I tried to sort some of them out and maybe clarify some things. If you know other things that Angular developers tend to misuse/misunderstand, feel free to share them in the comments below.