angularobservablengrx-storestate-managementngrx-effects

Why does my state observable tell me Type must have a [Symbol.iterator]()


Framework: Angular v18.1.0

Dependencies:

RxJS v7.8.0

@ngrx/effects v18.0.2

@ngrx/store v18.0.2


Given the following RxJS architecture. I have a simple reducer with one state and an interface with a single property of clinic_analytics.

reducer.ts

export interface ClinicAnalyticsState {
    clinic_analytics: ClinicAnalytics[]
}

export const initialState: Array<ClinicAnalytics> = []

export const ClinicAnalyticsReducer = createReducer(
    initialState,
    on(getClinicAnalyticsSuccess, (state, {clinic_analytics}) => ({...state, ...clinic_analytics}))
)

I then have a single action file, with two actions. Based on previous errors that I had researched I made sure to incorporate two different types for each action, and will continue that pattern going forward.

action.ts

export const getClinicAnalytics = createAction('[API: CLinic Analytics] Get Analytics')

export const getClinicAnalyticsSuccess = createAction(
    '[API: CLinic Analytics] Get Analytics Success',
    props<{clinic_analytics: ClinicAnalytics[]}>() 
)

I created an effect to trigger the service call. The effect has no issues triggering the service, or returning the data. I can place breakpoints in the reducer and will see the data there. I have verified that it is indeed returning a type of Observable<ClinicAnalyticsState>.

effect.ts

@Injectable()
export class ClinicAnalyticsEffect {
    
    loadClinicAnalytics$ : Observable<ClinicAnalyticsState> = createEffect(() => 
        this.actions$.pipe(
            ofType(getClinicAnalytics),
            exhaustMap(() => this.clinicAnalyticsService.getAll()
                .pipe(
                    map(clinic_analytics => getClinicAnalyticsSuccess({clinic_analytics})),
                    catchError(() => EMPTY)
                )
            )
        )
    )
    
    constructor(
        private actions$: Actions,
        private clinicAnalyticsService: ClinicAnalyticsService
    ) {}
}

For the sake of completion I have the service call here as well:

getAll() : Observable<ClinicAnalytics[]>{
    return this.http.get<Array<ClinicAnalytics>>('API LINK IS HERE').pipe(
      map(clinic_analytics => clinic_analytics)
    )
  }

After the architecture I have a simple compononent:

export class MetricsComponent {
  clinicAnalytics$ = this.store.select(clinic_analytics => clinic_analytics)
  _clinicAnalytics$: ClinicAnalytics[] = [];

  constructor(
    private store: Store<ClinicAnalyticsState>) { }

  ngOnInit() {
    this.store.dispatch(getClinicAnalytics())
  }
}

and in the template I have the following

<tbody class="table-group-divider">
    @for(clinic of (clinicAnalytics$); track clinic) {
        <tr>
            <td></td>
            <td></td>
            <td></td>
            <td></td>
            <td></td>
            <td></td>
            <td></td>
            <td></td>
            <td></td>
            <td></td>
        </tr>
    }
</tbody>

it is at @for(clinic of (clinicAnalytics$ | async); track clinic) that I am receiving the error:

Type 'Observable<ClinicAnalyticsState>' must have a '[Symbol.iterator]()' method that returns an iterator.

I have double and triple-checked my architecture, of the state management, to make sure I didn't miss something. Everything I have compared it to checks out. I have made sure the return object on the service call has an iterator hence the pipe map schema, and the same types are being returned across the board. If everything is set up correctly then why is this occurring? I can't find any differences between my error and the solutions that have been provided previously that supposedly fix this issue.

EDIT: I have changed some of the code reflecting current answers and other attempts. The updates were made to the reducer and the html template


Solution

  • Your observable returns an object of type {clinic_analytics: ClinicAnalytics[]}.

    So you need to access the inner property clinic_analytics which contains the array, which is iterable and will get rid of the error.

    @for(clinic of (clinicAnalytics$ | async).clinic_analytics; track clinic) {
        ...
    }