Reputation: 149
I have a web component created with Angular 19 prerelease version. It has a liked
signal input, which can be set from a parent but can't be modified from within because signal inputs are read only. To manage toggling the state, I introduced a local signal _liked
, which I can toggle and emit its value to the parent. This feels like an overkill, I'm not sure if this is a good way of handling the state.
I need to transform the input value into a boolean, because when the custom element is used outside Angular context, say in a Vue app, the resulting html will render my input as a string and strings are always truthy.
Do you have any suggestions on improving the following code?
export class LikeButtonComponent {
liked = input(false, { transform: booleanAttribute });
_liked = signal(this.liked());
likedChange = output<boolean>({ alias: 'likedchange' });
constructor() {
effect(() => {
this._liked.set(this.liked());
});
}
toggleLike() {
this._liked.update(value => !value);
this.likedChange.emit(this._liked());
}
}
I looked into the model()
and it doesn't seem to be suitable here because it doesn't expose a transform option. The problem is that when values are passed into a web component they are passed as strings, like this <like-button liked="true"></like-button>
.
Upvotes: 2
Views: 2033
Reputation: 17768
Since Angular 19 you can use the new linkedSignal()
function which is basically a computed
that is writable at the same time. It allows you to skip the effect:
export class LikeButtonComponent {
liked = input(false, { transform: booleanAttribute });
_liked = linkedSignal(() => this.liked());
likedChange = output<boolean>({ alias: 'likedchange' });
toggleLike() {
this._liked.update(value => !value);
this.likedChange.emit(this._liked());
}
}
As of Angular v19,
linkedSignal
is in developer preview and might change before becoming stable
Upvotes: 3
Reputation: 135
It looks like everyone of us is going around the same thing, so I'll try with my approach :)
import {Component, computed, model, output} from '@angular/core';
@Component({
selector: 'app-like',
standalone: true,
imports: [],
template: `
<p>Like status is: {{ this.derived_like() }}</p>
<button (click)="changeState()">Toggle like</button>
`
})
export class LikeComponent {
like_state = model<boolean | string>(false);
derived_like = computed<boolean>(
(): boolean => {
if (typeof this.like_state() === 'string') {
return this.like_state() === 'true';
} else {
return <boolean>this.like_state();
}
});
emit_like = output<boolean>();
changeState() {
this.like_state.set(!this.like_state());
this.emit_like.emit(this.derived_like());
}
}
For the input I used a model so that I can change the state from inside.
I checked the type of the input in the computed derived_like so that I don't need to transform the input and I'm sure that the output will always be a boolean.
Upvotes: 0
Reputation: 61994
Since using model()
instead of input()
is not an option in your case, I would refactor your current solution by getting rid of effect()
:
export class LikeButtonComponent {
liked = input(false, { transform: booleanAttribute });
_liked = computed(() => signal(this.liked())); // resets if the external "liked" changes
likedChange = output<boolean>({ alias: 'likedchange' });
toggleLike() {
this.likedChange.emit(this._liked());
}
}
Inspired by: https://www.youtube.com/watch?v=aKxcIQMWSNU&ab_channel=TechStackNation
Upvotes: 1
Reputation:
Your approach with the local _liked signal is understandable, but you can refine it for better simplicity. Convert the liked attribute from a string to a boolean directly within toggleLike. This way, you don't need _liked and can update and emit changes from a single place.
export class LikeButtonComponent {
// Directly transform 'liked' signal input
liked = input(false, { transform: booleanAttribute });
likedChange = output<boolean>({ alias: 'likedchange' });
toggleLike() {
const newLikedValue = !this.liked();
this.likedChange.emit(newLikedValue);
}}
Upvotes: 0