---
description: >-
  Watching data for changes, including support for expressions where you want to
  watch for changes to one or more dependencies and react accordingly.
---

# Watching data

## Introduction

Unlike other observation strategies detailed in the observation section, the `@watch` functionality allows you to watch for changes in your view models. If you worked with the `computedFrom` decorator in Aurelia 1, the watch decorator is what you would replace it with.

## Watching values with @watch

Aurelia provides a way to author your components in a reactive programming model with the `@watch` decorator. Decorating a class or a method inside it with the `@watch` decorator, the corresponding method will be called whenever the watched value changes.

{% hint style="info" %}
**Intended usages**

* The `@watch` decorator can only be used on custom element and attribute view models.
* Corresponding watchers of `@watch` decorator will be created once, bound after `binding`, and unbound before `unbinding` lifecycles. This means mutation during `binding`/ after `unbinding` won't be reacted to, as watchers haven't started or have stopped.
{% endhint %}

## APIs

```typescript
import { watch } from '@aurelia/runtime-html';

// on class
@watch(expressionOrPropertyAccessFn, changeHandlerOrCallback)
class MyClass {}

// on method
class MyClass {
  @watch(expressionOrPropertyAccessFn)
  someMethod() {}
}
```

| Name                         | Type                        | Description                                                                                                                                                                                                                                                                                                                                                                                                                    |
| ---------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| expressionOrPropertyAccessFn | string \| IPropertyAccessFn | Watch expression specifier                                                                                                                                                                                                                                                                                                                                                                                                     |
| changeHandlerOrCallback      | string \| IWatcherCallback  | The callback that will be invoked when the value evaluated from watch expression has changed. If a name is given, it will be used to resolve the callback `ONCE`. This callback will be called with 3 parameters: (1st) new value from the watched expression. (2nd) old value from the watched expression (3rd) the watched instance. And the context of the function call will be the instance, same with the 3rd parameter. |

## Reacting to property changes with @watch

The `@watch` decorator allows you to react to changes on a property or computed function.

In the following example, we will call a function every time the name property in our view model is changed.

```typescript
import { watch } from '@aurelia/runtime-html';

class NameComponent {
  name = '';

  @watch('name')
  nameChange(newName, oldName) {
    console.log(newName); // The new name value
    console.log(oldName); // The old name value
  }
}
```

This is referred to in Aurelia as an expression. You can also observe properties on things like arrays for changes which might be familiar to you if you worked with Aurelia 1 and the `@computedFrom` decorator.

This is what an expression looks like observing an array length for changes:

```typescript
import { watch } from '@aurelia/runtime-html';

class PostOffice {
  packages = [];

  @watch('packages.length')
  log(newCount, oldCount) {
    if (newCount > oldCount) {
      // new packages came
    } else {
      // packages delivered
    }
  }
}
```

## Using computed functions to react to changes

Sometimes you want to watch multiple values in a component, so you need to create an expression. A computed function is a function provided to the `@watch` decorator, which allows you to do comparisons on multiple values.

To illustrate how you can do this, here is an example:

```typescript
import { watch } from '@aurelia/runtime-html';

class PostOffice {
    packages = [];

    @watch(post => post.packages.length)
    log(newCount, oldCount) {
        if (newCount > oldCount) {
            // new packages came
        } else {
            // packages delivered
        }
    }
}
```

In this example, the `log` method of `PostOffice` will be called whenever a new package is added to, or an existing package is removed from the `packages` array. The first argument of our callback function is the view model, allowing us to access class properties and methods.

## Usage examples

### Decorating on a class, string as watch expression, with arrow function as a callback

```typescript
import { watch } from '@aurelia/runtime-html';

@watch('counter', (newValue, oldValue, app) => app.log(newValue))
class App {

  counter = 0;

  log(whatToLog) {
    console.log(whatToLog);
  }
}
```

### Decorating on a class, string as watch expression, with method name as a callback

{% hint style="danger" %}
The method name will be used to resolve the function once, which means changing the method after the instance has been created will not be recognized.
{% endhint %}

```typescript
import { watch } from '@aurelia/runtime-html';

@watch('counter', 'log')
class App {

  counter = 0;

  log(whatToLog) {
    console.log(whatToLog);
  }
}
```

