Skip to content

usePreviousSignal

Creates a signal that tracks the previous value of a source signal. Useful for comparing current vs previous state or implementing undo functionality.

Usage

typescript
import { usePreviousSignal } from 'ng-reactive-utils';

@Component({
  template: `
    <p>Current: {{ count() }}</p>
    <p>Previous: {{ previousCount() }}</p>
    <button (click)="increment()">Increment</button>
  `,
})
class ExampleComponent {
  count = signal(0);
  previousCount = usePreviousSignal(this.count);

  increment() {
    this.count.update((v) => v + 1);
  }
}

Parameters

ParameterTypeDefaultDescription
sourceSignalSignal<T>requiredThe source signal to track previous value

Returns

Signal<T | undefined> - A readonly signal containing the previous value (undefined initially)

Notes

  • The previous signal starts with undefined on first read
  • Updates to track the previous value whenever the source changes
  • Returned signal is readonly to prevent direct manipulation

Source

ts
import { Signal, effect, signal } from '@angular/core';

/*
 * Creates a signal that tracks the previous value of a source signal. Useful for comparing
 * current vs previous state or implementing undo functionality.
 *
 * @param sourceSignal - The source signal to track the previous value of.
 *
 * Example:
 *
 * const currentValue = signal('hello');
 * const previousValue = usePreviousSignal(currentValue);
 *
 * // previousValue() will be undefined initially, then track the previous value
 * console.log(previousValue()); // undefined
 * currentValue.set('world');
 * console.log(previousValue()); // 'hello'
 */
export function usePreviousSignal<T>(sourceSignal: Signal<T>): Signal<T | undefined> {
  const previousSignal = signal<T | undefined>(undefined);
  let lastValue: T | undefined = undefined;
  let isFirstRun = true;

  // Track changes via effect to avoid side effects inside computed
  effect(() => {
    const currentValue = sourceSignal();
    if (!isFirstRun) {
      previousSignal.set(lastValue);
    }
    lastValue = currentValue;
    isFirstRun = false;
  });

  return previousSignal.asReadonly();
}

Released under the MIT License.