In my code I am trying to get the result from this observable:
// Runs fine
obtenerOfertaPorId(id: number): Observable<Oferta> {
return this.http.get<Oferta>(`${this.apiUrl}${this.base_url}/obtenerOfertaPorId/${id}`)
.pipe(
tap(resultado => console.log(resultado)),
catchError(error => {
console.log('Error fetching oferta by id');
return throwError(() => new Error(error))
})
);
}
On this component:
export class JobPostDetailPage {
private activatedRoute = inject(ActivatedRoute);
private id = this.activatedRoute.snapshot.paramMap.get('id')!;
private ofertaService = inject(OfertaService);
jobPost = toSignal(this.ofertaService.obtenerOfertaPorId(parseInt(this.id)));
}
To send the result as an input signal to the children:
<navbar-wrapper/>
<job-posting-form [jobPostInput]="jobPost()"/>
<wkFooter/>
And then the job-posting-form (This component is also for creating new entities, which is why is too convoluted, but I am open to ideas to make it more clean)
export class JobPostingForm {
private ofertaMapper = OfertaMapper;
private fb = inject(NonNullableFormBuilder);
private titleCasePipe = new TitleCasePipe();
private contrataService = inject(ContrataService);
formUtils = FormUtilsJobPosting;
router = inject(Router);
jobPostInput = input<Oferta>();
tiposContratoOptions = Object.values(TipoContrato).map(value => ({
label: this.titleCasePipe.transform(value),
value: value
}));
tiposModalidadesOptions = Object.values(ModalidadTrabajo).map(value => ({
label: this.titleCasePipe.transform(value),
value: value
}));
jobPostingForm: FormGroup<OfertaFormGroup> = this.fb.group({
puesto: [this.jobPostInput()?.puesto || '', [Validators.required, Validators.pattern(this.formUtils.onlyCharactersRegex), Validators.maxLength(23)]],
sector: [this.jobPostInput()?.sector || '', [Validators.required, Validators.pattern(this.formUtils.onlyCharactersRegex), Validators.maxLength(23)]],
descripcion: [this.jobPostInput()?.descripcion || '', Validators.maxLength(23)],
ciudad: [this.jobPostInput()?.ciudad || '', [Validators.required, Validators.pattern(this.formUtils.onlyCharactersRegex), Validators.maxLength(23)]],
horas: [this.jobPostInput()?.horas || null as number | null, [Validators.required, Validators.pattern(this.formUtils.onlyNumbersRegex)]],
salarioAnual: [this.jobPostInput()?.salarioAnual || null as number | null, [Validators.required, Validators.pattern(this.formUtils.onlyNumbersRegex)]],
modalidadTrabajo: [this.jobPostInput()?.modalidadTrabajo || null as ModalidadTrabajo | null, Validators.required],
tipoContrato: [this.jobPostInput()?.tipoContrato || null as TipoContrato | null, Validators.required],
});
submitForm () {
this.jobPostingForm.markAllAsTouched();
if (this.jobPostingForm.valid) {
this.contrataService.uploadNewOferta(
this.ofertaMapper.mapNewOfertaFormGroupToOferta(this.jobPostingForm)
).subscribe({
next: () => this.router.navigate(['employerSection','myJobPostings'])
})
}
}
goBackEvent () {
this.router.navigate(['employerSection','myJobPostings'])
}
}
My problem is that the input does not register the changes when the observable resolves and thus the form does not render the values from the observable request.
I already tried using the async pipe sending the observable on the input and I also tried using ngOnChanges to change the input value, but that is not possible.
Can it be done without lifecycle hooks? I am using Angular 20.
EDIT: Naren Murali and Lavi Kumar responses didn´t work straight but they were helpful to figure out a solution combining both, so thanks to them for their contributions.
Apparently if you set the value of a field from an input, the value of the FormControl won´t update by itself even if the value of the input changes:
sector: [this.jobPostInput()?.sector || '', [Validators.required, Validators.pattern(this.formUtils.onlyCharactersRegex), Validators.maxLength(23)]],
// This FormControl value won´t update by itself even if jobPostInput changes
So I had to manage it by myself.
First I have changed the way I get my Observable result by getting it with a reactive resource to handle the Observable:
// job-post-detail-page.ts
export class JobPostDetailPage {
private activatedRoute = inject(ActivatedRoute);
private id = computed(() =>
parseInt(this.activatedRoute.snapshot.paramMap.get('id')!)
);
private ofertaService = inject(OfertaService);
jobPost = rxResource({
params: () => this.id(),
stream: ({ params: id }) => this.ofertaService.obtenerOfertaPorId(id),
defaultValue: {} as Oferta, // Required 'as Oferta', otherwise would fail
});
}
Then I asked on the html if the resource was resolved:
<!-- job-post-detail-page.html -->
<navbar-wrapper/>
@if (jobPost.status() === 'resolved') {
<job-posting-form [jobPostInput]="jobPost.value()"/>
}
<wkFooter/>
And finally, I declared a horrendous ugly effect to manually change the form values with the updated ones from the input:
export class JobPostingForm {
/** Same code **/
jobPostInput = input<Oferta>();
jobPostingForm: FormGroup<OfertaFormGroup> = this.fb.group({
puesto: ['', [Validators.required, Validators.pattern(this.formUtils.onlyCharactersRegex), Validators.maxLength(23)]],
sector: ['', [Validators.required, Validators.pattern(this.formUtils.onlyCharactersRegex), Validators.maxLength(23)]],
descripcion: ['', Validators.maxLength(500)],
ciudad: ['', [Validators.required, Validators.pattern(this.formUtils.onlyCharactersRegex), Validators.maxLength(23)]],
horas: [null as number | null, [Validators.required, Validators.pattern(this.formUtils.onlyNumbersRegex)]],
salarioAnual: [null as number | null, [Validators.required, Validators.pattern(this.formUtils.onlyNumbersRegex)]],
modalidadTrabajo: [null as ModalidadTrabajo | null, Validators.required],
tipoContrato: [null as TipoContrato | null, Validators.required],
});
private _patchFormEffect = effect(() => {
const jobPost = this.jobPostInput();
if (jobPost) {
this.jobPostingForm.patchValue({
puesto: jobPost.puesto,
sector: jobPost.sector,
descripcion: jobPost.descripcion ?? '',
ciudad: jobPost.ciudad,
horas: jobPost.horas,
salarioAnual: jobPost.salarioAnual,
modalidadTrabajo: jobPost.modalidadTrabajo,
tipoContrato: jobPost.tipoContrato,
});
}
});
/** Same code **/
}
Unnecesary overengineered solution imo