angularsignals

Passing an Angular signal value from a component input into a service


I was doing a little POC work with Angular 17 and signals and hit a use case that I'm not sure how to address without devolving into ngOnChanges and @Input handling.

So, say you have a component with input signals. You then want to copy / set those same values into a service so that components in that hierarchy can access it.

I tried to put writable signals in the service, but the trick is trying to set the service values from the component inputs with the right timing. So I could be setting the value into the service with the component initial signal values, but then they don't track changes.

So this seemed like an okay use for toObservable or effect, but it sounds that toObservable uses effects. What seems to happen is that effects run after children components are initialized (ngOnInit being called). So, my question is how to listen for signal value changes, be able to set them on something else (provider / service) without passing the actual signal which doesn't seem like a good idea.

With just "normal" components, using ngOnChanges makes it simple to track the changes, but I'm wondering how it could be tracked using signals with the same timing.

Greatly simplified example, but shows the issue

@Injectable()
export class ExampleProvider {
  readonly name = signal('');
}

@Component({ 
  ...
  providers: [ExampleProvider]
})
export class ExampleComponent {
  readonly name = input<string>();
  private readonly exampleProvider = inject(ExampleProvider);

  constructor() {
    effect(() => {
      // This will run after `ngOnInit` of child components
      this.exampleProvider.name.set(this.name());
    });
  }
}

How can one propagate the changes to the input of the component to the service and have it set before the child components are initialized / re-rendered?

https://stackblitz.com/edit/stackblitz-starters-hjmcgz?file=src%2Fmain.ts

You can run this example. There are 4 inputs. While the UI will show the correct values, if you open the console, you will notice that during ngOnInit of the child component, the service has not been updated yet for the values set by effect nor toObservable.

You will see that during an input change, two change detections will run. You will see the first one has the old value and then the second one will have updated value.

Also, during the first rendering you will see the values for 2 and 4 have not been set yet:

top-level.component.ts:66 TopLevelComponent ngOnInit
  input1: a
  input2: b
  input3: c
  input4: d
child.component.ts:39 ChildComponent ngOnInit
  input1: a
  input2: 
  input3: c
  input4: 

Solution

  • Upgrading to Angular 19 fixes the issue. The timing of effects are now correct and the signal values propogate as desired/expected.

    https://stackblitz.com/edit/stackblitz-starters-awtcv4ds?file=package.json,src%2Ftop-level%2Ftop-level.component.ts

    enter image description here