This example is going to cover the problem that I have been facing recently regarding passing function to the component.
Lets imagine we got quite a big application that is using e.g. a combobox or table component that provides the list of countries. The component is used in several places in the app.
Now there comes additional requirements – on one page there is a limitation, that user wants to see by default only his favourites countries.
So far everything was encapsulated in the component itself, the call to the backend was done in there as well:
import {Component, OnInit} from '@angular/core';
import {CountryService} from "../services/country.service";
import {Country} from "../model/country";
@Component({
selector: 'app-country-table',
templateUrl: './country-table.component.html',
styleUrls: ['./country-table.component.css']
})
export class CountryTableComponent implements OnInit {
countries: Country[] = [];
constructor(private countryService: CountryService) {
}
ngOnInit(): void {
this.countryService.getAllCountries().subscribe(data => {
this.countries = data;
// any other operations
}, error => console.log(error));
}
}
Now chages can be implementeed in few ways:
calls to the backed can be done in the parent component that is using the country component, and simply passed it as an input array – I am not saying that this approach is bad, just IMHO country component should do all the magic, and parent should be only responsible of using it with correct parameters.
there can be additional parameter e.g. useFavourite, and a condition in the country component that will call proper backend request – I would like to keep this component as hermetic as possible. In the future there can be some changes like e.g. show favourites from Europe that users has already visited. That would require adding again some additional parameters. This would not be very nice.
country component can be extended, to e.g. favourite-country. One method that is used to make a call to the backend in this case would be overwritten. In general I would like to avoid using inheritance, so this I would simply not use, or leave as the last one to use.
passing the function that is used for making call to the backend – seems reasonable to me.
After giving it some thoughts, I have decided to go on with the fourth approach – passing the function to the component. One very important thing that I have to take in count, is that already exiting use of the country component should not be changed at all, that means that I should provide a default callback call (get all countries) for the component, that should be always used when there is not one provided as an input.
First of all I have to provide the input with the default request call. Then I need to change in code the use of hardcoded method to the one given as the input function. In code it looks like that:
import {Component, OnInit} from '@angular/core';
import {CountryService} from "../services/country.service";
import {Country} from "../model/country";
export class CountryTableComponent implements OnInit {
countries: Country[] = [];
@Input() request: () => Observable<Country[]> =
this.countryService.getAllCountries;
constructor(private countryService: CountryService) {
}
ngOnInit(): void {
this.request().subscribe(data => {
this.countries = data;
// any other operations
}, error => console.log(error));
}
}
When I refreshed the application in the place where all countries should be used I got… empty page.
I thought what is wrong? The default function should be used, so I debugged the problem in the browser. I could see a call is made in the country component, so then I checked the service, and boy was I surprised. I added a breakpoint and this is what I saw in the service:
So the break is in the country service, but ‘this‘ is pointing to the… CountryTableComponent ?
Somehow the scope was incorrect, and this was my problem. After some analysis and internet search I found a solution for that. You have to use a bing method that (from API):
creates a new function that, when called, has its this keyword set to the provided value….
I changed the definition of the input property, and now the hole component looks like that:
@Component({
selector: 'app-country-table',
templateUrl: './country-table.component.html',
styleUrls: ['./country-table.component.css']
})
export class CountryTableComponent implements OnInit {
countries: Country[] = [];
@Input() request: () => Observable<Country[]> =
this.countryService.getAllCountries.bind(this.countryService);
constructor(private countryService: CountryService) {
}
ngOnInit(): void {
this.request().subscribe(data => {
this.countries = data;
// any other operations
}, error => console.log(error));
}
}
When I refreshed the app, all countries were displayed correctly.
When I wanted to show only favourite countries, the use of the component looked like that:
<app-country-table
[request]="getRequest">
</app-country-table>
and its definition like that
@Component({
selector: 'app-favourite-countries',
templateUrl: './favourite-countries.component.html',
styleUrls: ['./favourite-countries.component.css']
})
export class FavouriteCountriesComponent implements OnInit {
constructor(public countryService: CountryService) { }
ngOnInit(): void {
}
getRequest():Observable<Country[]>{
return this.countryService.getFavouritesCountries();
}
}
You can imagine this component being much more complex. I think in the future this kind of implementation should bring some benefits. Hope someone will find it usefull.
Simple implementation can be found in here
Top comments (0)