Search code examples
angulartypescripthttpclient

ctx. is undefined in Angular


I am working on a weather app from OpenWeatherMap API, I blocked my service twice already, because of too many requests. I checked my code many times and I can't find a single place which would cause loop demand from the server, I checked the console and it gave the following error: ERROR TypeError: ctx.amindiDGES is undefined.

I get this error on a couple of lines in my main.component.html:

MainComponent_Template main.component.html:8
getLocation main.component.ts:39
ngOnInit main.component.ts:27

This is what my Service looks like today.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';

import { Observable, throwError } from 'rxjs';
import { catchError, retry } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class TodayService {
  url = 'http://api.openweathermap.org/data/2.5/weather';
  apiKey = '***********************';

  constructor(private http: HttpClient) { }

daitriecoordinatebi(lat, lon) {
  let params = new HttpParams()
    .set('lat', lat)
    .set('lon', lon)
    .set('units', 'metric')
    .set('appid', this.apiKey)

  return this.http.get(this.url, { params });

This is my main.component.ts

import { Component, OnInit } from '@angular/core';
import { TodayService } from './today.service';


@Component({
  selector: 'app-main',
  templateUrl: './main.component.html',
  styleUrls: ['./main.component.scss']
})

export class MainComponent implements OnInit {
  lat;
  lon;
  amindiDGES;
  kvirisdgeToday;
  ikonkaToday;

  
  title = 'Day1';
  today = new Date();

  constructor(private todayService: TodayService) { }

  ngOnInit(): void {

    // lokacia
    this.getLocation();

    // zusti saati
  

    this.today = new Date();
    

  }

  getLocation() {
    if ("geolocation" in navigator) {
      navigator.geolocation.watchPosition((success) => {
        this.lat = success.coords.latitude;
        this.lon = success.coords.longitude;

        this.todayService.daitriecoordinatebi(this.lat, this.lon).subscribe(data => {
          this.amindiDGES = data;
        })
      })
    }
  }
  
}

And this is part of my main.component.html

<table>
  <tbody>
    <tr>
      <td><i class="fas fa-wind"></i></td>
      <td>&nbsp;&nbsp;Wind - {{amindiDGES.wind.speed}}</td>
    </tr>
    <tr>
      <td><i class="far fa-eye"></i></td>
      <td>&nbsp;&nbsp;Visibility - {{amindiDGES.visibility}}</td>
    </tr>
    <tr>
      <td><i class="fas fa-tachometer-alt"></i></td>
      <td>&nbsp;&nbsp;Preassure - {{amindiDGES.main.pressure}}</td>
    </tr>
    <tr>
      <td><i class="fas fa-tint"></i></td>
      <td>&nbsp;&nbsp;Humidity - {{amindiDGES.main.humidity}}</td>
    </tr>
  </tbody>
</table>

The funny thing in all of this is that I get data from the server and I do see results, but somehow the app is blocked after a couple of use due to too many demands, allowed amount is 60 per minute and my app demands 800+ requests per minute, apart from it there is a constant error in console: ERROR TypeError: ctx.amindiDGES is undefined

My guess is that somehow the app may try to display data before it is fetched via server, it gives error in the console and after that, it makes multiple requests over and over again until API is blocked.

I was wondering if you had such a problem with data fetching


Solution

  • Well, the below function has some serious issues:

     getLocation() {
        if ("geolocation" in navigator) {
          navigator.geolocation.watchPosition((success) => {
            this.lat = success.coords.latitude;
            this.lon = success.coords.longitude;
    
            this.todayService.daitriecoordinatebi(this.lat, this.lon).subscribe(data => {
              this.amindiDGES = data;
            })
          })
        }
      }
    

    As the document states :

    The Geolocation method watchPosition() method is used to register a handler function that will be called automatically each time the position of the device changes.

    So your code will subscribe daitriecoordinatebi observable every time the location changed. So if the location is changed 3 times you will have 3 subscriptions. So you will call the API 3 times...

    You have many options to resolve this.

    1. Use toPromise and then await the result
    async  (success) => {
        this.amindiDGES = await 
              this.todayService.daitriecoordinatebi(this.lat, this.lon).toPromise();
    // or you can use
         await lastValueFrom(this.todayService.daitriecoordinatebi(this.lat, this.lon))
    // since toPromise will be deprecated
    })
    
    1. use first operator within pipe to make the subscription destroyed automatically
     this.todayService.daitriecoordinatebi(this.lat, this.lon).pipe(first()).subscribe...
     // or you can use take(1)
    
    1. You can use a Subject and a mergeMap operator to make it more elegant
    positionChanged = new Subject();
      getLocation() {
        if ("geolocation" in navigator) {
          navigator.geolocation.watchPosition((success) => {
            this.lat = success.coords.latitude;
            this.lon = success.coords.longitude;
            this.positionChanged.next();        
          })
        }
        this.positionChanged.pipe(
           mergeMap(()=>this.todayService.daitriecoordinatebi(this.lat, this.lon)))
            .subscribe(data => {
              this.amindiDGES = data;
            })
      }