How to use Angular Signals

Simplify Angular Development With Reactive Properties

Β·

7 min read

How to use Angular Signals

Angular Signals are hot right now! Even if you still need to start using them because you're not on Angular 16, Making your Angular development more straightforward and future-proof is worth your time.

Do you find RxJS complex? Well, you are not the only one. So many others think that way. Well, Angular Signals solves that problem for so many developers. So let's dive into it together.

divider-byrayray.png

Sources:

divider-byrayray.png

Signals

Angular Signals are reactive properties in your Angular component, directive, or service. They update their value automatically; you don't need to listen to changes. No, they are doing all of that for you. Let's start with creating a Signal and showing it in the view.

Get the value

Let's start simple. Getting a value from a signal is pretty straightforward.

If we want to show the value of a signal in the view, we need to define it first in the component.

@Component({...})
export class Component {
    count = signal<number>(0)
}

Then in the view, we can output it in an expression like this:

<div>{{ count() }}</div>

It is that easy. But how do we use a value of a signal in our component?

How to get a value in a component class

The class of your component is also slightly different. The Signal value can be retrieved as easily as this.count().

@Component({...})
export class Component {
    count = signal<number>(0)

    private logCount(): void {
        console.log('Count: ', this.count())
    }
}

Since signals are built for reactivity in Angular, we want to set and update a value in a signal. Let's dive into that.

divider-byrayray.png

Setting and updating the value

Reading the values of a Signal is simple. But if you are used to RxJS with Observables and Behavioursubjects, for example, this is a bit different, but I know you will love the simplicity!

By default, you can give a signal a value (just like the BehaviourSubject). When the value is changed, the view is updated automatically. Setting the value of a signal can be done with

@Component({...})
export class Component {
    count = signal<number>(0)

    private changeCount(): void {
        this.count.set(30)
    }
}

How to update a value in a Signal based on the previous value

If you want to update your Signal based on the previous value, use the update() method.

count = signal(25)

// Update value
this.signal.update(prevValue => prevValue + 10) // 35

When the update has been done, it will automatically be reflected in the view.

divider-byrayray.png

How to update a signal with an array of objects

Updating an Array of Objects is equally simple with Signals. For this, we will use the .mutate() method.

Let's say we have an Array of Objects. Each Object has a price property. What if we want to update the first price, we can do that with .mutate() like this.

export class SignalsUpdatesArrayComponent {
    prices = signal<Obj[]>([
      { price: 10 }, 
      { price: 20 }, 
      { price: 30 }
    ])

    updatePrice(): void {
        this.prices.mutate(value => (value[0].price = this.getRandomPrice()))
    }

    getRandomPrice(): number {
        return Math.round(Math.random() * 100)
    }
}

We call the updatePrice() in the template, and it will be performed.

But what if we want to add an Object to the Array on the fly? Well just as easy. Push a new Object in the mutate() method.

export class SignalsUpdatesArrayComponent {
    prices = signal<Obj[]>([
      { price: 10 }, 
      { price: 20 }, 
      { price: 30 }
    ])

    updatePrice(index: number): void {
        this.prices.mutate(value => (value[index].price = this.getRandomPrice()))
    }
    addPrice(): void {
        this.prices.mutate(value => value.push({ price: this.getRandomPrice() }))
    }

    getRandomPrice(): number {
        return Math.round(Math.random() * 100)
    }
}

I created an example so you can see that it works by easily updating the Array.

divider-byrayray.png

If you want to learn more about RxJS Subjects, BehaviourSubjects and more? Then read the post I wrote about that in detail.

divider-byrayray.png

Effect

Listen to value updates with effect()

Listening to changes can easily be done with effect() . But it's not equal to the behavior of listening to changes of an Observable or BehaviourSubject in RxJS. So let's see what the difference is.

The effect() method with Signals is mostly for updating logging, saving data to local storage, or updating your canvas. By default, you are not allowed to update a signal within the effect().

For example, we create a component where we want to log when the price is updated. In the constructor, we have added the effect() to show that the price has been updated. In the HTML, we create a button to update the price.

@Component({...})
export class SignalsGetUpdatesComponent {
  price = signal<number>(10);
  priceUpdated = signal<boolean>(false);

  constructor() {
    effect(() => {
      console.log(`Price updated ${this.price()}`);
    });
  }

  updatePrice() {
    this.price.update((price) => price + 30);
    this.priceUpdated.set(true);
    setTimeout(() => {
      this.priceUpdated.set(false);
    }, 2000);
  }
}
<div>
    <h2>Get updates</h2>
    <p>
        Add {{30 | currency: 'EUR'}} to the price <br />
        of {{10 | currency: 'EUR'}} and notify.
    </p>

    <p><strong>Is price updated: </strong> {{priceUpdated() ? "πŸ‘" : "πŸ‘Ž"}}</p>
    <p><strong>Price: </strong> {{price() | currency:'EUR'}}</p>
    <button (click)="updatePrice()" [ngClass]="{'is-updated': priceUpdated()}">Add {{30 | currency: 'EUR'}}</button>
