in a few words what I'm trying to do here is displaying some property of a specific item of the jobs array in a child component when navigating to /jobs/:id
Partent Component
export class HomeComponent implements OnInit {
public jobs!: JobInterface[]
constructor(
private jobsService: JobsService
) {
}
ngOnInit() {
this.getJobs()
}
getJobs(page = 1, filters = {city: 'Rome, Italy', level: ''}): void {
this.jobsService.getJobs(page, filters)
.subscribe((response) => {
this.jobs = response.results.map(job => ({
id: job.id,
contents: job.contents,
name: job.name,
publication_date: job.publication_date,
locations: job.locations,
levels: job.levels,
company: job.company
})
)
})
}
}
everything works on the routing perspective but into the child component the jobs array is shown as undefined making me unable to it for the desired item:
Child component
export class JobDetailsComponent implements OnInit {
jobId : any
@Input() jobs! : JobInterface[]
selectedJob : any
constructor(private ActivatedRoute : ActivatedRoute) { }
ngOnInit(): void {
this.jobId = this.ActivatedRoute.snapshot.paramMap.get('id')
console.log(this.jobs)
this.selectedJob = this.jobs.filter(this.jobId)
}
}
Partent component's HTML
<app-search-bar (OutputFilters)="getFilteredResults($event)" ></app-search-bar>
<app-job-cards [jobs]="jobs"></app-job-cards>
<app-job-details [jobs]="jobs"></app-job-details>
what is the correct way to do this? What am I doing wrong here?
Well, because the route changes, there's no jobs
or job
in the job-details
component. Also, components render before data is retrieved.
You could add a flag to render component when data is ready and then you could move job-details
component to job-cards
component, and pass a single job
from there via router, like so:
export class HomeComponent implements OnInit {
dataReady:boolean = false;
// add after the response..
this.dataReady = true;
and in the template:
<app-job-cards *ngIf="dataReady" [jobs]="jobs"></app-job-cards>
and then:
app-job-details
from home-component
pass job
to job-details
component via router
from job-cards
template:
<a mat-stroked-button routerLink="jobs/{{job.id}}" [state]="job">See More</a>
and then in the job-details
component use it via history
API:
ngOnInit(): void {
this.selectedJob = window.history.state;
}
But that's a bad approach, and also the route would be empty when accessed directly.. you should decide what to do with that..
A better approach would be to store jobs
in the jobs-service
instead of home-component
and passing them via parent/child, which will be shared between components, and add getJobs
and getJobDetails
methods that would get or fetch jobs, or find (rather than filter) a single job for job-details
component (and maybe fetch a single job if you want details page to always have some data), which is a lot of refactoring to do..
edit: sharing service data:
move processing from home components to jobs-service. There, combine fetch and processing methods, and fetch jobs if there are no any, else return jobs, as an Observable. Then, subscribe to that method to get jobs from each component.
Try:
getJobs(): Observable<JobInterface[]> {
if(this.jobs?.length > 0) {
return of(this.jobs);
}
return this.fetchJobs();
}
fetchJobs(page = 1, filters = { city: 'Rome, Italy', level: '' }): Observable<JobInterface[]> {
const params = new HttpParams({ encoder: new CustomHttpParamsEncoder() })
.set('category', 'Science and Engineering')
.set('page', page)
.set('location', filters.city)
.set('level', filters.level)
return this.http.get<APIResponseInterface>(this.url, { params })
.pipe(map(response => {
this.jobs = response.results.map(job => ({
id: job.id,
contents: job.contents,
name: job.name,
publication_date: job.publication_date,
locations: job.locations,
levels: job.levels,
company: job.company
})
)
return this.jobs;
})
);
}
getJobs(): void {
this.jobsService.getJobs().subscribe((jobs: JobInterface[]) => {
this.jobs = jobs;
this.dataReady = true;
});
}
ngOnInit(): void {
this.jobId = this.ActivatedRoute.snapshot.paramMap.get('id')
this.jobsService.getJobs().subscribe((jobs:JobInterface[]) => {
this.selectedJob = jobs.find(job=> job.id == this.jobId);
});
}
filter method would then call jobsService.fetchJobs
directly, and it would modify jobs throughout at the same time.
have a look at this example
How can i share data from http Response between components?
also check other solutions here