Issue
Basically all I wanna do is display some data according to the item I clicked on the component before.
I created a component similar to a HousesComponent, which has a list of houses and their names. In order to display that information on that component, I have an interface called Houses which looks like the following:
export interface Houses {
id: number
name: string
}
Also, I have an array called housesArray which contains all the houses names and their ids:
export var housesArray = [
{name: 'pink house', id: 1},{name: 'blue house', id: 2}
]
In order to change the route parameters, I followed some tips and put this subscribe function inside the ngOnInit of my HousesTemplateComponent (which is the one that is going to recieve and display the data selected).
this.route.params.subscribe(params => this.getHouseById(params['id']))
My app-routing.module has 'house/id' as the path for the HousesTemplateComponent. The url changes perfectly according to each house I click on. Athough, when I try to bind the house name, it comes as undefined.
The way I looked up to do it was by creating a get function that requires an id as the parameter. Then, I get the house's id try to subscribe the house variable the information into it.
getHouseById(id: number){
this.houseService.getHouseById(id).subscribe((data: IHouses) => this.house= data)
}
The example I followed was made consuming a backend response, so they have an api for that. In my case, instead of using apis and a database (since I do not need one to show simple information), I am trying to access data from the housesArray.
The houseService has this get function:
house$ = new Subject();
getHouseById(id: number): Observable<IHouses>{
return this.house$.asObservable();
}
I am new at creating these type of routes without consuming an api, so I think the problem could be on the getHouseById function on my HouseService. I would appreaciate any help! Thanks in advance.
Edit (solution):
Thanks to @Michael Ziluck, here is the outcome of the help I got from them. Stackblitz Example
Solution
Main Problems
- There are no references to
housesArray
in yourgetHouseById
method ActivatedRoute
parameters are always strings, not numbers. Any number value whose equality is checked using===
must be parsed to a number first.
Recommended solution
This approach replicates the functionality of GET request to a REST API. First part should be the usage in the component, second part is the use in the template.
houses.component.ts
house$!: Observable<Houses>;
constructor(
private route: ActivatedRoute,
) {
}
ngOnInit() {
this.house$ = this.route.params.pipe(
map(params => parseInt(params["id"])), // could also just do +params["id"] but that is a less safe operation
switchMap(id => this.getHouseById(id)),
);
}
getHouseById(id: number): Observable<Houses> {
const house = housesArray.find(house => house.id === id);
return house === undefined
? throwError(() => new Error(`House not found with id ${id}`))
: of(house);
}
houses.component.html
<div>House ID: {{(house | async).id}}</div>
<div>House Name: {{(house | async).name}}</div>
If not using strict mode
Strict mode prevents the use of null
or undefined
when using type parameters. If you aren't using it, you can change that first line to be simply:
houses$: Subject<Houses> = new BehaviorSubject<Houses>(null);
Debugging
If things still aren't popping up for you, try some of these debug steps. For each of them, check out the console and see when things start returning as null
or undefined
.
Check that housesArray
is imported correctly
In getHouseById
, try adding these log statements right after the line that starts with const house
:
console.log("housesArray: ", housesArray);
console.log("house: ", house);
Check that the map
and switchMap
work correctly:
Change the operators within pipe
in the ngOnInit
to look like this:
this.house$ = this.route.params.pipe(
tap(params => console.log("params: ", params)),
map(params => parseInt(params["id"])),
tap(id => console.log("id: ", id)),
switchMap(id => this.getHouseById(id)),
tap(house => console.log("house: ", house)),
);
Alternatives
There is no single "right" way, but I would discourage use of the other answer here. In it, the houses$
property will be typed as Observable<IHouses[]>
which then forces you to filter it every time it is called.
Option 1:
getHouseById(id: number): Observable<Houses | undefined> {
return of(housesArray.find(house => house.id === +id));
}
Option 2 (if you're dead set on using a Subject :
houses$: ReplaySubject<Houses>;
constructor() {
this.houses$ = new ReplaySubject<Houses>(housesArray.length);
housesArray.forEach(house => houses$.next(house));
}
getHouseById(id: number): Observable<Houses> {
return this.houses$.pipe(first(house => house.id === +id));
}
Am a little confused on what IHouses
is as the interface you described above is called simply House
. Going to answer this operating under the assumption that IHouses
= House
and it was just a copy-paste typo.
Answered By - Michael Ziluck
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.