javascriptangulartypescriptrefactoringangular-signals

How do I refactor an `@Input` which is being updated directly, which is not possible to do in `input` signal


I am refactoring my angular code to work with signals. I have this below code, which needs to be refactored.

@Component({ ... })
export class SomeComponent {
  @Input() test = null;

  ngOnInit() {
    this.test = 'qwerty';
  }
  ...

There was no error when I update an @Input before input signal.

Logically I would refactor this to:

@Component({ ... })
export class SomeComponent {
  test = input(null);

  ngOnInit() {
    this.test.set('qwerty'); // <- set does not exist for input signal
  }
  ...

So in the above code, input signal does not have a set or update method, so my refactor is wrong, what is the logical way to solve this problem, the update must be done.


Solution

  • Linked Signal:

    We can solve this problem, by using a linkedSignal which updates the local state when there are new updates from the input signal, as well as having set and update methods to react to update the local state.

    One more thing you should do is rename the signal input to a different name, but use the alias property to not change the HTML binding name (less refactoring in HTML side).

    @Component({ ... })
    export class SomeComponent {
      // change the input signal name but use alias to keep the same binding name
      readonly testInput = input(null, { alias: 'test' });
      // linked signal takes the value of input signal, can be used for modify local state
      // if new value comes from input signal, local state is overridden
      readonly test = linkedSignal(() => this.testInput());
    
      ngOnInit() {
        this.test.set('qwerty'); // <- set does not exist for input signal
      }
      ...
    

    Without LinkedSignal:

    If your angular does not have linkedSignal (Before Angular 19), then you can use an effect to achieve the same behavior:

    @Component({ ... })
    export class SomeComponent {
      // change the input signal name but use alias to keep the same binding name
      readonly testInput = input(null, { alias: 'test' });
      readonly test = signal(undefined);
      constructor() {
        effect(() => {
          this.test.set(this.testInput());
        }, { allowSignalWrites: true });
      }
    
      ngOnInit() {
        this.test.set('qwerty'); // <- set does not exist for input signal
      }
      ...