Angular2 模板试图在 http.get 返回值之前呈现值
Angular2 template trying to render value before value returned from http.get
我有一个angular2项目如下:
package.json:
{
"name": "self-assessment",
"version": "1.0.0",
"scripts": {
"postinstall": "npm run typings install",
"lite": "lite-server",
"gulp": "gulp",
"start": "concurrent \"npm run gulp\" \"npm run lite\" ",
"typings" : "typings"
},
"license": "ISC",
"dependencies": {
"angular2": "2.0.0-beta.3",
"systemjs": "0.19.6",
"es6-promise": "^3.0.2",
"es6-shim": "^0.33.3",
"reflect-metadata": "0.1.2",
"rxjs": "5.0.0-beta.0",
"zone.js": "0.5.11"
},
"devDependencies": {
"autoprefixer": "^6.2.1",
"cssnano": "^3.4.0",
"concurrently": "^1.0.0",
"gulp": "^3.9.0",
"gulp-ext-replace": "^0.2.0",
"gulp-imagemin": "^2.4.0",
"gulp-postcss": "^6.0.1",
"gulp-sourcemaps": "^1.6.0",
"gulp-typescript": "^2.10.0",
"gulp-uglify": "^1.5.1",
"lite-server": "^2.0.1",
"postcss": "^5.0.13",
"postcss-scss": "^0.1.3",
"precss": "^1.3.0",
"typings":"^0.6.8",
"tsd": "^0.6.5-beta"
}
}
app.component.ts:
import {Component} from 'angular2/core';
import {AssessmentListComponent} from "./assessment-list.component";
import {HomeComponent} from "./home.component";
import {ApiService} from "../services/api.service";
import {RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS} from 'angular2/router';
import {AssessmentComponent} from "./assessment.component";
@Component({
selector: 'my-app',
templateUrl: 'app/components/app.component.html',
directives: [
ROUTER_DIRECTIVES,
],
providers: [
ApiService,
ROUTER_PROVIDERS,
]
})
@RouteConfig([
{path: '/home', name: 'Home', component: HomeComponent, useAsDefault: true},
{path: '/assessments', name: 'Assessments', component: AssessmentListComponent},
{path: '/assessment/:id', name: 'Assessment', component: AssessmentComponent},
])
export class AppComponent {
title = "Welcome to Self-Assessment"
}
assessment.component.ts:
import {Component, OnInit} from 'angular2/core';
import {AssessmentModel} from '../models/assessment.model';
import {RouteParams} from 'angular2/router';
import {ApiService} from '../services/api.service';
@Component({
selector: 'assessment-component',
templateUrl: 'app/components/assessment.component.html',
})
export class AssessmentComponent implements OnInit{
assessment: AssessmentModel;
errorMessage: any;
constructor(
private _apiService: ApiService,
private _routeParams: RouteParams
) {}
ngOnInit():any {
let id = +this._routeParams.get('id');
this.getAssessment(id);
}
private getAssessment(id: number) {
this._apiService.getAssessment(id)
.subscribe(
assessment => this.assessment = assessment,
error => this.errorMessage = <any>error
);
}
goBack() {
console.log(this.assessment);
}
}
api.service.ts:
import {Injectable} from 'angular2/core';
import {Http, Response} from 'angular2/http';
import {Observable} from 'rxjs/Observable';
@Injectable()
export class ApiService {
constructor(private _http: Http) {}
private _baseApiUrl = 'http://localhost:8000/';
getAssessments() {
return this.getObjectList('assessments', 'json');
}
getAssessment(id: number) {
return this.getObject('assessments', id, 'json');
}
getObjectList(listName:string, listExtension: string) {
var url = this._baseApiUrl + listName + '.' + listExtension;
return this._http.get(url)
.map(response => response.json())
.catch(this.handleError);
}
getObject(objectName:string, objectId:number, objectExtension: string) {
var url = this._baseApiUrl + objectName + '/' + objectId + '.' + objectExtension;
return this._http.get(url)
.map(response => response.json())
.catch(this.handleError);
}
private handleError(error: Response) {
console.error(error);
return Observable.throw(error.json().error || 'Server error');
}
}
最后
assessment.component.html:
<h3>{{ assessment.id }}</h3>
<button (click)="goBack()">Go Back 2</button>
当使用 url: http://localhost:3000/assessment/4 调用页面时,它对后端进行适当的调用 两次 并在控制台:
Uncaught EXCEPTION: TypeError: Cannot read property 'id' of undefined in [{{ assessment.id }} in AssessmentComponent@0:4]
ORIGINAL EXCEPTION: TypeError: Cannot read property 'id' of undefined
ORIGINAL STACKTRACE:
TypeError: Cannot read property 'id' of undefined
at AbstractChangeDetector.ChangeDetector_AssessmentComponent_0.detectChangesInRecordsInternal (viewFactory_AssessmentComponent:30:28)
at AbstractChangeDetector.detectChangesInRecords (http://localhost:3000/node_modules/angular2/bundles/angular2.dev.js:8116:14)
at AbstractChangeDetector.runDetectChanges (http://localhost:3000/node_modules/angular2/bundles/angular2.dev.js:8099:12)
at AbstractChangeDetector._detectChangesInViewChildren (http://localhost:3000/node_modules/angular2/bundles/angular2.dev.js:8184:14)
at AbstractChangeDetector.runDetectChanges (http://localhost:3000/node_modules/angular2/bundles/angular2.dev.js:8103:12)
at AbstractChangeDetector._detectChangesContentChildren (http://localhost:3000/node_modules/angular2/bundles/angular2.dev.js:8178:14)
at AbstractChangeDetector.runDetectChanges (http://localhost:3000/node_modules/angular2/bundles/angular2.dev.js:8100:12)
at AbstractChangeDetector._detectChangesInViewChildren (http://localhost:3000/node_modules/angular2/bundles/angular2.dev.js:8184:14)
at AbstractChangeDetector.runDetectChanges (http://localhost:3000/node_modules/angular2/bundles/angular2.dev.js:8103:12)
at AbstractChangeDetector.detectChanges (http://localhost:3000/node_modules/angular2/bundles/angular2.dev.js:8088:12)
ERROR CONTEXT:
[object Object]ExceptionHandler.call @ angular2.dev.js:1206(anonymous function) @ angular2.dev.js:12591NgZone._notifyOnError @ angular2.dev.js:13635collection_1.StringMapWrapper.merge.onError @ angular2.dev.js:13539Zone.run @ angular2-polyfills.js:1247NgZone._notifyOnTurnDone @ angular2.dev.js:13450(anonymous function) @ angular2.dev.js:13565zoneBoundFn @ angular2-polyfills.js:1220lib$es6$promise$asap$$flush @ angular2-polyfills.js:262
所以它看起来好像 {{ assessment.id }} 试图在对象从后端返回之前呈现。如果我然后单击 "Go Back 2" 按钮,它将控制台适当地记录对象:
{"id":4,"title":"Assessment Title here!","description":"Here is a description","questions":["http://localhost:8000/questions/1.json","http://localhost:8000/questions/2.json","http://localhost:8000/questions/3.json","http://localhost:8000/questions/4.json","http://localhost:8000/questions/5.json","http://localhost:8000/questions/6.json","http://localhost:8000/questions/7.json","http://localhost:8000/questions/8.json","http://localhost:8000/questions/9.json","http://localhost:8000/questions/10.json","http://localhost:8000/questions/11.json","http://localhost:8000/questions/12.json","http://localhost:8000/questions/13.json"],"responses":[]}
"Interestingly" 我没有在应用程序的其他地方看到问题(即可以很好地加载和显示评估列表等)。此外,我在昨晚作为学习项目使用的 Ionic2 应用程序中遇到了完全相同的问题。
用谷歌搜索了我的屁股,希望有人知道这个谜语的答案。
谢谢!
您有两个选择:
- 使用 safe navigation operator(以前称为 Elvis 运算符)
{{ assessment?.id }}
,它可以防止空值和未定义值
- 使用 NgIf,
<h3 *ngIf="assessment">{{assessment.id}}</h3>
,如果您不想在 DOM 中添加元素,直到数据为 available/populated ,这更合适
您可能不会遇到列表问题,因为 NgFor 会为您处理未定义、空或空数组的情况。然后当数据进来时,NgFor 被重新评估。
我有一个angular2项目如下:
package.json:
{
"name": "self-assessment",
"version": "1.0.0",
"scripts": {
"postinstall": "npm run typings install",
"lite": "lite-server",
"gulp": "gulp",
"start": "concurrent \"npm run gulp\" \"npm run lite\" ",
"typings" : "typings"
},
"license": "ISC",
"dependencies": {
"angular2": "2.0.0-beta.3",
"systemjs": "0.19.6",
"es6-promise": "^3.0.2",
"es6-shim": "^0.33.3",
"reflect-metadata": "0.1.2",
"rxjs": "5.0.0-beta.0",
"zone.js": "0.5.11"
},
"devDependencies": {
"autoprefixer": "^6.2.1",
"cssnano": "^3.4.0",
"concurrently": "^1.0.0",
"gulp": "^3.9.0",
"gulp-ext-replace": "^0.2.0",
"gulp-imagemin": "^2.4.0",
"gulp-postcss": "^6.0.1",
"gulp-sourcemaps": "^1.6.0",
"gulp-typescript": "^2.10.0",
"gulp-uglify": "^1.5.1",
"lite-server": "^2.0.1",
"postcss": "^5.0.13",
"postcss-scss": "^0.1.3",
"precss": "^1.3.0",
"typings":"^0.6.8",
"tsd": "^0.6.5-beta"
}
}
app.component.ts:
import {Component} from 'angular2/core';
import {AssessmentListComponent} from "./assessment-list.component";
import {HomeComponent} from "./home.component";
import {ApiService} from "../services/api.service";
import {RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS} from 'angular2/router';
import {AssessmentComponent} from "./assessment.component";
@Component({
selector: 'my-app',
templateUrl: 'app/components/app.component.html',
directives: [
ROUTER_DIRECTIVES,
],
providers: [
ApiService,
ROUTER_PROVIDERS,
]
})
@RouteConfig([
{path: '/home', name: 'Home', component: HomeComponent, useAsDefault: true},
{path: '/assessments', name: 'Assessments', component: AssessmentListComponent},
{path: '/assessment/:id', name: 'Assessment', component: AssessmentComponent},
])
export class AppComponent {
title = "Welcome to Self-Assessment"
}
assessment.component.ts:
import {Component, OnInit} from 'angular2/core';
import {AssessmentModel} from '../models/assessment.model';
import {RouteParams} from 'angular2/router';
import {ApiService} from '../services/api.service';
@Component({
selector: 'assessment-component',
templateUrl: 'app/components/assessment.component.html',
})
export class AssessmentComponent implements OnInit{
assessment: AssessmentModel;
errorMessage: any;
constructor(
private _apiService: ApiService,
private _routeParams: RouteParams
) {}
ngOnInit():any {
let id = +this._routeParams.get('id');
this.getAssessment(id);
}
private getAssessment(id: number) {
this._apiService.getAssessment(id)
.subscribe(
assessment => this.assessment = assessment,
error => this.errorMessage = <any>error
);
}
goBack() {
console.log(this.assessment);
}
}
api.service.ts:
import {Injectable} from 'angular2/core';
import {Http, Response} from 'angular2/http';
import {Observable} from 'rxjs/Observable';
@Injectable()
export class ApiService {
constructor(private _http: Http) {}
private _baseApiUrl = 'http://localhost:8000/';
getAssessments() {
return this.getObjectList('assessments', 'json');
}
getAssessment(id: number) {
return this.getObject('assessments', id, 'json');
}
getObjectList(listName:string, listExtension: string) {
var url = this._baseApiUrl + listName + '.' + listExtension;
return this._http.get(url)
.map(response => response.json())
.catch(this.handleError);
}
getObject(objectName:string, objectId:number, objectExtension: string) {
var url = this._baseApiUrl + objectName + '/' + objectId + '.' + objectExtension;
return this._http.get(url)
.map(response => response.json())
.catch(this.handleError);
}
private handleError(error: Response) {
console.error(error);
return Observable.throw(error.json().error || 'Server error');
}
}
最后 assessment.component.html:
<h3>{{ assessment.id }}</h3>
<button (click)="goBack()">Go Back 2</button>
当使用 url: http://localhost:3000/assessment/4 调用页面时,它对后端进行适当的调用 两次 并在控制台:
Uncaught EXCEPTION: TypeError: Cannot read property 'id' of undefined in [{{ assessment.id }} in AssessmentComponent@0:4]
ORIGINAL EXCEPTION: TypeError: Cannot read property 'id' of undefined
ORIGINAL STACKTRACE:
TypeError: Cannot read property 'id' of undefined
at AbstractChangeDetector.ChangeDetector_AssessmentComponent_0.detectChangesInRecordsInternal (viewFactory_AssessmentComponent:30:28)
at AbstractChangeDetector.detectChangesInRecords (http://localhost:3000/node_modules/angular2/bundles/angular2.dev.js:8116:14)
at AbstractChangeDetector.runDetectChanges (http://localhost:3000/node_modules/angular2/bundles/angular2.dev.js:8099:12)
at AbstractChangeDetector._detectChangesInViewChildren (http://localhost:3000/node_modules/angular2/bundles/angular2.dev.js:8184:14)
at AbstractChangeDetector.runDetectChanges (http://localhost:3000/node_modules/angular2/bundles/angular2.dev.js:8103:12)
at AbstractChangeDetector._detectChangesContentChildren (http://localhost:3000/node_modules/angular2/bundles/angular2.dev.js:8178:14)
at AbstractChangeDetector.runDetectChanges (http://localhost:3000/node_modules/angular2/bundles/angular2.dev.js:8100:12)
at AbstractChangeDetector._detectChangesInViewChildren (http://localhost:3000/node_modules/angular2/bundles/angular2.dev.js:8184:14)
at AbstractChangeDetector.runDetectChanges (http://localhost:3000/node_modules/angular2/bundles/angular2.dev.js:8103:12)
at AbstractChangeDetector.detectChanges (http://localhost:3000/node_modules/angular2/bundles/angular2.dev.js:8088:12)
ERROR CONTEXT:
[object Object]ExceptionHandler.call @ angular2.dev.js:1206(anonymous function) @ angular2.dev.js:12591NgZone._notifyOnError @ angular2.dev.js:13635collection_1.StringMapWrapper.merge.onError @ angular2.dev.js:13539Zone.run @ angular2-polyfills.js:1247NgZone._notifyOnTurnDone @ angular2.dev.js:13450(anonymous function) @ angular2.dev.js:13565zoneBoundFn @ angular2-polyfills.js:1220lib$es6$promise$asap$$flush @ angular2-polyfills.js:262
所以它看起来好像 {{ assessment.id }} 试图在对象从后端返回之前呈现。如果我然后单击 "Go Back 2" 按钮,它将控制台适当地记录对象:
{"id":4,"title":"Assessment Title here!","description":"Here is a description","questions":["http://localhost:8000/questions/1.json","http://localhost:8000/questions/2.json","http://localhost:8000/questions/3.json","http://localhost:8000/questions/4.json","http://localhost:8000/questions/5.json","http://localhost:8000/questions/6.json","http://localhost:8000/questions/7.json","http://localhost:8000/questions/8.json","http://localhost:8000/questions/9.json","http://localhost:8000/questions/10.json","http://localhost:8000/questions/11.json","http://localhost:8000/questions/12.json","http://localhost:8000/questions/13.json"],"responses":[]}
"Interestingly" 我没有在应用程序的其他地方看到问题(即可以很好地加载和显示评估列表等)。此外,我在昨晚作为学习项目使用的 Ionic2 应用程序中遇到了完全相同的问题。
用谷歌搜索了我的屁股,希望有人知道这个谜语的答案。
谢谢!
您有两个选择:
- 使用 safe navigation operator(以前称为 Elvis 运算符)
{{ assessment?.id }}
,它可以防止空值和未定义值 - 使用 NgIf,
<h3 *ngIf="assessment">{{assessment.id}}</h3>
,如果您不想在 DOM 中添加元素,直到数据为 available/populated ,这更合适
您可能不会遇到列表问题,因为 NgFor 会为您处理未定义、空或空数组的情况。然后当数据进来时,NgFor 被重新评估。