Skip to content
rdlabo.devdocs

signal-use-as-signal

This plugin check to valid signal use as signal.

Angular Signals are getter functions. Reading them requires (), and writing them must go through .set() or .update(). This rule catches code that uses a Signal variable as if it were a plain value, and it can auto-fix many common mistakes.

Rule Details

The rule tracks class properties initialized with Signal factories (signal, model, input, linkedSignal, toSignal, asReadonly) and reports misuse such as:

  • this.count instead of this.count() in an expression context
  • this.count() = value instead of this.count.set(value)
  • this.user().name = 'Jane' instead of this.user.update(user => ({ ...user, name: 'Jane' }))
  • this.items().push(x) instead of this.items.update(items => { items.push(x); return items; })
  • this.#user = value (direct assignment to a Signal property) instead of this.#user.set(value)

The rule distinguishes between contexts where a Signal reference is expected and contexts where its value is expected. For example, passing a Signal object as a prop is allowed:

const props = { food: this.food };
launchModal({ food: this.food });

Examples

Incorrect

export class SigninPage {
  readonly #id = signal<number | undefined>(undefined);

  constructor() {
    this.#id = 1;
  }

  useMethod() {
    if (this.#id) {
      this.#id().hoge = 1;
    }
  }
}
export class SigninPage {
  readonly #user = signal<{ name: string }>({ name: 'John' });

  updateUser() {
    this.#user().name = 'Jane';
  }
}
export class SigninPage {
  readonly #numbers = signal<number[]>([1, 2, 3]);

  updateNumbers() {
    this.#numbers().push(4);
  }
}
export class SigninPage {
  readonly #value = signal<number>(0);

  updateValue() {
    this.#value() = 42;
  }
}

Correct

export class SigninPage {
  readonly #user = signal<{ name: string }>({ name: 'John' });

  updateUser() {
    this.#user.update((user) => ({ ...user, name: 'Jane' }));
  }
}
export class SigninPage {
  readonly #numbers = signal<number[]>([1, 2, 3]);

  updateNumbers() {
    this.#numbers.update((numbers) => {
      numbers.push(4);
      return numbers;
    });
  }
}
export class SigninPage {
  readonly #value = signal<number>(0);

  updateValue() {
    this.#value.set(42);
  }
}
export class SigninPage {
  readonly food = signal<number>(0);

  openPreview() {
    const props = { food: this.food };
    launchModal({ food: this.food });
  }
}

Auto-fix

The rule provides auto-fix for the patterns above:

  • this.count = value -> this.count.set(value)
  • this.count() = value -> this.count.set(value)
  • this.count().x = value -> this.count.update(value => ({ ...value, x: value }))
  • this.count().push(x) -> this.count.update(value => { value.push(x); return value; })

Options

This rule has no options.

When to enable

Enable this rule in any Angular project that uses Signals. It is complementary to @rdlabo/rules/signal-use-as-signal-template, which checks Signal usage in templates.

See also

Implementation