Signals vs. ngOnChanges for better Angular state management
Tags: angular, webdev
Published: 2024-06-13 14:01:14

**Written by [Lewis Cianci](https://blog.logrocket.com/author/lewiscianci/)✏️**

You know what framework just hasn’t stayed still lately? Angular. For a long time, nothing really seemed to change, and now we’re smack-bang in the middle of what some are calling the Angular Renaissance. 

First up, we received signals, then [control flow programming](https://blog.logrocket.com/control-flow-syntax-angular-17/) was added to templates. And now, signals are continuing to grow in Angular, spreading their wings into the area of state management. 

Signals in components are [now available in developer preview](https://blog.angular.dev/signal-inputs-available-in-developer-preview-6a7ff1941823), and no doubt will be available for use in stable Angular before long.

## Why the change in Angular?

One of the core design principles of Angular, as compared to something like React, relates to how changes to the view are handled. 

In a library like React, developers could modify properties that should be displayed on the page. However, the view would not update until `setState()` was called. Making the developer responsible for telling the framework when to redraw components can lead to a somewhat harder DX, but can also yield performance benefits. 

Angular takes a different route by using data binding in the view. When a variable is updated in code, this causes Angular to redraw the affected view to reflect the changes. The developer doesn’t have to manually call something like `setState()`, as the framework tries to work it out internally. 

The only caveat is that when text is rendered from a component to a view, it’s usually for simple objects like `string` or `number`. These data types obviously don’t have special functionality built in to notify when they have been updated. 

In such cases, the responsibility falls to Angular itself to set up appropriate places where values within views can be updated as required. This is both complicated and [fascinating to read about](https://blog.angular-university.io/how-does-angular-2-change-detection-really-work/). 

This all makes sense and works well for as long as we constrain ourselves to a single component. But the moment we add another component, and want to pass a variable into that component, the complexity is kicked up a notch. 

How do we handle changes between bound data that occur in the child component? Let’s use a sample app to demonstrate the problem, and how signals in our components can help.

## Building a price tracker app to demonstrate Angular signals

Let's imagine we have an app that’s tracking the price of four different products. Over time, the price of the product can go up or down. It’s rudimentary, but will help us to understand the concept at hand. It looks like this: ![Demo Cat Product Pricer Tracker App Showing Four Cat Related Products And Updating To Show Change In Price](https://blog.logrocket.com/wp-content/uploads/2024/06/Cat-product-price-tracker.gif) The data is provided through an `interval` that updates every second. It stays subscribed until the component is destroyed. Until then, it updates the `model` with new random price data:

```javascript
ngOnInit() {
  this.timerSub = interval(1000)
    .pipe(takeUntil(this.$destroying))
    .subscribe(x => {
    this.model = [
      {
        name: "The book of cat photos",
        price: getRandomArbitrary(5, 15)
      },
      {
        name: "How to pat a cat",
        price: getRandomArbitrary(10, 40)
      },
      {
        name: "Cat Photography: A Short Guide",
        price: getRandomArbitrary(12, 20),
      },
      {
        name: "Understanding Cat Body Language: A Cautionary Tale",
        price: getRandomArbitrary(2, 15)
      }
    ]
  });
}
```

Next up, we also have our `ChildComponent` which shows the list of prices. It just accepts an `Input()` of type `Array<PriceData>`. Every second, the price data updates, and the update flows to our child component. Nice.

## Reacting to data changes with `ngOnChanges` in Angular

But now, we want to introduce an improvement. When the price goes up or down for individual items, we want to visually signify that to the user. Additionally, how much the product has gone up or down by should show. 

Essentially, we are reacting to changes in the data. Before signal inputs, we’d have to implement the `OnChanges` interface in our component. Let’s go ahead and bring that in now:

```javascript
export class ChildComponentComponent implements OnChanges {
  ngOnChanges(changes: SimpleChanges): void {
    console.log(changes);
  }
  @Input() data: Array<PriceData> | undefined;
}
```

Now we get notified each time the data has changed, and the output is logged. Let’s see how that helps us. Our console window can give us more insight: ![Console Window Showing Data Change With Output Logged](https://blog.logrocket.com/wp-content/uploads/2024/06/Console-window-showing-data-change-output-logged-e1717621393486.png) First up, our data changes from undefined (`previousValue`) to the new value (`currentValue`): ![Example Showing Data Changing From Undefined To New Value](https://blog.logrocket.com/wp-content/uploads/2024/06/Example-data-changing-undefined-new-value.png) On subsequent changes, the old data is updated to the new data. This repeats every time the value is changed on the component. 

There’s nothing technically wrong with this approach. But in Angular, with TypeScript, whose main selling point is types, there’s certainly a lack of types being handed around. The types of `previousValue` and `currentValue` are just `any`: ![Example Showing Lack Of Types In Angular Project For Previousvalue And Currentvalue Variables](https://blog.logrocket.com/wp-content/uploads/2024/06/Example-lack-types-previousValue-currentValue.png) To meet our requirements, this means we have to blindly cast from these types into types that we expect before we can work on the data. Our `ngOnChanges` becomes the following: ![Using Ngonchanges To Cast Expected Types](https://blog.logrocket.com/wp-content/uploads/2024/06/Using-ngOnChanges-cast-expected-types.png) We likely started our Angular project with high hopes of using types, but this code almost immediately feels like a gutterball for two main reasons:

1.  We use an index signature to access the data object, which we hope we haven’t typed or entered incorrectly, because there’s nothing saving us from that situation
2.  We shove `previousValue` and `currentValue` into their respective types, with no idea as to how the implementor is populating these values. If we refactor the code tomorrow and change the type that comes into the component via the `Input()` directive, our code will stop working and we wouldn’t be sure why

Remember, this is in a simple application as well. If we were working on an app with any more complexity, it’s not hard to see how using `ngOnChanges` would become unwieldy. 

We could introduce some techniques to help deal with it, but in reality, the changes coming into our component probably should have some sort of type, and should react appropriately when they are updated. Fortunately, that’s exactly what signals do.

## Signals to the rescue in our Angular demo

[Signals, introduced recently in Angular](https://blog.logrocket.com/angular-signals-vs-observables/), can help us remove our dependency on `ngOnChanges`, and make it easier for us to achieve a better solution. Admittedly, bringing signals into this code does require a bit of reasoning, but leaves us with cleaner code that makes more sense. 

If we were to break down what’s happening here in plain English, the description of the problem would be:

*   We receive a list of prices
*   When the prices change, we want to store the received prices in an “old prices” variable
*   Then, we want to compare the new prices with the old prices

This helps us understand two key components to how we’ll solve this with signals. 

First, using “when the x happens” language indicates that we’ll need to use an `effect` because we want something to happen when the signal changes — in this case, storing the old value to a variable. 

Second, using a phrase like “and then compare” indicates that we want to compute a value that depends on the incoming value. Unsurprisingly, this means we’ll need to use a `compute` function. 

Okay, let’s bring these changes into our component. First of all, we’ll need to remove the dependency we have on `ngOnChanges`, as that’s no longer a dependency of this change detection. Next, we’ll need some new properties for the data:

```javascript
prices = input.required<Array<PriceData>>(); // The incoming price data
oldPrices = Array<PriceData>();
```

### Creating the `effect`

Ah, this is the easy part. Basically, whenever the prices update, we just want to store the last emitted value into an `oldPrices` variable. This happens in our constructor:

```javascript
constructor() {
  effect(() => {
    this.oldPrices = this.prices();
  });
}
```

Admittedly, it still feels weird at times calling `prices` like it’s a function, but it’s how we interact with signals. We receive an array of prices, which are immediately set to the `oldPrices` variable. 

But if we’re just doing this every single time the value changes, how will we effectively compare the old and new values? Simple — we have to compute it.

### Creating the `computed` function

Within our `computed` function, we now have access to a fully type-safe instance of our prices and prices array. Whenever the `prices` signal changes, `computed` sees that the signal has changed, and updates the computed signals as required. The comparison occurs, and our new computed signal is returned:

```javascript
priceDifferences = computed(() => {
    let priceDelta = [
      this.priceCompare(this.oldPrices[0], this.prices()[0]),
      this.priceCompare(this.oldPrices[1], this.prices()[1]),
      this.priceCompare(this.oldPrices[2], this.prices()[2]),
      this.priceCompare(this.oldPrices[3], this.prices()[3]),
    ]

    return priceDelta.map(x => ({
      change: x,
      direction: (x ?? 0) > 0 ? PriceDirection.Increasing : PriceDirection.Decreasing,

    } as PriceDescription));
})
```

In our example, the `computed` function runs first, and then the `effect` function runs second. This means that the old and new values are stored and compared effectively. 

It’s also worth mentioning that when I first wrote this code, I attempted to set a signal from the `effect` code and skip the `computed` signal altogether. That’s actually the wrong thing to do — and Angular won’t let you do it unless you change a setting — for a couple of reasons:

1.  Updating signals from within effects makes it difficult to track what is updating and why
2.  Signals are mutable and can be set by you, whereas `computed` signals are read-only — they can’t be `set` by you. This makes sense when your `computed` signal is downstream from your other data

The benefits of this approach is that our code has more type safety, and it makes more sense to read and understand. It also means that our components will work if our change detection is set to `OnPush`, and sets us up for [Angular’s move away from using zones for change detection](https://blog.angular.dev/angular-v18-is-now-available-e79d5ac0affe). 

The other nice thing about this approach is that it actually solves a problem that a lot of Angular developers will probably have in the future. 

Namely, with no `ngOnChanges` giving old and new values to identify what’s changed, how will we perform comparisons? Fortunately, it’s as easy as setting up an effect to store the old value, and then performing the comparison in a computed signal value.

## Conclusion

[Angular is evolving](https://blog.logrocket.com/exploring-angular-evolution/) in some pretty exciting ways. In this tutorial, we explored how signals are growing in Angular to enhance state management. 

To see how to use signals for better state management in Angular, we created a demo project and looked at the “old” approach using `ngOnChanges` as well as the improved approach using signals. 

As always, you can clone the code yourself from [this GitHub repo](https://github.com/azimuthdeveloper/angular-signal-components). You can use the commit history to change between a version of the app with `ngOnChanges` and the newer Signals implementation.

---
###Experience your Angular apps exactly how a user does

Debugging Angular applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Angular state and actions for all of your users in production, [try LogRocket](https://lp.logrocket.com/blg/angular-signup).

[![LogRocket Signup](https://files.readme.io/610d6a7-687474703a2f2f692e696d6775722e636f6d2f696147547837412e706e67.png)](https://lp.logrocket.com/blg/angular-signup)

[LogRocket](https://lp.logrocket.com/blg/angular-signup) is like a DVR for web and mobile apps, recording literally everything that happens on your site including network requests, JavaScript errors, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.

The LogRocket NgRx plugin logs Angular state and actions to the LogRocket console, giving you context around what led to an error, and what state the application was in when an issue occurred.

Modernize how you debug your Angular apps — [start monitoring for free](https://lp.logrocket.com/blg/angular-signup).