### Decorating on a class, string as watch expression, with normal function as a callback

```typescript
import { watch } from '@aurelia/runtime-html';

@watch('counter', function(newValue, oldValue, app) {
  app.log(newValue);
  // or use this, it will point to the instance of this class
  this.log(newValue);
})
class App {

  counter = 0;

  log(whatToLog) {
    console.log(whatToLog);
  }
}
```

### Decorating on a class, normal function as watch expression, with arrow function as callback

```typescript
import { watch } from '@aurelia/runtime-html';

@watch(function (app) { return app.counter }, (newValue, oldValue, app) => app.log(newValue))
class App {

  counter = 0;

  log(whatToLog) {
    console.log(whatToLog);
  }
}
```

### Decorating on a class, arrow function as watch expression, with arrow function as callback

```typescript
import { watch } from '@aurelia/runtime-html';

@watch(app => app.counter, (newValue, oldValue, app) => app.log(newValue))
class App {

  counter = 0;

  log(whatToLog) {
    console.log(whatToLog);
  }
}
```

### Decorating on a method, string as watch expression

```typescript
import { watch } from '@aurelia/runtime-html';

class App {
  counter = 0;

  @watch('counter')
  log(whatToLog) {
    console.log(whatToLog);
  }
}
```

### Decorating on a method, normal function as watch expression

```typescript
import { watch } from '@aurelia/runtime-html';

class App {
  counter = 0;

  @watch(function(app) { return app.counter })
  log(whatToLog) {
    console.log(whatToLog);
  }
}
```

### Decorating on a method, arrow function as watch expression

```typescript
import { watch } from '@aurelia/runtime-html';

class App {
  counter = 0;

  @watch(app => app.counter)
  log(whatToLog) {
    console.log(whatToLog);
  }
}
```

## @watch reactivity examples

> During `binding` lifecycle, bindings created by `@watch` decorator haven't been activated yet, which means mutations won't be reacted to:

```typescript
import { watch } from '@aurelia/runtime-html';

class PostOffice {
  packages = [];

  @watch(post => post.packages.length)
  log(newCount, oldCount) {
    console.log(`packages changes: ${oldCount} -> ${newCount}`);
  }

  // lifecycle
  binding() {
    this.packages.push({ id: 1, name: 'xmas toy', delivered: false });
  }
}
```

There will be no log in the console.

> During `bound` lifecycle, bindings created by `@watch` decorator have been activated, and mutations will be reacted to:

```typescript
import { watch } from '@aurelia/runtime-html';

class PostOffice {
  packages = [];

  @watch(post => post.packages.length)
  log(newCount, oldCount) {
    console.log(`packages changes: ${oldCount} -> ${newCount}`);
  }

  // lifecycle
  bound() {
    this.packages.push({ id: 1, name: 'xmas toy', delivered: false });
  }
}
```

There will be 1 log in the console that looks like this: `packages changes: 0 -> 1`.

{% hint style="info" %}
**Other lifecycles** that are invoked after `binding` and before `unbinding` are not sensitive to the working of the `@watch` decorator, and thus don't need special mentions. Those lifecycles are `attaching`, `attached`, and `detaching`.
{% endhint %}

> During `detaching` lifecycle, bindings created by `@watch` decorator have not been de-activated yet, and mutations will still be reacted to:

```typescript
import { watch } from '@aurelia/runtime-html';

class PostOffice {
  packages = [];

  @watch(post => post.packages.length)
  log(newCount, oldCount) {
    console.log(`packages changes: ${oldCount} -> ${newCount}`);
  }

  // lifecycle
  detaching() {
    this.packages.push({ id: 1, name: 'xmas toy', delivered: false });
  }
}
```

There will be 1 log in the console that looks like this: `packages changes: 0 -> 1`.

> During `unbinding` lifecycle, bindings created by `@watch` decorator have been deactivated, and mutations won't be reacted to:

