【问题标题】:problem with nested observables and loading content in angular嵌套可观察对象和以角度加载内容的问题
【发布时间】:2020-08-11 06:51:24
【问题描述】:

我开始使用天气 api 制作我的第一个 angular 应用程序,在第一个加载视图中,我想通过订阅 observables 来发出 3 个请求,所有三种类型的 get 都取决于以下内容:

1 - 获取所有状态的列表 2 - 获取每个州的部门列表 3 - 从每个随机状态获取一个部门的预测

我要面对的问题是,在发出不同的连接请求时,我不想在所有加载完成之前加载数据表,但这让我非常困难,因为我试图把代码开头的加载变量和 for 末尾的另一个加载变量存在于第 2 点和第 3 点之间,但它不能按预期工作。

我的代码如下:

general.component.ts:

import { Component, OnInit } from '@angular/core';
import { eltiempoDataService } from 'src/app/services/eltiempo-data.service';
import { weatherGeneralModel } from 'src/app/models/weatherGeneral.model';


@Component({
  selector: 'app-general',
  templateUrl: './general.component.html',
  styleUrls: ['./general.component.css']
})
export class GeneralComponent implements OnInit {

  data_mun: weatherGeneralModel[] = [];
  loading_data: boolean;

  constructor(private eltiempoDataService: eltiempoDataService) {
    this.getRandomMunicipio()
    console.log('finish2')
  }

  ngOnInit() {
  }

  getRandomMunicipio() {
    let provs
    let prov_random: {}
    this.getProvincias().subscribe((data: any[]) => {
      this.loading_data = true;
      provs = data['provincias']
      this.processData(provs)
    })
  }

  processData(provs: any[]){
    for (let prop in provs) {
      this.getMunicipios(provs[prop]['CODPROV']).subscribe((data_municipios) => {
        let object_data_general: weatherGeneralModel = new weatherGeneralModel()
        let local_muni: any[] = data_municipios['municipios']
        var random = Math.floor(Math.random() * local_muni.length);
        let cod_ine_mun = local_muni[random]['CODIGOINE'].toString().substring(0, 5);
        this.getDataMunicipio(provs[prop]['CODPROV'], cod_ine_mun).subscribe((data_predict) => {
          object_data_general.comunidad = provs[prop]['COMUNIDAD_CIUDAD_AUTONOMA']
          object_data_general.provincia = provs[prop]['NOMBRE_PROVINCIA']
          object_data_general.municipio = data_predict['municipio']['NOMBRE']
          object_data_general.pronostico = data_predict['temperaturas']['max'] + '/' + data_predict['temperaturas']['min']
          object_data_general.cod_prov = provs[prop]['CODPROV']
          this.data_mun.push(object_data_general)
        })
      })
    }
    this.loading_data = false;
  }

  getProvincias() {
    return this.eltiempoDataService.getProvincias()
  }

  getMunicipios(cod_prov: string) {
    return this.eltiempoDataService.getMunicipios(cod_prov)
  }

  getDataMunicipio(cod_prov: string, id_muni: string) {
    return this.eltiempoDataService.getDataMunicipio(cod_prov, id_muni)
  }
}

eltiempo-dataService.service.ts:

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

@Injectable({
  providedIn: 'root'
})
export class eltiempoDataService {

  constructor(private http: HttpClient) {
    console.log('servicio ElTiempo.net listo')
  }

  getProvincias(){
    return this.http.get('https://www.el-tiempo.net/api/json/v2/provincias')
  }

  getMunicipios(cod_prov:string){
    return this.http.get(`https://www.el-tiempo.net/api/json/v2/provincias/${cod_prov}/municipios`)
  }

  getDataMunicipio(cod_prov:string, id_muni:string){
    return this.http.get(`https://www.el-tiempo.net/api/json/v2/provincias/${cod_prov}/municipios/${id_muni}`)
  }
}

general.component.html:

 
<div class="contianer mt-5 m-sm-5">
  <div class="table-responsive">
    <table class="table table-striped table-dark" *ngIf="!loading_data">
      <thead>
        <tr>
          <th scope="col">#</th>
          <th scope="col">Comunidad</th>
          <th scope="col">Provincia</th>
          <th scope="col">Muicipio</th>
          <th scope="col">Prediccion</th>
        </tr>
      </thead>
      <tbody>
        <tr *ngFor="let data of data_mun">
          <th scope="row">{{data.CODPROV}}</th>
          <td>{{data.comunidad}}</td>
          <td>{{data.provincia}}</td>
          <td>{{data.municipio}}</td>
          <td>{{data.pronostico}}</td>
        </tr>
      </tbody>
    </table>
  </div>
</div>

我认为我没有遵循最佳做法,如果有任何帮助、建议和指导,我将不胜感激。

谢谢大家

【问题讨论】:

标签: javascript angular typescript get observable


【解决方案1】:

这是 StackBlitz 链接: https://stackblitz.com/edit/nested-api-calls-forkjoin?file=src/app/app.component.ts

首先我建议为您的所有端点创建接口,因为它是 typescript 的强大功能,您将从 IDE 获得很多帮助,因为它知道您将获得什么样的数据。

我导出了一些端点接口,看看它是如何工作的:

