An Overview of Signal APIs in Angular
We’ve already looked at the basic primitive building blocks of signals in
Angular which are: signal
, computed
, and effect
.
However, we are not just given these bare bones primitives to make use of ourselves. Angular is also further integrating these into Angular itself and exposing core functionality via signal APIs.
input
We have already seen one example of this: input
. In the case of inputs, we
don’t need to actually create any signals ourselves. We just use the input
function provided by Angular to define an input, and then we get that input
given to us as a signal within the component.
This is particularly useful in the case of inputs, because we could then use other signal primitives in combination with that.
For example, if we want to derive some other value from an input:
name = input.required<string>();
reversedName = computed(() => [...this.name()].reverse().join(""))
NOTE: I am making use of required
here which is a way to force an input to
be supplied to the component — if we don’t supply a name
input to this
component it would cause an error.
Now we take the value of name
and create a new computed signal that reverses
the name. The cool thing is that whenever name
changes our reversedName
will
update automatically.
What if we wanted to run some side effect when name
changed? We can easily do
that too:
name = input.required<string>();
constructor(){
effect(() => {
console.log(this.name())
})
}
Now every time the name
input changes, our effect
function will run.
View Queries
We will be diving more into the specifics of view queries as we use them in the
applications we will build, but this is another story very similar to the
migration from @Input
to input
.
In some cases, we might want to grab a reference to some element in our template.
In a previous lesson, we saw that we could create a template variable like this:
<p #myParagraph></p>
If we wanted to grab a reference to that in our class we could use a “view child” to do that. Previously, with the old decorator based approach, that would have looked like this:
@ViewChild('myParagraph') myParagraph;
With the new signal based API it looks like this:
myParagraph = viewChild('myParagraph');
Again, it is the same story as input
where myParagraph
is now provided as a
signal
which means we would access its value like this: this.myParagraph()
.
Also, just as with inputs, we can do things like create derived values from this
signal using computed
or run side effects with effect
.
As well as viewChild
we also have viewChildren
for retrieving multiple
references, and then we also have contentChild
and contentChildren
for
situations where we are dealing with “projected” content. We are going to make
use of this a little later, but the general idea with “projected” content is
that rather than having some content directly in the template of a component
like this:
<div>
<p>I am directly in the template, I'm a view child!</p>
</div>
We might want to supply content dynamically to a component — this is sort of
like supplying an input
but it’s part of the template. To do this, our child
component would use ng-content
like this:
<div>
<ng-content></ng-content>
</div>
Now there is no p
tag directly in the template; it is not a “view child”. But
now, in the parent component, we can dynamically pass in this part of the
template:
<app-my-comp-with-content-projection>
<p>I am passed in from the parent, I will be a content child!</p>
</app-my-comp-with-content-projection>
The end result in the DOM will be the same, e.g:
<div>
<p>I am passed in from the parent, I will be a content child!</p>
</div>
Everything from within the opening/closing tags of the component in the parent
component gets “projected” into the child component wherever the ng-content
tag is placed.
model
A model
looks deceptively like an input. One basic use case it enables is that
it allows a component that defines an “input” to also modify the value of that
input.
For example, in this case:
import { Component, input } from '@angular/core';
@Component({
selector: 'app-welcome',
template: ` <p>Hi, {{ name() }}!</p> `,
})
export class WelcomeComponent {
name = input('friend');
}
The name
input can only be changed by passing the value in through the parent
component:
<app-welcome [name]="user.name" />
If we wanted to set
the value of name
from within the WelcomeComponent
we would not be allowed to do that as an input
signal is read-only:
import { Component, input } from '@angular/core';
@Component({
selector: 'app-welcome',
template: ` <p>Hi, {{ name() }}!</p> `,
})
export class WelcomeComponent {
name = input('friend');
resetName(){
// does NOT work
this.name.set('');
}
}
However, model
will allow us to do that: