在访问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不存在,因为它是异步的。处理这种情况的最佳方法是什么
我想确保:
我正在寻找一个很好的解决方案/最佳实践来解决这个问题。我担心会产生错误的代码,因为我想在我的应用程序中的多个组件中传播此模型。如何,在数据返回后嵌套您的方法
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:布尔值;
构造函数(专用路由:已激活)