```typescript
import { watch } from '@aurelia/runtime-html';

class PostOffice {
  packages = [];

  @watch(post => post.packages.length)
  log(newCount, oldCount) {
    console.log(`packages changes: ${oldCount} -> ${newCount}`);
  }

  // lifecycle
  unbinding() {
    this.packages.push({ id: 1, name: 'xmas toy', delivered: false });
  }
}
```

There will be no log in the console.

## How it works

By default, a watcher will be created for a `@watch()` decorator. This watcher will start observing before `bound` lifecycle of components. How the observation works will depend on the first parameter given.

* If a string or a symbol is given, it will be used as an expression to observe, similar to how an expression in Aurelia templating works.
* If a function is given, it will be used as a computed getter to observe dependencies and evaluate the value to pass into the specified method. Two mechanisms can be employed:
  * For JavaScript environments with native proxy support: Proxy will be used to trap & observe property read. It will also observe collections (such as an array, map and set) based on invoked methods. For example, calling `.map(item => item.value)` on an array should observe the mutation of that array and the property `value` of each item inside the array.
  *   For environments without native proxy support: the 2nd parameter inside the computed getter can be used to observe (or register) dependencies manually. This is the corresponding watcher created from a `@watch` decorator. It has the following interface:

      ```typescript
      interface IWatcher {
          observeProperty(obj: object, key: string | number | symbol): void;
          observeCollection(collection: Array | Map | Set): void;
      }
      ```

      **An example is:**

      ```typescript
      import { watch } from '@aurelia/runtime-html';

      class Contact {
        firstName = 'Chorris';
        lastName = 'Nuck';

        @watch((contact, watcher) => {
          watcher.observeProperty(contact, 'firstName');
          watcher.observeProperty(contact, 'lastName');
          return `${contact.firstName} ${contact.lastName}`;
        })
        validateFullName(fullName) {
          if (fullName === 'Chuck Norris') {
            this.faint();
          }
        }
      }
      ```

      The `firstName` and `lastName` properties of `contact` components are being observed manually. And every time either `firstName`, or `lastName` change, the computed getter is run again, and the dependencies will be observed again. Observers are cached, and the same observer won't be added more than once, old observers from the old computed getter run will also be disposed of, so you won't have to worry about stale dependencies or memory leaks.

{% hint style="warning" %}
**Automatic array observation**

* By default, in the computed getter, array mutation method such as `.push()`, `.pop()`, `.shift()`, `.unshift()`, `.splice()`, and `.reverse()` are not observed, as there are no clear indicators of the dependencies to collect from those methods.
{% endhint %}

## Best practices

### Avoid mutations on dependencies inside a computed getter

It is best to avoid mutation on dependencies collected inside a computed getter.

```typescript
// don't do this
@watch(object => object.counter++)
someMethod() {}

// don't do these
@watch(object => object.someArray.push(...args))
@watch(object => object.someArray.pop())
@watch(object => object.someArray.shift())
@watch(object => object.someArray.unshift())
@watch(object => object.someArray.splice(...args))
@watch(object => object.someArray.reverse())
someMethod() {}
```

### Be careful with objects not access from the first parameter

To ensure identity equality with proxies, always be careful with objects not accessed from the first parameter passed into the computed getter. Better get the raw underlying object before doing the strict comparison with `===`.

```typescript
import { watch } from '@aurelia/runtime-html';

const defaultOptions = {};

class MyClass {
  options = defaultOptions;

  @watch(myClass => myClass.options === defaultOptions ? null : myClass.options)
  applyCustomOptions() {
    // ...
  }
}
```

In this example, even if `options` on a `MyClass` instance has never been changed, the comparison of `myClass.options === defaultOptions` will still return false, as the actual value for `myClass.options` is a proxied object wrapping the real object, and thus is always different with `defaultOptions`.

### Don't return promises or async functions

Dependency tracking inside a watch computed getter is done synchronously, which means returning a promise or having an async function won't work properly.&#x20;

Don't do the following:

```typescript
import { watch } from '@aurelia/runtime-html';

class MyClass {
  // don't do this
  @watch(async myClassInstance => myClassinstance.options)
  applyCustomOptions() {}

  // don't do this
  @watch(myClassInstance => {
    Promise.resolve().then(() => {
      return myClassinstance.options
    })
  })
}
```
