I am stuck with above error. [NG8: Type 'Signal<User[]>' must have a '[Symbol.iterator]()' method that returns an iterator.] I am using ngrx/signal-store for data management with my angular app. I used "typescript": "5.8.2",
After I received this error, I modified my tsconfig.json, tsconfig.app.json as added the following config:
"lib": ["es2022", "dom", "dom.iterable"]
but above error not suspended. also I checked my template it's exactly shows that my signal is an Array. I stuck up to handle this error.
Looking for advice by sharing my template:
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, inject, effect } from '@angular/core';
import { USERSTORE, UserStoreType } from './users-store/user.store';
@Component({
selector: 'app-users',
standalone: true,
imports: [CommonModule],
template: `
<div class="users-container">
<h1 class="users-title">Users {{ $userStore.$name() || '' }}</h1>
@if ($userStore.$userLoading()) {
<p class="loading-message">Loading users...</p>
} @else if ($userStore.$userError()) {
<p class="error-message">Error: {{ $userStore.$userError() }}</p>
} @else {
@if ($userStore.$users().length > 0) {
<ul class="users-list">
@for (user of $userStore.$users(); track user.userId) {
<li class="user-item">{{ user.userName }}</li>
}
</ul>
} @else {
<p class="no-users-message">No users found.</p>
}
}
</div>
`,
providers: [USERSTORE],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class Users {
protected readonly $userStore: UserStoreType = inject(USERSTORE);
constructor() {
effect(() => {
console.log('Hi', Array.isArray(this.$userStore.$users())); //true
});
}
}
attaching ngrx store
import { httpResource, HttpResourceRef } from '@angular/common/http';
import { computed } from '@angular/core';
import { signalStore, withComputed, withProps, withState } from '@ngrx/signals';
import { ApiResponse, initialUserState } from './user.slice';
const basePath = 'https://api.freeprojectapi.com/api/BusBooking';
const getAllUsers = `${basePath}/GetAllUsers`;
export const USERSTORE = signalStore(
withState(initialUserState),
withProps(() => ({
_users: httpResource<ApiResponse>(() => getAllUsers)
})),
withComputed(({ _users }: { _users: HttpResourceRef<ApiResponse | undefined> }) => ({
$users: computed(() => (_users.hasValue() ? _users.value()?.data || [] : [])),
$userLoading: computed(() => _users.isLoading()),
$userError: computed(() => _users.error()),
$name: computed(() => `${initialUserState.$name()}${String(new Date().getMilliseconds())}`)
}))
);
export type UserStoreType = InstanceType<typeof USERSTORE>;
inteface:
import { Signal, signal } from '@angular/core';
export interface User {
userId: number;
userName: string;
emailId: string;
role: string;
}
export interface UserState {
$users: Signal<User[]>;
$name: Signal<string>;
$userLoading: Signal<boolean>;
$userError: Signal<boolean>;
}
export const initialUserState: UserState = {
$users: signal([]),
$name: signal(''),
$userLoading: signal(false),
$userError: signal(false)
};
export interface ApiResponse {
data: User[];
message?: string;
status?: string;
}
We need not define the signal types in the initial state type. NgRx takes care of that under the hood.
That is why you are getting the error, because the state properties defined are basically a signal inside a signal, which is not necessary.
export interface User {
userId: number;
userName: string;
emailId: string;
role: string;
}
export interface UserState {
users: User[];
_users: HttpResourceRef<ApiResponse | undefined> | null;
name: string;
}
export const initialUserState: UserState = {
users: [],
_users: null,
name: '',
};
export interface ApiResponse {
data: User[];
message?: string;
status?: string;
}
We need to make sure the HttpResourceRef property exists, which takes care of storing the resource for created the derived states.
Then we can use this _users resource reference and calculate the derived states.
We can also mark the httpResource with asReadOnly so that we can access the data but not write to the resource (Read Only).
const basePath = 'https://api.freeprojectapi.com/api/BusBooking';
const getAllUsers = `${basePath}/GetAllUsers`;
export const USERSTORE = signalStore(
withState(initialUserState),
withProps(() => ({
_users: httpResource<ApiResponse>(() => getAllUsers).asReadonly(),
})),
withComputed((state) => ({
$users: computed(() =>
state._users.hasValue() ? state._users.value()?.data || [] : []
),
$userLoading: computed(() => state._users.isLoading()),
$userError: computed(() => state._users.error()),
$name: computed(
() => `${state.name()}${String(new Date().getMilliseconds())}`
),
}))
);
import {
ChangeDetectionStrategy,
Component,
inject,
effect,
} from '@angular/core';
import { CommonModule, DecimalPipe } from '@angular/common';
import { bootstrapApplication } from '@angular/platform-browser';
import 'zone.js';
import { CurrencySwitcherComponent } from './currency-switcher/currency-switcher.component';
import { CurrencyStore } from './currency.store';
import { provideHttpClient } from '@angular/common/http';
import { httpResource, HttpResourceRef } from '@angular/common/http';
import { computed } from '@angular/core';
import { signalStore, withComputed, withProps, withState } from '@ngrx/signals';
import { Signal, signal } from '@angular/core';
export interface User {
userId: number;
userName: string;
emailId: string;
role: string;
}
export interface UserState {
users: User[];
_users: HttpResourceRef<ApiResponse | undefined> | null;
name: string;
}
export const initialUserState: UserState = {
users: [],
_users: null,
name: '',
};
export interface ApiResponse {
data: User[];
message?: string;
status?: string;
}
const basePath = 'https://api.freeprojectapi.com/api/BusBooking';
const getAllUsers = `${basePath}/GetAllUsers`;
export const USERSTORE = signalStore(
withState(initialUserState),
withProps(() => ({
_users: httpResource<ApiResponse>(() => getAllUsers).asReadonly(),
})),
withComputed((state) => ({
$users: computed(() =>
state._users.hasValue() ? state._users.value()?.data || [] : []
),
$userLoading: computed(() => state._users.isLoading()),
$userError: computed(() => state._users.error()),
$name: computed(
() => `${state.name()}${String(new Date().getMilliseconds())}`
),
}))
);
export type UserStoreType = InstanceType<typeof USERSTORE>;
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule],
template: `
<div class="users-container">
<h1 class="users-title">Users {{ $userStore.$name() || '' }}</h1>
@if ($userStore.$userLoading()) {
<p class="loading-message">Loading users...</p>
} @else if ($userStore.$userError()) {
<p class="error-message">Error: {{ $userStore.$userError() }}</p>
} @else {
@if ($userStore.$users().length > 0) {
<ul class="users-list">
@for (user of $userStore.$users(); track user.userId) {
<li class="user-item">{{ user.userName }}</li>
}
</ul>
} @else {
<p class="no-users-message">No users found.</p>
}
}
</div>
`,
providers: [USERSTORE],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class App {
protected readonly $userStore: UserStoreType = inject(USERSTORE);
constructor() {
effect(() => {
console.log('Hi', Array.isArray(this.$userStore.$users())); //true
});
}
}
bootstrapApplication(App, { providers: [provideHttpClient()] });