export interface Municipios {
  breadcrumb: {
    name: string;
    title: string;
    url: string;
  }[];
  codprov: string;
  keywords: string;
  metadescripcion: string;
  municipios: Municipio[];
  provincia: string;
  title: string;
}

在你的服务中,它会是这样的。

  getMunicipios(cod_prov: string): Observable<Municipios> {
    return this.http.get<Municipios>(
      `https://www.el-tiempo.net/api/json/v2/provincias/${cod_prov}/municipios`
    );
  }

第二:服务、接口和类命名约定是PascalCase 所以eltiempoDataService 将是EltiempoDataService

第三:由于您需要来自第一个数组的数据,这是您的第一个 API 请求的结果,因此您可以使用 forkJoin 简单地将所有请求一起发送,当它们都准备好时,您可以使用答案。 forkJoin:https://www.learnrxjs.io/learn-rxjs/operators/combination/forkjoin

所以在这里您可以创建一个数组,其中包含您需要的下一个数据的省和可观察的数据。

    this.loading_data = true;
    const provinciasGetMunicipiosObservables: {
      province: Province;
      getMunicipios: Observable<Municipios>;
    }[] = [];

    const getDataMunicipioObservables: { // This is for second wave of API Calls
      province: Province;
      municipios: Municipios;
      getDataMunicipio: Observable<DataMunicipio>;
    }[] = [];

    provincias.forEach(province => {
      /***************************
       **** Creating Array of province and  getMunicipios observable
       ***************************/
      provinciasGetMunicipiosObservables.push({
        province,
        getMunicipios: this.getMunicipios(province.CODPROV)
      });
    });

当你的数组准备好后,你可以将它传递给 forkJoin,当所有数组的结果都准备好时,它会给出答案。

注意:因为我没有创建一个绝对的 observable 数组,所以我只需要 map Observable 所以它返回一个 Observable 数组

observable 中的管道用于映射特定 Observable 的答案,当它可以访问省份时,我将其映射到答案中。

    forkJoin(
      provinciasGetMunicipiosObservables.map(x => {
        return x.getMunicipios.pipe(
          map(y => {
            return {
              province: x.province,
              getMunicipiosAnswer: y
            };
          })
        );
      })
    )
      .pipe(first())
      .subscribe(municipiosArray => {
// Time to send Second Wave of API requests

}

您可以像以前一样重复该过程。

      .subscribe(municipiosArray => {
        municipiosArray.forEach(municipios => {
          /***************************
           **** Part two of data when we got getMunicipios
           ***************************/
          let local_muni: any[] = municipios.getMunicipiosAnswer.municipios;
          var random = Math.floor(Math.random() * local_muni.length);
          let cod_ine_mun = local_muni[random]["CODIGOINE"]
            .toString()
            .substring(0, 5);
          /***************************
           **** Creating second array for getting getDataMunicipio
           ***************************/
          getDataMunicipioObservables.push({
            municipios: municipios.getMunicipiosAnswer,
            province: municipios.province,
            getDataMunicipio: this.getDataMunicipio(
              municipios.province.CODPROV,
              cod_ine_mun
            )
          });
        });
          /***************************
           **** Send Requests with forkJoin
           ***************************/
        forkJoin(
          getDataMunicipioObservables.map(x => {
            return x.getDataMunicipio.pipe(
              map(y => {
                return {
                  province: x.province,
                  municipios: x.municipios,
                  getDataMunicipioAnswer: y
                };
              })
            );
          })
        ).subscribe(arrayOfFinalData => {
          // temproray array
          const finalData: WeatherGeneral[] = [];
          arrayOfFinalData.forEach(weatherData => {
            const max = weatherData.getDataMunicipioAnswer.temperaturas.max;
            const min = weatherData.getDataMunicipioAnswer.temperaturas.min;
            const pronostico = `${max}/${min}`;
            const weatherGeneral = new WeatherGeneral({
              cod_prov: weatherData.province.CODPROV,
              comunidad: weatherData.province.COMUNIDAD_CIUDAD_AUTONOMA,
              municipio: weatherData.getDataMunicipioAnswer.municipio.NOMBRE,
              pronostico,
              provincia: weatherData.province.NOMBRE_PROVINCIA
            });
            finalData.push(weatherGeneral);
          });
           this.loading_data = false;
          this.data_mun = [...finalData];
        });
      });

如果有不清楚的地方,请随时询问。

【讨论】:

  • 非常感谢您的回复和参与测试。我对 Angular 编程还是很陌生,并且忽略了有关命名和格式的样式指南,也非常感谢您的这些评论。我会阅读并理解您的答案并实施它,然后我会告诉您结果。再次感谢。
  • 嗨!最后我设法实施了这个解决方案!我必须对代码进行一些更改,因为在您的示例中它仅提供了两个 wave 请求。这意味着不仅仅是复制粘贴,而是要理解。我已经实现了三个连接的forkjoin,开始获取省份列表,并且一切正常!也许这不是了解 forkjoins 如何工作的最佳第一个示例,但这是一个很好的解释,我明白了!谢谢你!
  • 太棒了。 forkJoin 唯一的问题可能是如果其中一个请求抛出错误,它将破坏整个请求,所以我建议使用重试运算符或其他方式来确保它完美运行。祝你好运
  • 明白了!我将阅读重试以发现可能的错误或问题!谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-11-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多