Yulia Galiulina
Yulia Galiulina

Reputation: 149

How to properly manage a boolean signal input in a custom Angular element (v19)?

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

Answers (4)

JSON Derulo
JSON Derulo

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

Nicola Piccoli
Nicola Piccoli

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

Francesco Borzi
Francesco Borzi

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

user16021941
user16021941

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

Related Questions