Gerard
Gerard

Reputation: 103

Why is setTimeOut,0 used in ngAfterContentInit()?

The purpose of the piece of code added at the bottom is to react to a change in the ContentChildren, which are directives linked to <td> elements. It is taken from a book and there is the following line:

setTimeout(() => this.updateContentChildren(this.modelProperty), 0);

According to me this is a peculiar construction. I don't understand why the author added a Timeout with delay 0. Instead, he could have just updated the ContentChildren with only the method:

this.updateContentChildren(this.modelProperty)

Yet he didn't do it and I don't see why. Could anybody explain?

@Directive({
    selector: "table"
})
export class PaCellColorSwitcher {

    @Input("paCellDarkColor")
    modelProperty: Boolean;

    @ContentChildren(PaCellColor, {descendants: true})
    contentChildren: QueryList<PaCellColor>;

    ngOnChanges(changes: { [property: string]: SimpleChange }) {
        this.updateContentChildren(changes["modelProperty"].currentValue);
    }

    ngAfterContentInit() {
        this.contentChildren.changes.subscribe(() => {
            setTimeout(() => this.updateContentChildren(this.modelProperty), 0);
        });
    }

    private updateContentChildren(dark: Boolean) {
        if (this.contentChildren != null && dark != undefined) {
            this.contentChildren.forEach((child, index) => {
                child.setColor(index % 2 ? dark : !dark);
            });
        }
    }

UPDATE: When I run it without the setTimeOut(,0) function I get an ExpressionChangedAfterItHasBeenCheckedError, but I don't understand why?

Upvotes: 1

Views: 1151

Answers (2)

NoNameProvided
NoNameProvided

Reputation: 8987

First of all, I want to say this is bad practice and you should avoid it. However I am not gonna lie, I have used this trick a few times myself as well.

The previous developer added setTimeout because A, there is probably a race condition between the behavior of various (Angular) components or B, one component changes the state of another component in a change detection cycle (indicated by the error).

The behavior the developer achieved

First, I need to explain what the developer achieves by setting his function into a setTimeout with zero delay. Javascript is a single-threaded language, meaning by default the JS engine does one thing at a time.

The super simplified (and kind of wrong) way of explaining this is that a JS engine has a list of statements to execute in a list and it always picks up the first. When you set something in a setTimeout you put that statement (your function call) into the end of this task list, so it will be executed when everything else "queued" up for execution until that point has been processed.

There is a great video on YouTube about this: What the heck is the event loop anyway?, I strongly encourage you to go and watch this video!

In case of a race condition

It might happen that two of your components has a race condition on each other, to demonstrate let's say the JS engine "list of tasks" looks like this:

  • component A does some stuff
  • component A wants to set some stuff in its child: component B
  • Angular runs a CD cycle and attempt to render your component
  • child component B does some stuff

The problem here is that in step 2 your child component (B) has not been created yet, so attempting to use it will throw an error. By putting the statement which modifies component B in a setTimeout your list of tasks will look like this:

  • component A does some stuff
  • Angular runs a CD cycle and attempt to render your component
  • child component B does some stuff
  • component A wants to set some stuff in its child: component B

This way your B component will exist by the time it has been created.

In case of an inconsistent state

Angular runs a so-called change detection cycle to check what has changed in the application state and how the UI needs to be updated. In developer mode, this check runs twice for the same cycle and the outputs are compared. If there is a difference the framework will throw the mentioned ExpressionChangedAfterItHasBeenCheckedError error to warn you about this.

At this point, you can say to yourself, great this issue won't appear in prod so I am good but you are not good this issue will lead to a worsened performance as Angular will run more change detection cycles than it should because it thinks something has changed when in reality you didn't intend to change anything. So you should track down the route cause of this problem and fix it.

The official Angular website has a dedicated document page for this with a video guide on how to solve the problem. There is also a detailed Stackoverflow answer here about this behavior and why is this checked.

So as for your original question by adding the setTimeout the developer tricks Angular to pass this double-checking because the updateContentChildren function will be executed only after the current change detection has finished. This means your internal state is always "one tick ahead" of the UI as some work is always finished after the CD cycle has finished.

Upvotes: 2

Kevin D&#225;vila
Kevin D&#225;vila

Reputation: 26

There is probably a reason for that. setTimeOut is an asynchronous function, which will be executed when the synchronous functions finish. Some time ago I used a library that rendered components asynchronously, but it had a bug and put a component on the right. So I wanted to change that with a style from typescript, but doing so did not see any changes. Why? Because my code was synchronous, the library positioned my component on the right after I placed it on the left. The simplest solution was to put the style in a setTimeOut of 10. And it worked

Upvotes: -1

Related Questions