NBash
NBash

Reputation: 515

Angular Component renders before getting the data from backend

I am having two components on one page: parent and child. Parent component makes a get request getCityForecast(cityCode) and binds the data to the child component.

The issue is that the child component reders before the parent one. and data is not popelated. How I can solve it?

Parent component

HTML

<div class="search-field">
  <input type="search" placeholder="Search..." [(ngModel)]="cityInput" (ngModelChange)="getAutoCompleteResults()">
  <button class="search-button">
    <svg id="search-icon" class="search-icon" viewBox="0 0 24 24">
      <path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
      <path d="M0 0h24v24H0z" fill="none"/>
    </svg>
  </button>
  <div *ngIf="hasSearchResults()">
    <p *ngFor="let city of autoCompleteSearchResults" (click)="cityChoice(city.LocalizedName, city.Key); getCityForecast(city.Key)">{{city.LocalizedName}}</p>
  </div>
</div>

<app-todays-forecast
  [city]="cityDetails"
  [todaysForecast]="todaysForecast">
</app-todays-forecast>

TS

import { Component, OnInit } from '@angular/core';
import { DataFetchService } from '../services/data-fetch.service';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-local-forecast',
  templateUrl: './local-forecast.component.html',
  styleUrls: ['./local-forecast.component.scss']
})
export class LocalForecastComponent implements OnInit {

  autoCompleteSearchResults: any;
  cityInput: string = '';
  cityDetails: any = {};
  defaultCityCode = 210841;
  defaultCityName: string = 'Tel-Aviv';
  todaysForecast: any;

  constructor(private fetchData: DataFetchService,
    private http: HttpClient) { }

  ngOnInit(): void {
    this.cityDetails.name = this.defaultCityName;
    this.getCityForecast(this.defaultCityCode);
    console.log(this.todaysForecast);
  }

  hasSearchResults() {
    return this.autoCompleteSearchResults?.length > 0;
  }

  cityChoice(name, key) {
    this.cityDetails.name = name;
    this.cityDetails.key = key;
  }

  getAutoCompleteResults() {
    this.fetchData.fetchAutoCompleteResults(this.cityInput)
      .subscribe(
        result => { this.autoCompleteSearchResults = result },
        err => console.log(`we have an error: ${err}`)
      )
  }

  getCityForecast(cityCode) {
    this.fetchData.getCityForecast(cityCode)
    .subscribe(
      result => this.todaysForecast = result[0],
      err => console.log(`we have an error: ${err}`)
    );
  }

Child component

HTML

<h1>Today's Weather in {{city.name}}</h1>
<p>Temperature: {{temperature}}°С</p>
<p>Weather: {{conditions}}</p>

TS

import { Component, OnInit, Input } from '@angular/core';
import { DataFetchService } from 'src/app/services/data-fetch.service';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-todays-forecast',
  templateUrl: './todays-forecast.component.html',
  styleUrls: ['./todays-forecast.component.scss']
})
export class TodaysForecastComponent implements OnInit {

  @Input() city: any;
  @Input() todaysForecast: any;

  temperature: any;
  conditions: any;

  constructor( public fetchData: DataFetchService) { }

  ngOnInit(): void {
    console.log('this.todaysForecast >>>', this.todaysForecast);
    this.temperature = this.todaysForecast?.Temperature.Metric.Value;
    this.conditions = this.todaysForecast?.WeatherText;
  }

}

Upvotes: 1

Views: 848

Answers (3)

William Martins
William Martins

Reputation: 442

If you want to fetch data before your component is rendered you can use an Resolver.

https://angular.io/api/router/Resolve

Upvotes: 1

Ga&#235;l J
Ga&#235;l J

Reputation: 15230

There are several ways to do it:

  • conditionnally display the child component in parent template *ngIf="data" where data is the data retrieved from backend
  • make the child component handle null or undefined values in a specific way
  • propagate the Observable in child component (usually more complex and does not fit the intelligent/dumb components principles)

Upvotes: 1

Eliseo
Eliseo

Reputation: 57971

instead of use ngOnInit, use a "setter" in @Input

@Input() todaysForecast(value){
    this.temperature = value.Temperature.Metric.Value;
    this.conditions = value.WeatherText;
    //is you need it you can also use another variable "_todayForecast
    //this._todayForecast=value
}
//and a getter
get todayForecast(){
   return this._todayForecast
}

or implements OnChanges

export class TodaysForecastComponent implements OnChanges {
  @Input() todaysForecast;

  ngOnChanges(changes: SimpleChanges) {
    // changes.prop contains the old and the new value...
  }
}

Upvotes: 2

Related Questions