Issue
I have a service like this:
const initialState: IUserState | null = null
@Injectable({
providedIn: 'root'
})
export class AuthService {
private user = signal(initialState)
constructor (private http: HttpClient) {
// setTimeout(() => this.user.set({ ...someFakeData }), 5000)
}
isAuthenticated = () => computed(() => !!this.user())
logIn = ({ email, password }: ILoginPayload) =>
this.http.post<LoginResponse>(`some/url`, { email, password })
.subscribe((data) => {
this.user.set(data)
})
updateSignal = () => this.user.set({ ...someFakeData })
}
export interface IUserState {
platform_id: string
name: string
email: string
picture_url: string
}
I also have a Login Component that calls logIn method like so:
onSubmit = () =>
this.authService.logIn({
email: this.loginForm.value.email!,
password: this.loginForm.value.password!
})
And a third component that listens to user signal:
export class AppComponent {
private authService = inject(AuthService)
isAuthenticated = this.authService.isAuthenticated()
}
When I try to update signal from .subscribe
method the app component will just not pick up the change.
But when I uncomment timeout in the constructor and update the signal from there, the component is picking up the change.
Also I added a method that will just update the signal, and when this method is called, signal gets updated and components pick it up without a problem.
So for some reason I am unable to update signal from my http Observable so that the change is being picked through the app. Signal gets updated but no component knows about it...
What am I doing wrong? Or is there any better pattern to handle http requests with signals. I tried to find some solutions online, but found nothing... I am using Angular version 17.0.8
Solution
I hope my answer will help a bit :)
I am not sure what kind of change detection strategy you have setup in the component. I assume OnPush
. I cannot see anything about zone
/zoneless
mentioned - I assume zone
as it is by default.
- this
isAuthenticated = () => computed(() => !!this.user())
should beisAuthenticated = computed(() => !!this.user())
- signals will automatically trigger change detection when they are utilized in a template - you just need to attach one of the signals to the template
- try to avoid subscribing observables outside of components - it is a kind of anti-pattern and may lead to pitfalls
See this exapmle code (stackblitz):
import { JsonPipe } from '@angular/common';
import { HttpClient, provideHttpClient } from '@angular/common/http';
import {
ChangeDetectionStrategy,
Component,
computed,
DestroyRef,
inject,
Injectable,
OnInit,
signal,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { bootstrapApplication } from '@angular/platform-browser';
import { finalize, tap } from 'rxjs';
import 'zone.js';
export interface IUserState {
platform_id: string;
name: string;
email: string;
picture_url: string;
}
const initialState: IUserState | null = null;
@Injectable({
providedIn: 'root',
})
export class AuthService {
#user = signal(initialState);
#login = signal(false);
user = this.#user.asReadonly();
login = this.#login.asReadonly();
isAuthenticated = computed(() => (!!this.#user() ? 'YES' : 'NO'));
http = inject(HttpClient);
logIn = ({ email, password }: any) => {
this.#login.set(true);
return this.http
.post<any>(`https://jsonplaceholder.typicode.com/posts`, {
email,
password,
})
.pipe(
tap((response) => this.setUser(response)),
finalize(() => this.#login.set(false))
);
};
setUser = (value: IUserState | null) => this.#user.set(value);
}
@Component({
selector: 'app-root',
standalone: true,
template: `
<h1>Hello from {{ name }}!</h1>
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
<p><input type="text" formControlName="email" placeholder="Email" [disabled]="authService.login()" /></p>
<p><input type="password" formControlName="password" placeholder="Password" [disabled]="authService.login()" /></p>
<p><button type="submit" [disabled]="authService.login()">Log in</button></p>
</form>
<pre>{{ authService.isAuthenticated() | json }}</pre>
<pre>{{ testValue | json }}</pre>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [JsonPipe, ReactiveFormsModule],
})
export class App implements OnInit {
authService = inject(AuthService);
destroyRef = inject(DestroyRef);
testValue: IUserState | null = null;
loginForm = new FormGroup({
email: new FormControl(null),
password: new FormControl(null),
});
name = 'Angular';
ngOnInit() {}
onSubmit() {
this.authService
.logIn(this.loginForm.value)
.pipe(
tap((response) => (this.testValue = response)),
takeUntilDestroyed(this.destroyRef)
)
.subscribe();
}
}
bootstrapApplication(App, { providers: [provideHttpClient()] });
Answered By - NgDaddy
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.