在访问angular2中加载到模板中的范围变量并缓存它之前,如何正确地进行ajax调用?

在访问angular2中加载到模板中的范围变量并缓存它之前,如何正确地进行ajax调用?,angular,Angular,我有以下问题。 我的组件在路由后加载时出现问题: /myroute/:id 问题是我在其中有一个函数,模板使用它来渲染: 构造函数(appState:appState){ } 恩戈尼尼特(){ //这将检索包含多个ID的对象(除非其已缓存) 如果(!this.appState.state.cache | | this.appState.state.id!=) 此.asyncDataWithWebpack(); //此块的目的是仅从路由参数获取id 这个.route.params .subscr

我有以下问题。 我的组件在路由后加载时出现问题:

/myroute/:id
问题是我在其中有一个函数,模板使用它来渲染:

构造函数(appState:appState){
}
恩戈尼尼特(){
//这将检索包含多个ID的对象(除非其已缓存)
如果(!this.appState.state.cache | | this.appState.state.id!=)
此.asyncDataWithWebpack();
//此块的目的是仅从路由参数获取id
这个.route.params
.subscribe((params:params)=>{
this.setSpecificDataRelated(params['id']);
});
}
//此函数提取包含可以匹配“id”的对象的数据。我认为这是对一些数据的休息。
私有asyncDataWithWebpack(){
设置超时(()=>{
System.import(“../../assets/mock data/mock data.json”)
。然后((json)=>{
this.arrayOfDataWithManyIds=json;
});
});
}
setSpecificDataRelatedToId(id){
this.nestedArrayBasedOneId=this.ArrayOfDataWithManyId[id].nestedArray;
this.appState.set(“缓存”,this.arrayOfDataWithManyIds);
this.appState.set(“cacheId”,Id);
}
在我的模板中,我有:


{{entry.name}
我之所以会出错,是因为ArrayOfDataWithManyId不存在,因为它是异步的。处理这种情况的最佳方法是什么

我想确保:

  • 性能很好,它获取数据,然后确保在数据实际返回后可以访问setSpecificDataRelatedToId(id)函数

  • 我不希望每次我转到路由时,它都必须不断请求数据,相反,因为它已经拉入了一次,所以它应该“缓存”并使用缓存的对象,而不必一直返回服务器(除非id更改)

  • 现在我只使用appState全局到应用程序(没有Rxjs或任何商店),但我正在寻找处理这个问题的最佳方法。这是一个只需要可观察对象就能优雅处理的问题吗


    我正在寻找一个很好的解决方案/最佳实践来解决这个问题。我担心会产生错误的代码,因为我想在我的应用程序中的多个组件中传播此模型。

    如何,在数据返回后嵌套您的方法

    ngOnInit() {
        let that = this;
        // this retrieves an object that contains multiple ids
        this.asyncDataWithWebpack(() => {
            // Purpose of this block is to just get the id from the route parameters
            that.route.params
                .subscribe((params: Params) => {
                    that.setSpecificDataRelated(id);
                });
        });
    
    }
    
    // this function pulls in the data that contains an object that 'id' can be matched to. I consider this a rest call to some data.
    private asyncDataWithWebpack(execAfter:any) {
        if (this.arrayOfDataWithManyIds && ... ){ // you may add your cache logic here 
            execAfter();
        } else {
            setTimeout(() => {
                System.import('../../assets/mock-data/mock-data.json')
                    .then((json) => {
                        this.arrayOfDataWithManyIds = json;
                        execAfter();
                    });
            });
        }
    }
    
    setSpecificDataRelatedToId(id) {
        this.nestedArrayBasedOnOneId = this.arrayOfDataWithManyIds[id].nestedArray;
    }
    
    现在我只使用appState全局到应用程序(没有Rxjs或任何商店),但我正在寻找处理这个问题的最佳方法。这是一个只需要可观察对象就能优雅处理的问题吗

    1) 在这里,您不需要任何商店来处理这个问题(比如使用Redux方法的ngrx)来实现您想要的。(即使这样也会玩得很好)

    2) 是的,可观测数据将帮助以良好的方式处理此问题,但一如既往,您不必使用它们。(你绝对应该使用可观测的,这真的很棒)

    3) 这里的问题更多的是一个架构问题,我将一步一步地指导您获得一个优化的、设计良好的解决方案,而无需缓存任何内容

    我为匹配(据我所知)您的问题所做的示例如下:

    • 显示人员列表
    • 单击某人时,获取他的详细信息
      (小好处:检索数据时显示加载指示器)

    • 获取详细信息后,显示它们
      (请注意,用户有一个狗数组来模拟嵌套数组)

    如果您想尝试现场演示,这里有一个提示:


    现在让我们回到代码上来,好吗? (我将逐一解释为什么以及如何构建该应用程序)

    首先,让我们为我们的反应式应用做好准备!我们必须添加一些将要使用的
    RxJs
    操作符。让我们将它们添加到
    src/main.ts

    import 'rxjs/add/operator/map';
    import 'rxjs/add/operator/switchMap';
    import 'rxjs/add/operator/delay';
    import 'rxjs/add/operator/do';
    import 'rxjs/add/observable/of';
    
    import {Injectable} from '@angular/core'
    import {Observable} from 'rxjs/Observable'
    
    @Injectable()
    export class PeopleService {
      // raw data to simulate your database on backend
      private people = {
        persId0: {
          id: 'persId0',
          name: 'Person 0', 
          weightKg: 60.8,
          heightCm: 183,
          dogs: [
            {id: 'dogId 0', name: 'Dog 0'},
            {id: 'dogId 1', name: 'Dog 1'}
          ]
        },
        persId1: {
          id: 'persId1',
          name: 'Person 1', 
          weightKg: 65.3,
          heightCm: 165,
          dogs: [
            {id: 'dogId 2', name: 'Dog 2'},
            {id: 'dogId 3', name: 'Dog 3'}
          ]
        },
        persId2: {
          id: 'persId2',
          name: 'Person 2', 
          weightKg: 77.6,
          heightCm: 192,
          dogs: [
            {id: 'dogId 2', name: 'Dog 2'},
            {id: 'dogId 2', name: 'Dog 2'}
          ]
        }
      };
    
      constructor() { }
    
      getPersonDetails(personId: string) {
        console.warn(`Calling the service to fetch the details a the person with ID : ${personId}`);
    
        // here, you may want to read your JSON file but avoid using setTimeout in such a case
        // and prefer Observable.of to wrap your data in a stream and use the delay operator to 
        // simulate network latency
        return Observable
          .of(this.people[personId])
          .delay(10000);
      }
    }
    
    import {Component, OnInit} from '@angular/core'
    import {ActivatedRoute} from '@angular/router'
    import {Observable} from 'rxjs/Observable'
    import {PeopleService} from 'src/people.service'
    
    @Component({
      selector: 'app-person',
      template: `
        <hr>
    
        <p *ngIf="isLoading">Loading ...</p>
    
        <!-- use the new syntax with "as" to save the observable result and re-use it accross the template -->
        <div *ngIf="personWithDetails$ | async as personWithDetails">
          <p>
            <b>{{ personWithDetails.name }}</b> is <b>{{ personWithDetails.heightCm }}</b>cm tall and weight <b>{{ personWithDetails.weightKg }}kg</b>
          </p>
    
          <div>
            <p>He/she also has {{ personWithDetails.dogs.length }} dogs :</p>
            <ul>
              <li *ngFor="let dog of personWithDetails.dogs">{{ dog.name }}</li>
            </ul>
          </div>
        </div>
      `
    })
    export class PersonComponent implements OnInit {
      // will be fetched from the service
      public personWithDetails$: Observable<any>;
      public isLoading: boolean;
    
      constructor(private route: ActivatedRoute, private peopleService: PeopleService) { }
    
      ngOnInit() {
        this.personWithDetails$ = this
          .route
          // every time the route params changes ...
          .params
          // pick only the id ...
          .map(params => params['id'])
          // and set a variable isLoading to true so we can display a spinner or some text into the view
          .do(x => this.isLoading = true)
          // use switchMap to auto-magically subscribe/unsubscribe from the service call
          .switchMap(personId => this
            .peopleService
            // this is the last function returning something and thus, it'll be that return saved in personWithDetails$
            .getPersonDetails(personId)
            // simply set the isLoading variable to false so we can hide/remove our spinner or text
            .do(x => this.isLoading = false));
      }
    }
    
    如果您还不熟悉RxJs,我建议您:
    1) 阅读
    2) 玩
    3) 使用大理石图查看官方文档:

    然后,在简单的
    应用程序
    组件中,定义一些人:

      public people = [
        {id: 'persId0', name: 'Person 0'},
        {id: 'persId1', name: 'Person 1'},
        {id: 'persId2', name: 'Person 2'}
      ];
    
    在模板中,通过指向
    /people/:id
    的链接向他们显示:

      <div *ngFor="let person of people">
        <a [routerLink]="['/people', person.id]">{{ person.name }}</a>
      </div>
    
    对!!我们想在到达该路线时显示一个
    PersonComponent
    ,但我们还没有定义它,我们将在几秒钟后返回。但是,让我们先定义检索一个人的服务:

    src/people.service.ts

    import 'rxjs/add/operator/map';
    import 'rxjs/add/operator/switchMap';
    import 'rxjs/add/operator/delay';
    import 'rxjs/add/operator/do';
    import 'rxjs/add/observable/of';
    
    import {Injectable} from '@angular/core'
    import {Observable} from 'rxjs/Observable'
    
    @Injectable()
    export class PeopleService {
      // raw data to simulate your database on backend
      private people = {
        persId0: {
          id: 'persId0',
          name: 'Person 0', 
          weightKg: 60.8,
          heightCm: 183,
          dogs: [
            {id: 'dogId 0', name: 'Dog 0'},
            {id: 'dogId 1', name: 'Dog 1'}
          ]
        },
        persId1: {
          id: 'persId1',
          name: 'Person 1', 
          weightKg: 65.3,
          heightCm: 165,
          dogs: [
            {id: 'dogId 2', name: 'Dog 2'},
            {id: 'dogId 3', name: 'Dog 3'}
          ]
        },
        persId2: {
          id: 'persId2',
          name: 'Person 2', 
          weightKg: 77.6,
          heightCm: 192,
          dogs: [
            {id: 'dogId 2', name: 'Dog 2'},
            {id: 'dogId 2', name: 'Dog 2'}
          ]
        }
      };
    
      constructor() { }
    
      getPersonDetails(personId: string) {
        console.warn(`Calling the service to fetch the details a the person with ID : ${personId}`);
    
        // here, you may want to read your JSON file but avoid using setTimeout in such a case
        // and prefer Observable.of to wrap your data in a stream and use the delay operator to 
        // simulate network latency
        return Observable
          .of(this.people[personId])
          .delay(10000);
      }
    }
    
    import {Component, OnInit} from '@angular/core'
    import {ActivatedRoute} from '@angular/router'
    import {Observable} from 'rxjs/Observable'
    import {PeopleService} from 'src/people.service'
    
    @Component({
      selector: 'app-person',
      template: `
        <hr>
    
        <p *ngIf="isLoading">Loading ...</p>
    
        <!-- use the new syntax with "as" to save the observable result and re-use it accross the template -->
        <div *ngIf="personWithDetails$ | async as personWithDetails">
          <p>
            <b>{{ personWithDetails.name }}</b> is <b>{{ personWithDetails.heightCm }}</b>cm tall and weight <b>{{ personWithDetails.weightKg }}kg</b>
          </p>
    
          <div>
            <p>He/she also has {{ personWithDetails.dogs.length }} dogs :</p>
            <ul>
              <li *ngFor="let dog of personWithDetails.dogs">{{ dog.name }}</li>
            </ul>
          </div>
        </div>
      `
    })
    export class PersonComponent implements OnInit {
      // will be fetched from the service
      public personWithDetails$: Observable<any>;
      public isLoading: boolean;
    
      constructor(private route: ActivatedRoute, private peopleService: PeopleService) { }
    
      ngOnInit() {
        this.personWithDetails$ = this
          .route
          // every time the route params changes ...
          .params
          // pick only the id ...
          .map(params => params['id'])
          // and set a variable isLoading to true so we can display a spinner or some text into the view
          .do(x => this.isLoading = true)
          // use switchMap to auto-magically subscribe/unsubscribe from the service call
          .switchMap(personId => this
            .peopleService
            // this is the last function returning something and thus, it'll be that return saved in personWithDetails$
            .getPersonDetails(personId)
            // simply set the isLoading variable to false so we can hide/remove our spinner or text
            .do(x => this.isLoading = false));
      }
    }
    
    如您所见,我通过使用(…).delay()的
    Observable.of模拟了网络调用。在这里,您可以在生产中用一个真正的HTTP请求替换它。但这是另一个话题

    不要忘记将服务声明到
    AppModule
    中,并使用:
    providers:[PeopleService]

    现在我们可以获取个人详细信息了,让我们创建一个组件来显示它们:
    (代码中有足够的注释,因此我不必在此处添加任何内容)

    src/person.component.ts

    import 'rxjs/add/operator/map';
    import 'rxjs/add/operator/switchMap';
    import 'rxjs/add/operator/delay';
    import 'rxjs/add/operator/do';
    import 'rxjs/add/observable/of';
    
    import {Injectable} from '@angular/core'
    import {Observable} from 'rxjs/Observable'
    
    @Injectable()
    export class PeopleService {
      // raw data to simulate your database on backend
      private people = {
        persId0: {
          id: 'persId0',
          name: 'Person 0', 
          weightKg: 60.8,
          heightCm: 183,
          dogs: [
            {id: 'dogId 0', name: 'Dog 0'},
            {id: 'dogId 1', name: 'Dog 1'}
          ]
        },
        persId1: {
          id: 'persId1',
          name: 'Person 1', 
          weightKg: 65.3,
          heightCm: 165,
          dogs: [
            {id: 'dogId 2', name: 'Dog 2'},
            {id: 'dogId 3', name: 'Dog 3'}
          ]
        },
        persId2: {
          id: 'persId2',
          name: 'Person 2', 
          weightKg: 77.6,
          heightCm: 192,
          dogs: [
            {id: 'dogId 2', name: 'Dog 2'},
            {id: 'dogId 2', name: 'Dog 2'}
          ]
        }
      };
    
      constructor() { }
    
      getPersonDetails(personId: string) {
        console.warn(`Calling the service to fetch the details a the person with ID : ${personId}`);
    
        // here, you may want to read your JSON file but avoid using setTimeout in such a case
        // and prefer Observable.of to wrap your data in a stream and use the delay operator to 
        // simulate network latency
        return Observable
          .of(this.people[personId])
          .delay(10000);
      }
    }
    
    import {Component, OnInit} from '@angular/core'
    import {ActivatedRoute} from '@angular/router'
    import {Observable} from 'rxjs/Observable'
    import {PeopleService} from 'src/people.service'
    
    @Component({
      selector: 'app-person',
      template: `
        <hr>
    
        <p *ngIf="isLoading">Loading ...</p>
    
        <!-- use the new syntax with "as" to save the observable result and re-use it accross the template -->
        <div *ngIf="personWithDetails$ | async as personWithDetails">
          <p>
            <b>{{ personWithDetails.name }}</b> is <b>{{ personWithDetails.heightCm }}</b>cm tall and weight <b>{{ personWithDetails.weightKg }}kg</b>
          </p>
    
          <div>
            <p>He/she also has {{ personWithDetails.dogs.length }} dogs :</p>
            <ul>
              <li *ngFor="let dog of personWithDetails.dogs">{{ dog.name }}</li>
            </ul>
          </div>
        </div>
      `
    })
    export class PersonComponent implements OnInit {
      // will be fetched from the service
      public personWithDetails$: Observable<any>;
      public isLoading: boolean;
    
      constructor(private route: ActivatedRoute, private peopleService: PeopleService) { }
    
      ngOnInit() {
        this.personWithDetails$ = this
          .route
          // every time the route params changes ...
          .params
          // pick only the id ...
          .map(params => params['id'])
          // and set a variable isLoading to true so we can display a spinner or some text into the view
          .do(x => this.isLoading = true)
          // use switchMap to auto-magically subscribe/unsubscribe from the service call
          .switchMap(personId => this
            .peopleService
            // this is the last function returning something and thus, it'll be that return saved in personWithDetails$
            .getPersonDetails(personId)
            // simply set the isLoading variable to false so we can hide/remove our spinner or text
            .do(x => this.isLoading = false));
      }
    }
    
    从'@angular/core'导入{Component,OnInit}
    从“@angular/router”导入{ActivatedRoute}
    从'rxjs/Observable'导入{Observable}
    从'src/people.service'导入{PeopleService}
    @组成部分({
    选择器:“应用程序人员”,
    模板:`
    
    正在加载

    {{personWithDetails.name}是{{personWithDetails.heightCm}厘米高,体重{{personWithDetails.weightKg}公斤

    他/她还有{{personWithDetails.dogs.length}条狗:

      {{dog.name}
    ` }) 导出类PersonComponent实现OnInit{ //将从服务中提取 公众人物详细信息$:可见; 公共isLoading:布尔值; 构造函数(专用路由:已激活)