How to handle Signal inputs in the new control flow for conditional rendering in Angular 17.2?

211 Views Asked by At

I'm struggeling with the control flow and a Signal Input in Angular 17.2.

I have this input in a component:

index = input<number|null>();

And I have this template. Since index can also be 0 I need to explcitly check for null and undefined:

@if (index() !== null && index() !== undefined) {
  <div appMyComponent [index]="index()!"></div>
}

Now I need ! to make it work since index() can be undefined or nullagain, at least for the compiler.

I can't use @if (index() !== null && index() !== undefined; as index) { } since index would be a boolean.

How to solve this correctly?

1

There are 1 best solutions below

0
maxime1992 On

Lets start by understanding why when you do:

@if (index() !== null && index() !== undefined) {
  <div appMyComponent [index]="index()!"></div>
}

You have to use !.

Your input is defined as such:

index = input<number|null>();

In order to consume an input, you have to call it, because it is a function. It's exactly what you've done.

Do you always get a similar result when you call an impure function? No, it can be different. Hence why the compiler won't consider your 2 checks enough to then consider that the index you're passing as an input in your @if is not null.

It's simple to reproduce this in Typescript and see what the compiler tells us:

enter image description here

We clearly see here that the value is either number, null and can even be undefined (this is due to the fact that it's not a required input, so if it's not set at all, the value will be undefined).

Now, how do deal with this?

Let think about that solution in Typescript first and see how we can transpose that within the context of an angular template.

When calling a function can give you a number | null | undefined, if you want to make checks on the value and have your compiler know that from this point, you've got something that isn't null nor undefined and that therefore it's a number: Save it in a variable, and make the checks on that variable. Because you're not calling the function again which could give a different result, the compiler is then able to infer that the type is number after the checks:

enter image description here

Final step: How to transpose that solution to an Angular template?

Well, there's a little trick that it was possible to use the old syntax too (*ngIf). I wrote a blog post about it 4.5 years ago.

Here's the bottom line of it. The following is totally valid:

<div
  *ngIf="{
    value1: value1$ | async,
    value2: value2$ | async,
    value3: value3$ | async
  } as data"
>
  {{ data.value1 }} {{ data.value2 }} {{ data.value3 }}
</div>

What's interesting about it, is that the *ngIf will always be true, as what it receives is an object. So you can store observable values into variables, wrap integers so that *ngIf doesn't consider the 0 as falsy, etc.

With the new @if syntax, it works the same. And here's in your case how you can achieve what you want:

@if({ index: index() }; as data){
  @if(data.index != null){
    <sub-sub-component [index]="data.index"></sub-sub-component>
  }
}

Now that we call the index function only once, store it in a variable, make an assertion on that variable, Typescript is able to narrow down the type accordingly.

I have made a Stackblitz repro for you to play with where you'll be able to see the 2 experiments, one test with your current setup and the other with the one I just shared. Your code has a compiler error, the one I shared works fine: https://stackblitz.com/edit/stackblitz-starters-wfvrva?file=src%2Fmain.ts