Reputation: 597
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.
I have created a StackBlitz to demonstrate this issue
Upvotes: 1
Views: 1129
Reputation: 81
You could try converting the signal to an observable before passing it to your load
function like this:
@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 {
id = input.required<number>();
user = this.store.user;
isBusy = this.store.isBusy;
private id$ = toObservable(this.id);
private readonly store = inject(UserStore);
constructor() {
this.store.load(this.id$);
}
}
Without the effect in the components constructor function the error should go away.
Upvotes: 0