- Published on
Angular Signals - Part 3
- Authors
- Name
- Iraianbu A
Table of Contents
Introduction
In the previous part, we covered the basics of Angular Signals. Check Angular Signals Part 1 and Angular Signals Part 2 for more details. Here we will cover the advanced topics of Angular Signals with Inputs, Queries and Linked Signals.
Signal Inputs
Signal inputs that allow component values to be bound from parent components. These values are exposed using a signal, which can change throughout the lifecycle of your component.
@Component({...})
export class AppComponent {
firstName = input.required<string>(); // Required input
lastName = input<string>(); // Optional input
age = input<number>(0); // Default value
currentAge = input<number>(0, { alias: 'studentAge' }); // Default value with alias
}
Signal Queries
The signal-based queries offer a way to retriev and interact with elements, components or directives in a component's template (view) or projected content.
Angular provides two main types of queries:
View Queries : These retrives results from the component's own template. It let us retrieve elements or components that are part of the component's template.
Content Queries : These retrieve results from the component's projected content.
viewChild
The viewChild
function retrieves a single element or component either by string reference or by component/directive type.
@Component({...})
export class AppComponent {
// Decorator
@ViewChild('rxjsInteropComponent') rxjsInteropComponent!: RxjsInteropComponent;
// viewChild by component/directive type
component1 = viewChild(RxjsInteropComponent); // Signal
component1 = viewChild(RxjsInteropComponent, {
read: ElementRef,
}); // ElementRef
// viewChild by element type
divElement = viewChild('rxjsInteropComponent');
}
viewChildren
The viewChildren
function retrieves multiple elements or components either by string reference or by component/directive type.
@Component({...})
export class AppComponent {
elements = viewChildren(RxjsInteropComponent);
}
contentChild
The contentChild
function retrieves a single element or component from the component's projected content.
@Component({...})
export class AppComponent {
headerElement = contentChild(HeaderComponent);
}
<app-content-elements>
<app-header />
</app-content-elements>
contentChildren
The contentChildren
function retrieves multiple elements or components from the component's projected content.
@Component({...})
export class AppComponent {
elements = contentChildren<ElementRef>('h1Elements');
}
<app-content-elements>
<div #h1Elements>
<h1>This is another heading element</h1>
<h1>This is another heading element</h1>
</div>
<app-header />
</app-content-elements>
Options
descendants
: This option allows you to retrieve elements or components that are descendants of the component.
read
: This option allows you to specify the type of the element or component.
Required Queries
If a child query (viewChild
or contentChild
) does not find a result, its value is undefined
.For such cases, you can mark child queries as required to enforce presence of at least one matching result. If no result is found, Angular will throw an runtime error.
@Component({...})
export class AppComponent {
headerElement = contentChild.required(HeaderComponent);
}
Query Availability Timing
When angular builds a component, there is a period where the signal query has been created but the template has not been fully processed. During this time, the queries will return :
undefined
(for single queries)- An empty array (for multiple queries)
Query Declarations and Usage
We can declare signal-based queries in class properties. These functions should not be called in other parts of the component (e.g., constructor
).
Linked Signals
linkedSignal
is a utility that allows us to create a signal that is linked to another signal. It behaves like computed
signals with a writable side.
linkedSignal
is like computed
but with 2 main differences :
The derived value of
linkedSignal
can be overridden at any time.The
linkedSignal
provide access to the previous values
Options
source
: The source signal that the linked signal is linked to.computation
: It is a function that receives the new value of source and a previous object. The previous object contains the previous value of source and the previous value of the computation.equal
: It is a function that compares the new value of source and the previous value of source.
import { Component, linkedSignal, signal } from '@angular/core'
@Component({
selector: 'app-linked-signals',
imports: [],
template: `
<ul>
@for (reaction of reactions(); track $index) {
<li [class.selected]="isSelected(reaction)" (click)="selectedReaction.set(reaction)">
{{ reaction }}
</li>
}
</ul>
<span>Selected Reaction: {{ selectedReaction() }}</span>
<button class="btn btn-primary" (click)="addReaction()">More Reaction</button>
<button class="btn btn-primary" (click)="removeReaction()">Less Reaction</button>
`,
styles: ``,
})
export class LinkedSignalsComponent {
reactions = signal<string[]>(['๐คฉ', '๐', '๐ค'])
// selectedReaction = linkedSignal(() => this.reactions()[-1]);
selectedReaction = linkedSignal<string[], string>({
source: () => this.reactions(),
computation: (source, previous) => {
return source.find((reaction) => reaction === previous?.value) ?? source[0]
},
})
addReaction = () => {
this.reactions.set(['๐คฃ', '๐', '๐', '๐ฏ', '๐ซ '])
}
removeReaction = () => {
this.reactions.set(['๐คฉ', '๐', '๐ค'])
}
isSelected = (reaction: string) => {
return this.selectedReaction() === reaction
}
}