</div>

In this case, I update the priceUpdated signal in the updatePrice() method while updating the price. When you use RxJS with a BehaviourSubject, you could have done that in the subscribe() method. This is a matter of taste on what you prefer.

If you know what you do and are aware of creating possibly an infinite loop with updating signals in the effect(), you can do that by passing a second parameter { allowSignalWrites: true }.

@Component({...})
export class SignalsGetUpdatesEffectUpdateComponent {
    price = signal<number>(10)
    priceUpdated = signal<boolean>(false)

    constructor() {
        effect(
            () => {
                console.log(`Price updated ${this.price()}`)

                this.priceUpdated.set(true)
                setTimeout(() => {
                    this.priceUpdated.set(false)
                }, 2000)
            },
            { allowSignalWrites: true }
        )
    }

    updatePrice() {
        this.price.update(price => price + 30)
    }
}
<div>
    <h2>Get updates and overwrite signal in effect</h2>
    <p>
        Add {{30 | currency: 'EUR'}} to the price <br />
        of {{10 | currency: 'EUR'}} and notify.
    </p>

    <p><strong>Is price updated: </strong> {{priceUpdated() ? "πŸ‘" : "πŸ‘Ž"}}</p>
    <p><strong>Price: </strong> {{price() | currency:'EUR'}}</p>
    <button (click)="updatePrice()" [ngClass]="{'is-updated': priceUpdated()}">Add {{30 | currency: 'EUR'}}</button>
</div>

The effect will run when the component is initialized, like the ngOnInit.

divider-byrayray.png

Computations

Let's talk about computations. You use a computation when calculating or doing string concatenation based on changing one or more values.

Get the value from the computation

// Signal
getDoubleCount() {
    console.log(this.doubleCount())
}

// BehaviourSubject
getDoubleCount() {
    this.doubleCount.subscribe((value) => console.log(value))
}

And this is where getting the simplest thing, as getting the double-count value, is going to be less straightforward with the BehaviourSubject if you compare it with the Signal.

Because with the BehaviourSubject, you want to ensure the subscription is removed when it's not needed anymore.

With signals, Angular handles many things for you, more than using RxJS. With RxJS, we can often get buried in complex and hard-to-read code, especially for people who have yet to get used to Angular or RxJS.

For example, with RxJS, we need to clean our subscriptions ourselves; with signals, we don't have that because Angular handles that for us.

// Inside a component class
// ...
destroy$ = new Subject<void>();

ngOnDestroy() {
    this.destroy$.next();
}

getDoubleCount() {
    this.doubleCount
        .pipe(takeUntil(this.destroy$))
        .subscribe((value) => console.log(value))
}
//...

No, signals bring a lot of simplicity while using reactivity. Developers coming from Vue will be used to a very similar way of handling reactive properties.

For example, let's update a first name and show the user the full name. We will use computed() to help us with that.

@Component({...})
export class SignalsComputeValueComponent {
  firstName = signal('Bart')
  lastName = signal('The Great')

  fullName = computed(() => this.firstName() + ' ' + this.lastName())

  changeFirstName() {
    this.firstName.set('Ray')
  }
}
<div>
    <h2>Get updates and overwrite signal in effect</h2>

    <p>
        <strong>Firstname:</strong> {{ firstName() }} <br />
        <strong>Lastname:</strong> {{ lastName() }}
    </p>
    <p><strong>Fullname computation:</strong> {{ fullName() }}</p>

    <p>
        <button (click)="changeFirstName()">Update firstname</button>
    </p>
</div>

As you can see, the fullName property merges two strings in the computed() method. When the firstName is updated, the fullName will be updated automatically, and you can see it directly in the view. It is that simple!

divider-byrayray.png

Thanks

Now you are entirely up to date with how to use Angular Signals. I hope you can start using them right away.

hashnode-footer.png

After reading this story, I hope you learned something new or are inspired to create something new! πŸ€— If I left you with questions or something to say as a response, scroll down and type me a message, send me a DM on Twitter @DevByRayRay

Want to receive new posts in your mailbox? No, not only a link, just the whole article without any ads πŸ€— or other stuff. Then subscribe to my newsletter πŸ‘. I promise I won’t spam you, only the most important and best-quality content will be sent to you ✌️.

Did you know that you can create a Developer blog like this one, yourself? It's entirely for free. πŸ‘πŸ’°πŸŽ‰πŸ₯³πŸ”₯

Did you find this article valuable?

Support Dev By RayRay by becoming a sponsor. Any amount is appreciated!

Β