# Signals

## About

{% embed url="<https://angular.dev/guide/signals>" %}
guide/signals
{% endembed %}

A signal is a wrapper around a value that notifies interested consumers when that value changes. *(Values may be primitive or complex data structures)*

Signals gives Angular a new way of handling data changes and updating the UI, instead of just `Change Detection`.

You read a signal's value by calling its getter function, which allows Angular to track where the signal is used.

## Best Practices

* **Avoid using** `effect()` **for propagation of state changes.**
  * This might lead to infinite circular updates, or unnecessary change detection cycles.
  * *Angular will forbid you from change signal states inside* `effect()`*.*
* Don't mutate signal values when updating them, always give them new values.

## Writable Signals

### `signal()`

Can be created with `signal(value)`.

You may update their values with:

* `set(newValue)` to just set a new value.
* `update((oldValue) => newValue)`, to access the old value.

```html
<!-- To Output a signal, just execute it's getter -->
<p>{{ counter() }}</p>
```

```typescript
@Component({ ... })
export class Component {
    counter: WritableSignal<number> = signal(0);
    // Or
    counter = signal<number>(0);

    actions = signal<string[]>([]);

    show() {
        console.log(this.counter());
    }

    increment() {
        this.counter.update((oldValue: number) => oldValue + 1);

        // NEVER mutate Signal values, instead always give it a new value
        this.actions.update((oldActions) => [...oldActions, 'new Value']);
    }

    decrement() {
        this.counter.update((oldValue: number) => oldValue - 1);
    }
}
```

## Computed Signals

### `computed()`

Are **readonly** signals that derive their value from other signals.

Created with `computed(() => this.otherSignal() + 1)`.

Computed signals depend on other signals, so computed signals are only updated when their dependee signals update.

#### Lazy and Memoized

{% hint style="warning" %}
**Computed signals are both lazily evaluated and memoized.**

* `lazily evaluated` means that they do not run to calculate its value until the first time you read their value.
* `memoized` means that its value is cached, so they wont be recalculated all the time. (Only when their dependee signal value changes)

As a result of this, you can safely perform computationally expensive derivations in them, like filtering arrays.
{% endhint %}

#### Dynamic dependencies

{% hint style="warning" %}
**Computed signals dependencies are dynamic.**

* Only signals that were actually read during the derivation are tracked. (And they can change on runtime)
  {% endhint %}

```typescript
const showCount = signal(false);
const count = signal(0);
const conditionalCount = computed(() => {
    if (showCount()) {
        return `The count is ${count()}.`;
    } else {
        return "Nothing to see here!";
    }
});
```

In this example:

* When `showCount` is `false`, updating `count` will not trigger a recomputation of the computed signal.
  * So only `showCount` change will trigger.
* But when `showCount` is `true`, `count` signal can be read, and now the computed signal will recalculate itself on both `showCount` and `count` updates.
* *Just remeber that the computed signal won't recalculate immediatly after the dependee signals update, but only when* `conditionalCount` *is read again.*

## Effects

### `effect()`

Effects are operations that runs whenever on or more signal values change.

* They also have dependee signals, which are the signals used inside them.
* When these dependee signals values change, the effect re-runs.

Create them with `effect(() => {})`.

{% hint style="info" %}
**Effect always run at least once.**
{% endhint %}

{% hint style="info" %}
Effects also keep track of their dependencies dynamically.

*So they will only track signals that were **read** during the last execution.*
{% endhint %}

{% hint style="info" %}
Effects always execute `asynchronously`, during the change detection process.
{% endhint %}

{% hint style="danger" %}
By default Angular **prevents** `updates` to signals inside effects.
{% endhint %}

Effects are good for:

* Logging data.
* Keeping data in sync with `window.localStorage`.
* Performing custom rendering to a `<canvas>`, charting library, or other third party UI library.

{% hint style="danger" %}
Don't use Signals for propagation of state changes, potentially resulting in infinite circular updates, or unnecessary change detection cycles.
{% endhint %}

#### Injection Context

By deafult, you can only create `effect()` within an [Broken link](https://kdongs.gitbook.io/kdocs/angular/broken-reference "mention").

{% hint style="info" %}
Effects are destroyed automatically when their Components, Directives, Services, etc are destroyed.
{% endhint %}

```typescript
export class Component {
    constructor() {
        effect(() => { ... });
    }
}
```

You can create `effect` outside of a `constructor` or assign specific names to it by:

```typescript
export class Component {
    // Give a descriptive name to the effect
    private cEffect = effect(() => { ... });
    
    // To create an Effect outside of the constructor, get the Injector dependency
    constructor(private injector: Injector) {}
    
    log() {
        effect(() => { ... }, { injector: this.injector });
    }
}
```

#### Avoid signal tracking

To avoid re-run of effects when signal values are updated, wrap the signals with `untracked()`.

```typescript
export class Component {
    counter = signal(0);
    constructor() {
        effect(() => {
            console.log(`Counter is ${ untracked(counter) }`);
        });
    }
}
```

`untracked` is also useful when an effect needs to invoke some external code which shouldn't be treated as a dependency.

```typescript
effect(() => {
    const user = currentUser();
    untracked(() => {
        // If the `loggingService` reads signals, they won't be counted as dependencies of this effect.
        this.loggingService.log(`User set to ${user}`);
    });
});
```

#### Effect cleanup functions

Effect might start long-running operations, which you should cancel if the effect is destroyed or runs again before the first operation finished.

When creating effects, you can optionally pass an `onCleanup` function as its first parameter. And this `onCleanup` accepts a callback function that is **invoked** before the next run of the effect begins, or when the effect is destroyed.

```typescript
effect((onCleanup) => {
    const user = currentUser();
    const timer = setTimeout(() => {
        console.log(`1 second ago, the user became ${user}`);
    }, 1000);
    onCleanup(() => {
        clearTimeout(timer);
    });
});
```
