@Input Complex对象,通过ngOnInit初始化,抛出错误无法读取未定义的属性

时间:2017-08-01 13:06:39

标签: angular typescript

将参数传递给父组件中的子组件:

<app-conf-name
    [inputName]='person.name'
    (updatedNameEvent)='updateName($event)'>
</app-conf-name>

在组件TS文件中定义

@Input() inputName: Names;
@Output() updatedNameEvent: EventEmitter<Names> = new EventEmitter();
constructor() { }
editName: Names = new Names();

ngOnInit() {
  this.editName = this.inputName;
}

在类中,Names类型为correctPerson字段设置默认值。

export class Names {
  indivId: number;
  firstName: string;
  prefName: string;
  lastName: string;
  suffix: string;
  updated: boolean = false;
  correctPerson: boolean = false;
  correctAsIs: boolean = false;
  correctionDate: Date;
  addedDate: Date;
}

为什么我收到此错误:

ERROR TypeError: Cannot read property 'correctPerson' of undefined
    at Object.View_ConfNameComponent_0._co [as updateDirectives] (ConfNameComponent.html:9)
    at Object.debugUpdateDirectives [as updateDirectives] (core.es5.js:13056)
    at checkAndUpdateView (core.es5.js:12236)
    at callViewAction (core.es5.js:12601)
    at execComponentViewsAction (core.es5.js:12533)
    at checkAndUpdateView (core.es5.js:12242)
    at callViewAction (core.es5.js:12601)
    at execComponentViewsAction (core.es5.js:12533)
    at checkAndUpdateView (core.es5.js:12242)
    at callViewAction (core.es5.js:12601)

对象person是从父组件中调用的服务初始化的。

父组件

person: Person = new Person();
constructor(private peopleService: PeopleService) { }
state: string = 'one';
ngOnInit() {
    this.peopleService.getPersonById(this.Id).subscribe((data)=>{
      this.person = data;
      console.log(this.person);
      this.person.name.correctPerson = false;
      this.person.name.correctAsIs = false;
      this.person.email.correctAsIs = false;
      this.person.phone.correctAsIs = false;
      this.person.address.correctAsIs = false;
    });
}

请注意,我已经通过作业初始化了第二次抛出错误的属性。

子组件的原始代码使用输入变量作为工作变量。

在几次尝试回答这个问题并让整个项目工作之后,我已经到了最后一个子组件是最后一个在将ngIf放到任何地方之后抛出错误的点。

这里是指向完整GitHub Repo

的链接

3 个答案:

答案 0 :(得分:2)

您不应该访问@Input()中的OnInit参数,而应访问OnChanges中的Observable参数。它是OnInit,并且无法保证它会在{{1}}触发之前解决。

详情请看这里: Angular 2/4 @Input only moves intrinsics - not lists or objects

答案 1 :(得分:1)

你在做两件事。延迟加载数据并将数据加载到父ngOnInit中。因此,您必须假设孩子中的@Input()绑定可能为undefined

这是因为在@ViewChild执行之前设置了ngOnInit属性,但之后设置了@ContentChild。这意味着在加载数据之前,父组件的模板将呈现给视图,并且该模板正在将undefined传递给子项上的@Input()绑定。

  

注意:我希望上述内容是准确的。还没吃早上的咖啡。

无论哪种方式,您都应始终支持组件的缺失绑定。

@Input() inputName: Names;
@Output() updatedNameEvent: EventEmitter<Names> = new EventEmitter();
constructor() { }
editName: Names;

ngOnInit() {
  this.updateEditName();
}

ngOnChanges(changes: SimpleChanges) {
   if('inputName' in changes) {
       this.updateEditName();
   }
}

private updateEditName() {
      this.editName = this.inputName || new Names();
}

答案 2 :(得分:0)

上述代码的问题是对服务器的调用是异步的。这意味着在创建子组件时,并不一定意味着定义了名称。

数据流如下

  • 创建父组件,其中包含空的People对象。 (因此在调用people.name时不会失败,因为人们永远不会被定义)。
  • 初始化时,父组件进行异步查询。
  • 在init之后创建子组件(它不会等待异步调用完成)。
  • 子组件失败,因为父对象(人)上的名称未定义(可能在子组件的html中读取)。

您的问题有两个解决方法。第一种方法是在您的子组件标记上添加ngIf,如下所示

<app-conf-name 
    *ngIf='person.name' [inputName]='person.name'
    (updatedNameEvent)='updateName($event)'>
</app-conf-name>

第二个解决方案是在创建父组件之前使用解析保护来解析服务中的数据。

在您的路线定义中

{
  path: 'your-path', component: YourParentComponent,
  resolve: {
    person: PersonResolveGuard
  }
}

你的决心后卫

@Injectable()
export class BlockingResolveGuard implements Resolve<Person> {

    constructor(private peopleService: PeopleService) {}

    resolve(route: ActivatedRouteSnapshot):Observable<Person> {
       // Call to service
    }
}

在您的父组件中,注入当前激活的路径

constructor(private route:ActivatedRoute, private peopleService: PeopleService) { }

和你的ngOnInit()

ngOnInit() {
    this.route.data.resolve((data:{person:Person}) => {
       this.person = data.person;
    }
}

这个有角度的文件:详细解释了如何解决警卫(和其他警卫的工作)https://angular.io/guide/router#resolve-guard

请注意,使用上述解决方案,您需要使用解析保护中的activatedRouterSnapshot从路由参数中解析您正在检索数据的人员的ID。