# @handle

`@handle(...keys)` calls the decorated method each time a publisher assigns one of up to three store paths, with one typed argument per key. Nothing is stored on the component: use it for derived values and side effects, and [@subscribe](/docs/decorators/subscribe) when you only need the value. See [Data flow](/docs/getting-started/dataFlow) for the overall model.

## Import

<sonic-code language="typescript">
  <template>
import { handle, Skip } from "@supersoniks/concorde/decorators";
import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";

const cockpitKey = new DataProviderKey&lt;{ altitude: number }&gt;("cockpit");

@handle(cockpitKey.altitude)
onAltitude(feet: number) {
  this.meters = Math.round(feet * 0.3048);
}
  </template>
</sonic-code>

## Basic usage

The method runs once with the current value when the component connects, then on **every** assignment, including `null` and `undefined`. Below, each climb assigns a new altitude and the method derives meters and flight level.

<sonic-code>
  <template>
    <docs-demo-sources for="demo-handle"></docs-demo-sources>
    <demo-handle></demo-handle>
  </template>
</sonic-code>

## Dynamic path

`${prop}` placeholders resolve from properties of the same component; when one changes, the method is called with the value at the new path. Rules: [Dynamic paths](/docs/misc/dynamic-path).

<sonic-code language="typescript">
  <template>
const personKey = new DataProviderKey&lt;OnBoard, { seatIndex: number }&gt;(
  "demoOnBoard.${seatIndex}",
);

@property({ type: Number }) seatIndex = 0;

@handle(personKey)
onPerson(person: OnBoard | null) {
  this.announcement = person ? `${person.name}, please proceed to gate B12.` : "";
}
  </template>
</sonic-code>

<sonic-code>
  <template>
    <docs-demo-sources for="demo-handle-dynamic"></docs-demo-sources>
    <demo-handle-dynamic></demo-handle-dynamic>
  </template>
</sonic-code>

## Multiple keys

Pass up to three [DataProviderKey](/docs/misc/dataProviderKey) values; the method receives one argument per key, in the same order. Any assignment to any key calls it, so other arguments may still be `null`.

<sonic-code language="typescript">
  <template>
const flightKey = new DataProviderKey&lt;{ status: string; gate: string }&gt;("flight");
const weatherKey = new DataProviderKey&lt;{ turbulence: boolean }&gt;("weather");

@handle(flightKey.status, weatherKey.turbulence)
onConditions(status: string, turbulence: boolean) {
  this.seatbeltSign = status === "boarding" || turbulence === true;
}
  </template>
</sonic-code>

## Wait for all values

With `waitForAllDefined: true`, the method runs only once every key holds a value other than `null` or `undefined`.

<sonic-code language="typescript">
  <template>
@handle(flightKey.gate, passengerKey.name, { waitForAllDefined: true })
announce(gate: string, name: string) {
  this.message = `${name}, please proceed to gate ${gate}.`;
}
  </template>
</sonic-code>

## Skip values

`skip` lists categories of values that must not call the method. A category is a named test, not a value to compare.

| Category | Matches |
|----------|---------|
| `Skip.Nullish` | `null` or `undefined` |
| `Skip.EmptyString` | `""` |
| `Skip.EmptyObject` | `{}` (arrays excluded) |
| `Skip.EmptyArray` | `[]` |

<sonic-code language="typescript">
  <template>
@handle(passengerKey, { skip: [Skip.Nullish, Skip.EmptyObject] })
onPassenger(passenger: Passenger) {
  // not called while the publisher still holds {} (not loaded yet)
  this.displayName = passenger.name;
}
  </template>
</sonic-code>

To reject a specific value, return early inside the method.

## API reference

| Argument | Type | Description |
|----------|------|-------------|
| `key` (1 to 3) | `DataProviderKey<T, U>` | Paths to watch; the method receives one value per key, in order |
| `options.waitForAllDefined` | `boolean` (default `false`) | Call the method only when no value is `null` or `undefined` |
| `options.skip` | `Skip[]` (default none) | Do not call the method when any value matches one of these categories |
| `options.skipEmptyPlaceholder` | `boolean` (default `false`) | Treat a placeholder resolved to `""` as not ready |

`options`, if present, is the last argument. `@handle` replaces the deprecated [@onAssign](/docs/decorators/on-assign): `@onAssign("a", "b")` is `@handle(keyA, keyB, { waitForAllDefined: true })`.
