Home > Back-end >  How to change api data in one component based on click in different component
How to change api data in one component based on click in different component

Time:12-12

Hello i am trying to learn angular and now hit a wall. So basically what i am trying to do is change weather data for city which is in one component after clicking in home component, in the home component there are 4 cities in a grid and whenever user clicks on either one it redirect him to component with the data (temperature, time, humidity etc) and the data changes based on the city he clicked on the landing home page. Currently the date is fixed to specific date and city. I am very confused on how to make this work and how does this data binding works between components. Can someone help please ?

this is my html weather data component html

<div>
    <p-table
      #dt
      [value]="lazyWeatherData"
      dataKey="id"
      [columns]="cols"
      [scrollable]="true"
      [tableStyle]="{ 'min-width': '50rem' }"
      scrollHeight="flex"
      [globalFilterFields]="['time','temperature','humidity','wind', 'pressure', 'direction', 'precipitation', 'rain', 'cloudcover','soilTemperature']"
      [rows]="10"
      [paginator]="true"
      [lazy]="true"
      (onLazyLoad)="loadData($event)"
      [loading]="loading"
    >
    <ng-template pTemplate="caption">
      <div *ngIf="filterSwitch" >
          <button pButton label="Clear"
          
          icon="pi pi-filter-slash"
          (click)="clearFilters(dt)">
        </button>
          <span >
              <i ></i>
              <input
              
              pInputText
              type="text"
              (input)="dt.filterGlobal($event.target.value, 'contains')" />
          </span>
      </div>
  </ng-template>
      <ng-template pTemplate="header" let-columns>
        <tr >
          <th
          style="min-width:250px"
          [pSortableColumn]="col.field"
          *ngFor="let col of columns">
            <fa-icon [icon]="col.icon"></fa-icon>
            {{col.header}}
            <p-sortIcon [field]="col.field"></p-sortIcon>
            <p-columnFilter
            *ngIf="filterSwitch"
            [type]="col.type"
            display="menu"
            [field]="col.field">
          </p-columnFilter>
          </th>
      </ng-template>
      <ng-template pTemplate="body" let-lazy let-columns="columns">
        <tr>
          <td *ngFor="let col of columns">{{ lazy[col.field]   col.value }}</td>
        </tr>
      </ng-template>
    </p-table>
  </div>

this is data table TS

export class WeatherDataComponent implements OnInit, OnDestroy {
  @ViewChild('dt') table: Table;
  showError: string;
  weatherData: WeatherDataItem[] = [];
  private componentDestroyed$: Subject<boolean> = new Subject();
  weatherDataLoading: boolean;
  icons: IconDefinition[] = [
    faClock,
    faTemperatureHigh,
    faPercent,
    faWind,
    faGem,
    faDirections,
    faWater,
    faCloud,
  ];
  cols: WeatherDataCol[];
  loading = false;
  lazyWeatherData: any = [];

  constructor(
    private weatherService: WeatherService,
    public datePipe: DatePipe
  ) {
    this.cols = [
      new WeatherDataCol('time', 'Time', 'text', '', this.icons[0]),
      new WeatherDataCol(
        'temperature',
        'Temperature',
        'text',
        '°C',
        this.icons[1]
      ),
      new WeatherDataCol('humidity', 'Humidity', 'numeric', '%', this.icons[2]),
      new WeatherDataCol('wind', 'Wind Speed', 'text', ' km/h', this.icons[3]),
      new WeatherDataCol(
        'pressure',
        'Air Pressure',
        'text',
        ' hPa',
        this.icons[4]
      ),
      new WeatherDataCol(
        'direction',
        'Wind Direction',
        'numeric',
        '°',
        this.icons[5]
      ),
      new WeatherDataCol(
        'precipitation',
        'Precipitation',
        'numeric',
        'mm',
        this.icons[6]
      ),
      new WeatherDataCol('rain', 'Rain', 'numeric', 'mm', this.icons[6]),
      new WeatherDataCol(
        'cloudcover',
        'Cloudcover',
        'numeric',
        '%',
        this.icons[7]
      ),
      new WeatherDataCol(
        'soilTemperature',
        'Soil Temperature',
        'text',
        '°C',
        this.icons[1]
      ),
    ];
  }

  ngOnInit(): void {
    this.weatherDataLoading = true;
    this.weatherService
      .getWeatherData()
      .pipe(
        finalize(() => (this.weatherDataLoading = false)),
        takeUntil(this.componentDestroyed$)
      )
      .subscribe({
        next: (historicalWeatherData) => {
          const temperatures = historicalWeatherData.hourly.temperature_2m;
          const times = historicalWeatherData.hourly.time.map((time) =>
            this.datePipe.transform(time, 'shortTime')
          );
          const humidities = historicalWeatherData.hourly.relativehumidity_2m;
          const windSpeeds = historicalWeatherData.hourly.windspeed_10m;
          const airPressures = historicalWeatherData.hourly.surface_pressure;
          const windDirections = historicalWeatherData.hourly.winddirection_10m;
          const precipitations = historicalWeatherData.hourly.precipitation;
          const rain = historicalWeatherData.hourly.rain;
          const cloudcover = historicalWeatherData.hourly.cloudcover;
          const soilTemperatures =
            historicalWeatherData.hourly.soil_temperature_0_to_7cm;

          temperatures.forEach((value, i) => {
            this.weatherData.push(
              new WeatherDataItem(
                value,
                times[i],
                humidities[i],
                windSpeeds[i],
                airPressures[i],
                windDirections[i],
                precipitations[i],
                rain[i],
                cloudcover[i],
                soilTemperatures[i]
              )
            );
          });
        },
        error: () =>
          (this.showError =
            'Something went wrong. Please try refreshing the page'),
      });
    this.loading = true;
  }

