I am converting to using signal inputs and ran into an interesting issue with signals and NgRx ComponentStore.
Component Store:
interface State {
user: any;
isBusy: boolean;
}
@Injectable()
export class UserStore extends ComponentStore<State> {
private readonly http = inject(HttpClient);
readonly user = this.selectSignal((state) => state.user);
readonly isBusy = this.selectSignal((state) => state.isBusy);
private readonly updateUser = this.updater((state, user: any) => ({
...state,
user,
isBusy: false,
}));
readonly load = this.effect((id$: Observable<number>) =>
id$.pipe(
tap(() => this.patchState({ isBusy: true })), // Causes the signal write error
switchMap((id: number) =>
this.http
.get<any>(`https://jsonplaceholder.typicode.com/users/${id}`)
.pipe(
tapResponse(
(user) => this.updateUser(user), // Does not cause signal write error because it's after the http call
() => this.patchState({ isBusy: false })
)
)
)
)
);
constructor() {
super({
user: null,
isBusy: false,
});
}
}
Component:
@Component({
selector: 'app-user',
standalone: true,
imports: [CommonModule],
template: `<div>
Is Busy: {{ isBusy() }}
@if(!isBusy()) {
<pre>{{ user() | json }}</pre>
}
</div>`,
styleUrl: './user.component.css',
providers: [UserStore],
})
export class UserComponent {
private readonly store = inject(UserStore);
id = input.required<number>();
user = this.store.user;
isBusy = this.store.isBusy;
constructor() {
effect(
() => {
this.store.load(this.id());
// Fixes error, but only works the first time the input is set
// asapScheduler.schedule(() => this.store.load(this.id()));
},
{
//allowSignalWrites: true, // Fixes issue, but is it the right way?
}
);
}
}
I want to use an effect on the signal input to trigger loading data, but if I modify anything in state prior to the http call, I get the 'writing to signals' error. This makes sense to me. I know that is how it's supposed to operate. However, in a case like this, is it best to just enable the writing to signals and call it good? Or should this whole thing be refactored to accomplish this another way?
Also, it's worth noting that I am following the recommended way of implementing the effects from the documentation fairly closely.
You could try converting the signal to an observable before passing it to your
loadfunction like this:Without the effect in the components constructor function the error should go away.