在ngOnInit中设置的属性会导致“类型错误:this.student未定义”

时间:2019-07-05 20:21:05

标签: angular typescript

编辑:我在这里发布的代码可以像我最初编写的那样正常工作。我的学生界面中有一个愚蠢的错字,因此对于每个学生对象,始终未定义值“ ID”。我很生气,花了我几天的时间才意识到这一点。谢谢大家的回答。

我目前正在学习Angular,但是在组件的OnInit方法中设置属性时遇到问题。我正在使用服务组件从json文件中获取数据,并将其分配给名为“ student”的自定义界面的实例。

当以数组的形式获取所有学生数据以显示在表格中时,一切都将正常运行。但是,当尝试仅获取一个学生的信息以显示在“详细”模板上时,出现“ TypeError:_co.student未定义”。

在模板中添加“ * ngIf ='student'”可消除错误消息,但不会填充模板。

这是获取服务组件中定义的数据的功能

private studentURL = 'assets/college-apps.json';

    constructor(private http: HttpClient){}

    getStudents(): Observable<Student[]> {
        return this.http.get<Student[]>(this.studentURL);
    }

    getStudent(id: number): Observable<Student> {
        return this.getStudents().pipe(map(students => students.find(student => student.ID === id)));
    }

这是学生详细信息组件中的ngOnInit方法

student: Student;

    constructor(private route: ActivatedRoute, private router: Router, private studentService: StudentService) {}

    ngOnInit() {
        let id = +this.route.snapshot.paramMap.get('id');

        this.studentService.getStudent(id).subscribe(
            student => this.student = student
        )
    }

这是模板

<mat-card *ngIf='student'>
    <mat-card-title>{{student.firstName}} {{student.lastName}}'s Details</mat-card-title>

    <mat-card-content let stude>First Name: {{student.firstName}} </mat-card-content>
    <mat-card-content>Last Name: {{student.lastName}} </mat-card-content>
    <mat-card-content>High School: {{student.highSchool}} </mat-card-content>
    <mat-card-content>GPA: {{student.gpa}} </mat-card-content>
    <mat-card-content>Reading SAT Score: {{student.readingSatScore}} </mat-card-content>
    <mat-card-content>Math SAT Score: {{student.mathSatScore}} </mat-card-content>

    <a mat-stroked-button [routerLink]="['']">Back</a>
</mat-card>

3 个答案:

答案 0 :(得分:0)

由于this.student是在未定义{第一次} this.student时在异步回调(在订阅中)中分配的,因此模板是第一次分配。当http调用返回一个值时,this.student得到一个值,直到那时angular尝试渲染模板时,它将产生未定义的错误。要解决这种情况,angular拥有一个名为safe navigation operator的运算符。

  

Angular安全导航运算符?防止null和   模板的属性路径中未定义的值。

<mat-card>
    <mat-card-title>{{student?.firstName}} {{student?.lastName}}'s Details</mat-card-title>

    <mat-card-content>First Name: {{student?.firstName}} </mat-card-content>
    <mat-card-content>Last Name: {{student?.lastName}} </mat-card-content>
    <mat-card-content>High School: {{student?.highSchool}} </mat-card-content>
    <mat-card-content>GPA: {{student?.gpa}} </mat-card-content>
    <mat-card-content>Reading SAT Score: {{student?.readingSatScore}} </mat-card-content>
    <mat-card-content>Math SAT Score: {{student?.mathSatScore}} </mat-card-content>

    <a mat-stroked-button [routerLink]="['']">Back</a>
</mat-card>

这是https://stackblitz.com/edit/angular-gem5sk概念的简单演示

尝试删除模板中的?之一,您将在控制台中看到未定义的错误。

还应注意,直到http调用完成并且分配给模板中this.student的相关位置的值将为空,这可能会带来糟糕的用户体验,尤其是对于持久的http调用。因为在应用程序中必须包含某种加载器机制,但这与该问题密切相关。在这里写只是作为补充信息。

还将console.log和async块一起放置,以正确查看是否从http调用返回了值

ngOnInit() {
    let id = +this.route.snapshot.paramMap.get('id');

    this.studentService.getStudent(id).subscribe(student => {
         this.student = student
         console.log(this.student); // prints when the request completes
    });
    console.log(this.student); // always prints undefined, and gets printed before above console.log
}

答案 1 :(得分:0)

您可以尝试将类变量重命名为student$($表示异步值) 然后更改您的mat-card标签:

<mat-card *ngIf="student$ | async as student>
  ... 
</mat-card>

答案 2 :(得分:-2)

您有两个问题:

1)student最初是未定义的,这会导致错误。

您通过在模板中添加*ngIf="student"来正确地解决了该问题,但随后遇到了第二个问题。

2)您的页面未使用最新值更新。

之所以会发生这种情况,是因为您没有在this.student中设置ngOnInit,而是通过一段时间后的回调进行设置的(特别是在网络请求完成后)。由于此代码发生在“未知”时间,因此不会通知Angular数据已更改,并且需要重新构建视图。

因此,您只需要手动通知Angular“嘿,某些更改”:

import { ChangeDetectorRef } from "@angular/core";

[...]

constructor(private route: ActivatedRoute, 
            private router: Router, 
            private studentService: StudentService,
            private changeDetector: ChangeDetectorRef) {}

ngOnInit() {
    let id = +this.route.snapshot.paramMap.get('id');

    // Start network request
    this.studentService.getStudent(id).subscribe(
        student => {
            // Run after the request is completed
            this.student = student;
            console.log(this.student);
            this.changeDetector.markForCheck();
        }
    );
    // Run immediately after starting the network request
    console.log(this.student); // Undefined because the request hasn't completed yet
}