  loadData(event: LazyLoadEvent) {
    this.loading = true;
    setTimeout(() => {
      if (this.weatherData) {
        this.lazyWeatherData = this.weatherData.slice(
          event.first,
          event.first   event.rows
        );
        this.loading = false;
        console.log(this.lazyWeatherData);
      }
    }, 1000);
  }

  clearFilters(table: Table) {
    table.clear();
  }

  ngOnDestroy(): void {
    this.componentDestroyed$.next(true);
    this.componentDestroyed$.complete();
  }
}

this is home page component with 4 cities in grid and when user click on either one of them it redirects him to data table above and it should change the data for the city clicked

<div >
  <div >
    <a routerLink="/weather-data"><h3>London</h3></a>
    <a routerLink="/weather-data"><h3>New York</h3></a>
    <a routerLink="/weather-data"><h3>Tokyo</h3></a>
    <a routerLink="/weather-data"><h3>Sydney</h3></a>  
</div>

this is ts for home

export class HomeComponent implements OnInit {
  constructor() {}

  ngOnInit(): void {}

}

this is weather service that get data from API

@Injectable({
  providedIn: 'root',
})
export class WeatherService {
  constructor(private http: HttpClient) {}

  getWeatherData(
    lat: string = '51.51',
    lon: string = '-0.13'
  ): Observable<WeatherData> {
    return this.http.get<WeatherData>(
      `https://archive-api.open-meteo.com/v1/era5?latitude=${lat}&longitude=${lon}&start_date=2005-08-25&end_date=2005-08-25&hourly=temperature_2m,relativehumidity_2m,dewpoint_2m,apparent_temperature,surface_pressure,precipitation,rain,cloudcover,windspeed_10m,winddirection_10m,soil_temperature_0_to_7cm`
    );
  }
}

basically i need to change the latitude and longitude in the weather service URL based on the city user clicks on , in the homepage which then redirect him to the weather data component with updated data for the city. how can i achieve this ? currently the lat and lon is set default to London

CodePudding user response:

The question is related to how pass parameter to your weather-data.component.

If the component is in the home.component (not inside a router-outlet) you use @Input

//if I imagine you pass the lat and long separated by an space
@Input() set latLong(value)
{   
    const [lat,long]=value.split(' ')
    this.getData(lat,long)
}
getData(lat:string,long:string)
{
    //the code you put in ngOnInit but using lat and long
    this.weatherDataLoading = true;
    this.weatherService
      .getWeatherData(lat,long)
      .pipe(...
}

And your app can be like

<li><button (click)="latlong='51.507222222222 -0.1275'">London</button></li>
<li><button (click)="latlong='40.416666666667 -3.7025'">Madrid</button></li>
<li><button (click)="latlong='48.856944444444 2.3513888888889'">Paris</button></li>

    <app-weather-data [latLong]="latlog"></app-weather-data>

If you has not a relation parent-child you has two options

  1. Pass the data in the parameters of the router. For this, your router should be like

    {path:weather/:lat/:long,',component:WeatherDataComponent}
    

    And in ngOnInit you subscribe to params like show the docs. See that you subscribe to activatedRoute.queryParams and use switchMap to return the weatherService.getWeatherData(lat,long)

    constructor(private activatedRoute: ActivatedRoute,...){},
    ngOnInit(){
      this.activatedRoute.queryParams.pipe(
        switchMap(params => {
        const lat=params['lat'];
        const long=params['long']
    
        return this.weatherService.getWeatherData(lat,long)
      }).subscribe(
        ...your function..
      });
    }
    }
    
  2. Or pass the data using state like this SO So your links should be like

    <a [routerLink]="['/weather']" [state]="{lat:'51.507222222222',long:'-0.1275'}">London</a>
        <a [routerLink]="['/weather']" [state]="{lat:'40.416666666667',long:'-3.7025'}">Madrid</a>
        <a [routerLink]="['/weather']" [state]="{lat:'48.856944444444',long'2.3513888888889')">Paris</a>
    

    And you use in your component (if not is main)

       this.activatedRoute.paramMap.pipe(
            take(1), //take(1) is for unsubscribe
            map(() => window.history.state),
            switchMap(res=>{
               return this.weatherService.getWeatherData(res.data.lat,res.data.long)
    
            })
        ).subscribe(res => {
           ..your code...
        })
    

    Or, if is main component

      this.router.events.pipe(
            filter(e => e instanceof NavigationStart),
            map(() => this.router.getCurrentNavigation().extras.state)
            switchMap(res=>{
               return this.weatherService.getWeatherData(res.data.lat,res.data.long)
    
            })
        ).subscribe(res => {
           ..your code...
        })
    
    ).subscribe(res=>{
       console.log(res)
    })
    

NOTE: I don't check fullfill the code, it's possible there was some syntax error, but I hope give some clue to get the data

NOTE2: Perhasf it's better that was the service who transform de data received from the URL in an array of WeatherDataTime (using map) instead the component.

BTW you should make "something" with your code. I don't see in your code that you use the "temperatures" array (it's temporal variable) nothing

  • Related