AngulartoSignal options lab: initialValue, requireSync, NG0601 and errors
import { Component, Injector, inject, signal } from "@angular/core";
import { toSignal } from "@angular/core/rxjs-interop";
import { BehaviorSubject, Observable, Subject, catchError, of } from "rxjs";
const failingHttp = () => new Observable<string>((sub) => sub.error(new Error("HTTP 500")));
@Component({
selector: "app-root",
template: `
<p>no initialValue: {{ plain() ?? "undefined" }}</p>
<p>initialValue -1: {{ withInitial() }}</p>
<p>requireSync on BehaviorSubject: {{ fromBehavior() }}</p>
<p>error caught in the pipe: {{ safe() }}</p>
<button (click)="push()">subject.next(7)</button>
<button (click)="tryRequireSync()">requireSync on a Subject</button>
<button (click)="readFailing()">read a failed toSignal</button>
<p>{{ message() }}</p>
`,
})
export class AppComponent {
private injector = inject(Injector);
private subject = new Subject<number>();
plain = toSignal(this.subject);
withInitial = toSignal(this.subject, { initialValue: -1 });
fromBehavior = toSignal(new BehaviorSubject("ready"), { requireSync: true });
failing = toSignal(failingHttp(), { initialValue: "n/a" });
safe = toSignal(failingHttp().pipe(catchError(() => of("fallback"))), { initialValue: "" });
message = signal("");
push() { this.subject.next(7); }
tryRequireSync() {
try {
toSignal(new Subject<number>(), { requireSync: true, injector: this.injector });
} catch (e: any) { this.message.set(e.message); }
}
readFailing() {
try { this.failing(); } catch (e: any) { this.message.set(`reading threw: ${e.message}`); }
}
}
AngularuserId signal to HttpClient with switchMap and back to a view-state signal
import { ApplicationConfig, Component, Injectable, computed, inject, signal } from "@angular/core";
import { HttpClient, HttpErrorResponse, HttpInterceptorFn, HttpResponse, provideHttpClient, withInterceptors } from "@angular/common/http";
import { toObservable, toSignal } from "@angular/core/rxjs-interop";
import { catchError, delay, map, of, startWith, switchMap, throwError, timer } from "rxjs";
type User = { id: number; name: string };
type View = { state: "loading" } | { state: "ok"; user: User } | { state: "error"; message: string };
@Injectable({ providedIn: "root" })
export class UserApi {
private http = inject(HttpClient);
get(id: number) { return this.http.get<User>(`/api/users/${id}`); }
}
@Component({
selector: "app-root",
template: `
<button (click)="userId.update(id => id + 1)">Next user</button>
@switch (view().state) {
@case ("loading") { <p>Loading user {{ userId() }}...</p> }
@case ("ok") { <p>{{ name() }}</p> }
@case ("error") { <p>Could not load user {{ userId() }}</p> }
}
`,
})
export class AppComponent {
private api = inject(UserApi);
userId = signal(1);
view = toSignal(
toObservable(this.userId).pipe(
switchMap((id) =>
this.api.get(id).pipe(
map((user): View => ({ state: "ok", user })),
catchError((e) => of<View>({ state: "error", message: e.message })),
startWith<View>({ state: "loading" }),
),
),
),
{ initialValue: { state: "loading" } as View },
);
name = computed(() => { const v = this.view(); return v.state === "ok" ? v.user.name : ""; });
}
const fakeBackend: HttpInterceptorFn = (req) => {
const id = Number(req.url.split("/").pop());
const users: Record<number, string> = { 1: "Ada Lovelace", 2: "Grace Hopper" };
if (!users[id]) return timer(20).pipe(switchMap(() => throwError(() => new HttpErrorResponse({ status: 404, url: req.url }))));
return of(new HttpResponse({ status: 200, body: { id, name: users[id] } })).pipe(delay(20));
};
export const appConfig: ApplicationConfig = {
providers: [provideHttpClient(withInterceptors([fakeBackend]))],
};