Is it possible to use a computed
signal to type guard within the template? I am using Angular 17.0.0 (17.1.0 is required for input
signals, which we can't change for this version of the library).
I have two tests within this component, one using a signal isItem
, and one using a function isItemFn
.
isItem
doesn't seem to allow me to typeguard this.item
.isItemFn
does allow me to typeguard this.item
.Since the value of item
can be one of three things (more to come later), I need a way to apply type guards in the template and I was hoping that signals would help but so far they have not. Is there a way to do this without using a function or a pipe?
@Component({})
export class CompositeChipsDisplayComponent<T> {
@Input() item!: T;
// Doesn't allow for type guards in the template
isItem = computed(() => this.item instanceof CompositeChipsItemDirective);
// Does allow for type guards in the template (but runs too often)
isItemFn(item: unknown): item is CompositeChipsItemDirective {
return item instanceof CompositeChipsItemDirective;
}
}
<!-- Types work within the `if` statement when using a function -->
@if (isItemFn(item)) {
<button class="item">
{{ item.label }}
</button>
}
<!-- Types do not work within the `if` statement when using a signal -->
@if (isItem()) {
<button class="item">
{{ item.label }}
</button>
}
Here is what the output looks like in my editor:
The problems with your code are follows.
You are using instanceof CompositeChipsItemDirective
which converts the computed to a boolean
signal, instead you should just return the signal after casting it to the directive type.
You are using a computed in the HTML, but you are again using the original input inside the template accessing the label. Instead using the @if as
syntax and use the type coming from the computed signal, and it should work fine.
import { Component, computed, Directive, Input, Signal } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import 'zone.js';
@Directive({
selector: '[someDirective]',
standalone: true,
exportAs: 'someDirective',
})
export class CompositeChipsItemDirective {
label = 'test';
}
@Component({
selector: 'app-child',
standalone: true,
template: `
@if (isItemFn(item)) {
<button class="item">
{{ item.label }}
</button>
}
@if (isItem(); as itemCasted) {
<button class="item">
{{ itemCasted.label }}
</button>
}
`,
})
export class CompositeChipsDisplayComponent<T> {
@Input() item!: T;
// Doesn't allow for type guards in the template
isItem: Signal<CompositeChipsItemDirective> = computed(
() => return this.item instanceof CompositeChipsItemDirective ? this.item as CompositeChipsItemDirective : null;
);
// Does allow for type guards in the template (but runs too often)
isItemFn(item: unknown): item is CompositeChipsItemDirective {
return item instanceof CompositeChipsItemDirective;
}
}
@Component({
selector: 'app-root',
standalone: true,
imports: [CompositeChipsDisplayComponent, CompositeChipsItemDirective],
template: `
<app-child [item]="test"/>
<div #test="someDirective" someDirective></div>
`,
})
export class App {
name = 'Angular';
}
bootstrapApplication(App);