Home > Software design >  How to bind data from another component without a http request in angular 14
How to bind data from another component without a http request in angular 14

Time:10-26

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.

CodePudding user response:

To convert the array to an observable use of.

To find the house with the correct id use pipe, map, find.

  house$ = of(housesArray);

  getHouseById(id: number): Observable<IHouses> {
    return this.house$.pipe(map((array) => array.find((h) => h.id === id)));
  }

Docs

of: https://www.learnrxjs.io/learn-rxjs/operators/creation/of

pipe: https://rxjs.dev/api/index/function/pipe

map: https://www.learnrxjs.io/learn-rxjs/operators/transformation/map

Example

const housesArray = [
 {name: 'pink house', id: 1},{name: 'blue house', id: 2}
]

const house$ = rxjs.of(housesArray);

function getHouseById(id) {
  return house$.pipe(rxjs.map((array) => array.find((h) => h.id === id)));
}

getHouseById(1).subscribe((h)=>console.log("House 1 name:", h.name))
getHouseById(2).subscribe((h)=>console.log("House 2 name:", h.name))
<script src="https://unpkg.com/rxjs@^7/dist/bundles/rxjs.umd.min.js"></script>


Although I'm not really sure I see the point in observables when you could just do:

  getHouseById(id: number): IHouses {
    housesArray.find((h) => h.id === id));
  }

Maybe you think change detection is tied to them in some way? Like hooks in React? That's not the case.

CodePudding user response:

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

houses$: Subject<Houses | null> = new BehaviorSubject<Houses | null>(null);

constructor(
  private route: ActivatedRoute,
) {
}

ngOnInit() {
  this.route.params.pipe(
    map(params => params["id"]),
    switchMap(id => this.getHouseById(id))
  ).subscribe(this.houses$);
}

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.route.params.pipe(
  tap(params => console.log("params: ", params)),
  map(params => params["id"]),
  tap(id => console.log("id: ", id)),
  switchMap(id => this.getHouseById(id)),
  tap(house => console.log("house: ", house)),
).subscribe(this.houses$);

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 housesArray.find(house => house.id === id);
}

Option 2 (same types and only accesses the array once):

houses$ = from(housesArray);

getHouseById(id: number): Observable<Houses> {
  return this.houses$.pipe(first(house => house.id === id));
}

Option 3 (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.

  • Related