Reputation: 11
I'm new to Rxjs and Ngrx. I'm trying to display an array of data in a grouped format with Angular 12 using an Ngrx store facade. The data is an array of objects of Visits (declared in the Ngrx store). I can retrieve the data from the store and output it via HTML {{visits$ | async | json}}
and the result is the correct array data. However, as soon as I try to process that data, Rxjs fails with an error Property 'date' does not exist on type 'Visit[]'.
.
As a starting point for writing the Rxjs transformation, I put the data directly in the component. I can group the data and display the data just fine.
The component with
export interface Visit {
route: string;
name: string;
categoryIcon: string;
subCategoryName: string;
date: Date;
}
const VISITS: Visit[] = [
{
route: '/',
name: 'File Exports',
categoryIcon: 'assessment',
subCategoryName: 'Data Processing',
date: new Date(new Date().setDate(new Date().getDate() - 5)),
},
{
route: '/',
name: 'Recent Orders',
categoryIcon: 'emoji_events',
subCategoryName: 'Record Lists',
date: new Date(new Date().setDate(new Date().getDate() - 5)),
},
{
route: '/',
name: 'Frank Fuvik',
categoryIcon: 'person',
subCategoryName: 'Member Lookup',
date: new Date(new Date().setDate(new Date().getDate() - 5)),
},
{
route: '/',
name: 'Amanda Nollen',
categoryIcon: 'person',
subCategoryName: 'Member Lookup',
date: new Date(),
},
];
import {Component} from '@angular/core';
import { combineAll, groupBy, map, mergeMap, tap, toArray, zip } from 'rxjs/operators';
import { VisitsFacade, Visit } from '@fibonacci/controlcenter/feature-visits';
import { of } from 'rxjs';
@Component({
selector: 'cc-recent',
templateUrl: './recent.component.html',
styleUrls: ['./recent.component.scss'],
})
export class RecentComponent {
constructor(private facade: VisitsFacade) {}
visits$ = this.facade.allVisits$
.pipe(
tap(v => console.log('tap', v)),
groupBy((visit) => visit.date.toISOString().split('T')[0]),
mergeMap((group) => zip(of(group.key), group.pipe(toArray()))),
map(([age, value]) => of({key: age, visits: value})),
combineAll(),
map((group) => group.sort((a, b) => b.key.localeCompare(a.key))),
)
.subscribe();
}
Which results in:
[
{
"key": "Data Processing",
"visits": [
{
"route": "/",
"name": "File Exports",
"categoryIcon": "assessment",
"subCategoryName": "Data Processing",
"date": "2022-12-20T19:58:56.551Z"
}
]
},
{
"key": "Member Lookup",
"visits": [
{
"route": "/",
"name": "Frank Fuvik",
"categoryIcon": "person",
"subCategoryName": "Member Lookup",
"date": "2022-12-20T19:58:56.551Z"
},
{
"route": "/",
"name": "Amanda Nollen",
"categoryIcon": "person",
"subCategoryName": "Member Lookup",
"date": "2022-12-25T19:58:56.551Z"
}
]
},
{
"key": "Record Lists",
"visits": [
{
"route": "/",
"name": "Recent Orders",
"categoryIcon": "emoji_events",
"subCategoryName": "Record Lists",
"date": "2022-12-20T19:58:56.551Z"
}
]
}
]
When I convert this component to retrieving the data from the store's facade, it errors out Property 'date' does not exist on type 'Visit[]'.
When I comment out all but the tap(...)
The array shows up in the browser's console.
Looking at the error message, it seems to think the type is an array of Visits instead of an individual Visit?
I tried various operators trying to convert the stream of data of the observable into an array, but without any luck.
Perhaps the error is in the store's effects where I've hardcoded the data for now:
DEFAULT_VISITS is the exact same array as above.
import {DEFAULT_VISITS} from './visits.types';
import {Injectable} from '@angular/core';
import {createEffect, Actions, ofType} from '@ngrx/effects';
import {fetch} from '@nrwl/angular';
import * as VisitsActions from './visits.actions';
import * as VisitsFeature from './visits.reducer';
@Injectable()
export class VisitsEffects {
init$ = createEffect(() =>
this.actions$.pipe(
ofType(VisitsActions.init),
fetch({
run: (action) => {
// Your custom service 'load' logic goes here. For now just return a success action...
return VisitsActions.loadVisitsSuccess({visits: DEFAULT_VISITS});
},
onError: (action, error) => {
console.error('Error', error);
return VisitsActions.loadVisitsFailure({error});
},
}),
),
);
constructor(private readonly actions$: Actions) {}
}
Hoping that somebody can explain how store data, observable data streams and rxjs will play nicely together.
Upvotes: 0
Views: 307
Reputation: 11
@O.MeeKoh confirmed my suspicion of the stream of data returned from the store as wrapping itself around the array of visits. Here's a way to unpack the array from the stream of data, but I wouldn't be surprised if there is a much more elegant solution.
visits$ = from(this.facade.allVisits$)
.pipe(
mergeMap((list) => from(list).pipe(
groupBy((visit) => visit.date.toISOString().split('T')[0]),
mergeMap((group) => zip(of(group.key), group.pipe(toArray()))),
map(([age, value]) => of({key: age, visits: value})),
combineAll(),
map((group) => group.sort((a, b) => b.key.localeCompare(a.key))),
)),
)
Upvotes: 1
Reputation: 2156
Well reading your error message Property 'date' does not exist on type 'Visit[]'.
that simply means that in your application you have an array of visits but you are treating it like a single visit object and then try to access its date property. You probably have to iterate over the array first and then access the date property.
Upvotes: 0