Reputation: 3182
I'm calling data from an NGXS state which when I console.log()
shows is present in the shape of the interface used to describe it. There's an items
property which is an array of 3 objects which I want to iterate over in my template. However when I target the items
property I get an error in vsCode telling me the property doesn't exist on that type and nothing renders on the screen.
My state model looks like this
export interface CattegoryIndexItem{
cattegory: string;
products: ProductIndexListItem[];
}
export interface ProductIndexListModel{ items: CattegoryIndexItem[]; }
my component looks like this
export class ProductPageComponent implements OnInit {
@Select() productIndexState$: Observable<ProductIndexListModel>;
constructor(private store: Store) {}
ngOnInit(): void {
this.store.dispatch(new GetProductIndexList());
}
showData(){
this.productIndexState$.subscribe(res => console.log(res));
}
}
inside my template
<article *ngFor="let item of productIndexState$.items | async">
<p>{{item.cattegory}}</p>
</article>
<button (click)="showData()">click</button>
When the page loads I get this error
error TS2339: Property 'items' does not exist on type 'Observable'.
I've never had this problem working with observables, is there something NGXS does that makes us need to access it differently?
Upvotes: 2
Views: 978
Reputation: 8001
Looks like a glitch in the template syntax - can't call .items
on the Observable itself, you want to call it on the object emitted by that Observable
.
So you need to pipe the Observable
via async
first e.g.
<ng-container *ngIf="productIndexState$ | async as productIndexState">
<article *ngFor="let item of productIndexState.items">
<p>{{item.cattegory}}</p>
</article>
<button (click)="showData()">click</button>
</ng-container>
Upvotes: 2
Reputation: 445
Glad that the given answer already helps.
I'd like to answer your comment here just to add to that answer.
You asked: now what makes this occur?
Now whats happening is this:
Your method showData() calls subscribe on the observable itself and unwraps it. This should have the structure of your interface. This interface then has the items property.
Your async pipe in your template
<article *ngFor="let item of productIndexState$.items | async">
<p>{{item.cattegory}}</p>
</article>
on the other site does not target the observable itself, but the items property. This does not exist here yet and also it not a valid observable which async could handle.
The async pipe of Angular works similar to the subscribe within showData(). It takes whatever is wrapped inside an observable and makes it available for processing.
What @Garth Mason did in his answer was calling async on the observable first to "unwrap" it and using the alias as to give it a proper name (without the $). The unwrapped object contains the items you are looking for which now can be iterated over via ngfor.
His code again for better reference:
<ng-container *ngIf="productIndexState$ | async as productIndexState">
<article *ngFor="let item of productIndexState.items">
<p>{{item.cattegory}}</p>
</article>
<button (click)="showData()">click</button>
</ng-container>
Just an additional comment on the ng-container wrapping your tag. Angular only allows a single directive like ngFor or ngIf on a tag. Therefor, whenever you need to do two "Angular process steps" within your template, you need something to do the first step (the ng-container here) and then your actual tags () which does the second step. The ng-container does not have a markup and it therefor not visible in your HTML. This is why it's perfect to wrap such processing steps ;-)
Upvotes: 1