---
# System prepended metadata

title: Angular New Control Flow Internals

---

# Angular New Control Flow Internals

In v17 Angular introduced a new control flow. In this article we will explore how it works and how it is different from the old one.

## What is control flow?

Control flow is a mechanism that allows you to control the execution of your code. In Angular, it is used to control the execution of the template. For example, you can use `*ngIf` to conditionally render a part of the template.

## Old control flow

In the old control flow, Angular used structural directives to control the execution of the template. Structural directives are directives that change the structure of the DOM. For example, `*ngIf`, `*ngFor` is a structural directive.

Let's take a look at the following template:

```html
<button (click)="count.set(count() + 1)">Increase</button>

<div *ngIf="count()">
    {{ count() }}
</div>

@if (double()) {
    <div>
        {{ double() }}
    </div>
}
```

In the first example, we use `*ngIf` to conditionally render a part of the template. In the second example, we use `@if` to do the same thing. Now, let's take a look at the generated code:

```js
function AppComponent_Template(rf, ctx) {
    if (rf & 1) {
        elementStart(0, "button", 0);
        listener("click", function AppComponent_Template_button_click_0_listener() {
            return ctx.count.set(ctx.count() + 1);
        });
        text(1, "Increase");
        elementEnd();
        template(2, AppComponent_div_2_Template, 2, 1, "div", 1)(3, AppComponent_Conditional_3_Template, 2, 1, "div");
    }
    if (rf & 2) {
        advance(2);
        property("ngIf", ctx.count());
        advance(1);
        conditional(3, ctx.double() ? 3 : -1);
    }
},

function AppComponent_div_2_Template(rf, ctx) {
    if (rf & 1) {
        elementStart(0, "div");
        text(1);
        elementEnd();
    }
    if (rf & 2) {
        const ctx_r0 = nextContext();
        advance(1);
        textInterpolate1(" ", ctx_r0.count(), " ");
    }
}
function AppComponent_Conditional_3_Template(rf, ctx) {
    if (rf & 1) {
        elementStart(0, "div");
        text(1);
        elementEnd();
    }
    if (rf & 2) {
        const ctx_r1 = nextContext();
        advance(1);
        textInterpolate1(" ", ctx_r1.double(), " ");
    }
}
```

The generated code for the views (`AppComponent_div_2_Template`, `AppComponent_Conditional_3_Template`) is identical, except the count vs double part.

If we focus on the template creation on the creation pahse (`rf & 1`), we can see that both `*ngIf` and `@if` are compiled to a `template` instruction. The `template` instruction is used to create a view. 

```js
if (rf & 1) {
    ...
    template(2, AppComponent_div_2_Template, 2, 1, "div", 1)(3, AppComponent_Conditional_3_Template, 2, 1, "div");
}
```

So, as we can see the new control flow also uses templates to control the execution of the template.

Let's focus on the update phase (`rf & 2`). In the update phase, we can see that `*ngIf` is compiled to a `property` instruction. The `property` instruction is used to update the value of a property. In this case, the `ngIf` property is updated with the value of `ctx.count()`. The `@if` is compiled to a `conditional` instruction. The `conditional` instruction is used to create or destroy a view. In this case, the view is created if `ctx.double()` is truthy, otherwise, it is destroyed.

```js
if (rf & 2) {
    ...
    property("ngIf", ctx.count());
    ...
    conditional(3, ctx.double() ? 3 : -1); // 3 is the index of the view in the LView array
}
```

When the `ngIf` property is updated, Angular will check if the value has changed. If the value has changed, Angular will create or destroy the view, using public API-s like `createEmbeddedView` and `destroyEmbeddedView` from the `ViewContainerRef`. 

While with the conditional instruction, Angular will directly use the internal API-s like `addLViewToLContainer` and `removeLViewFromLContainer` to create or destroy the view.

## Performance

The new control flow is faster than the old one. The main reason for this is that the new control flow uses internal API-s to create or destroy views. But also, because Angular doesn't have to set the property value, and then check if the value has changed. Instead, Angular directly creates or destroys the view based on the condition, skipping a few steps.



![Template with *ngIf (**refreshView** takes **1.49ms**, CPU: 4x slowdown)](https://i.imgur.com/jG1qpaj.png)
Template with *ngIf (**refreshView** takes **1.49ms**, CPU: 4x slowdown)


![Template with @if (**refreshView** takes **0.82ms**, CPU: 4x slowdown)](https://i.imgur.com/0m6zZmd.png)
Template with @if (**refreshView** takes **0.82ms**, CPU: 4x slowdown)

As we can see in just this simple case the new control flow is ~45% faster.

## Migration
In order to get the best performance in your app make sure to ng update, but to also run:

```bash!
ng g @angular/core:control-flow
```

It will migrate your application code to use the new control flow instead of the old one.

## Thanks for reading!
I tweet and blog a lot about **Angular** (latest news, signals, videos, podcasts, updates, RFCs, pull requests and so much more). 💎

> If you want to learn more about **Modern Angular** features like standalone, signals, functional guards, interceptors, ssr, hydration, new inject method, Directive Composition API, NgOptimizedImage, feel free to take a look at our [Modern Angular Workshop from Push-Based.io](https://push-based.io/workshop/modern-angular?source=enea-blog) 💎
> 
If this article was interesting and useful to you, and you want to learn more about Angular, give me a follow at [@Enea_Jahollari](https://twitter.com/Enea_Jahollari) or [Medium](https://eneajahollari.medium.com/). 📖
I'd appreciate it if you would support me by [buying me a coffee ☕️](https://ko-fi.com/eneajahollari). Thank you in advance 🙌