使用依赖于ViewChildren的属性时出错

时间:2017-02-03 22:58:44

标签: javascript angular angular2-template angular2-forms

我创建了一个包含表单<address></address>的自定义组件。我有一个父组件,其中包含以下数组:

@ViewChildren(AddressComponent) addressComponents: QueryList<AddressComponent>;

因此可以包含这些元素的集合,用户可以根据输入的地址数添加和删除它们。

父级还有一个按钮,可在用户输入所有需要的地址后继续。但是,<address>组件必须正确填写,因此我在<address>组件上有一个公共getter:

get valid(): boolen {
  return this._form.valid;
}

返回父级按钮。如果任何<address>组件无效,则需要禁用它。所以我写了以下内容:

get allValid() {
   return this.addressComponents && this.addressComponents.toArray().every(component => component.valid);
}

模板中:

<button [disabled]="!allValid" (click)="nextPage()">Proceed</button>

但angular不喜欢这样,因为在addressComponents生命周期事件之前,ngAfterViewInit未在父级中定义。并且因为它立即运行ngOnViewInit(),我得到两个不同的表达式检查值,这会导致错误。 (至少这是我的想法)。

如何在模板中使用依赖于ngAfterViewInit的属性?或者告诉我的父母其所有孩子都有效的最佳方式是什么?

错误讯息

  

检查后表情发生了变化。上一个值:'false'。   当前价值:'true'

更新

所以我console.log修改了allValid的返回值并注意到它第一次是undefined。这是预期的,this.addressComponentsundefined,直到ngAfterInit。下一个日志是true,这是令人惊讶的,因为我在页面上还没有任何<address>组件。我在父组件的ngOnInit中使用模拟数据(尽管都是有效的)来创建组件。我确实知道([].every...在空数组上返回true)。因此,对console.log的第三次调用正在返回false。再次,我有点惊讶,因为我的所有数据都是有效的。在第4个日志中它返回true这是我的预期。所以我假设返回的最终值是Angular不喜欢的。

无论如何,我能够解决这个问题。我不知道我是否正在修复问题或者只是抑制错误。我不喜欢这个解决方案,因此我将继续提出问题以寻求更好的解决方案。

get allValid() {
  return this.addressComponents && this.addressComponents.length > 0 && this.addressComponents().toArray().every(component => component.valid);
}

2 个答案:

答案 0 :(得分:0)

所以我认为正在发生的事情:

第一波更改检测会使您的函数出错,然后您的父组件在实例化视图后找到此信息(然后返回true)。在“dev”模式下,Angular运行两次更改检测,以确保在更改检测后不会发生更改(当然,更改检测应检测所有更改!)

根据这里找到的答案:

Angular2 - Expression has changed after it was checked - Binding to div width with resize events

使用AfterViewInit会导致这些问题,因为它可能在更改检测完成后运行。

在超时时包装你的作业将解决这个问题,因为它会在设置值之前等待一个滴答。

ngAfterViewInit(){
    setTimeout(_ => this.allValid = this.addressComponents && this.addressComponents.toArray().every(component => component.valid));    
}

由于这些原因,我不会在模板变量上使用getter,因为视图初始化可能会在更改检测完成后更改值。

答案 1 :(得分:0)

如果我理解,您可能需要在父母中找到一种方法来跟踪孩子的实例数,并且孩子需要EventEmitter来通知家长它有效或无效。

因此,在父级中,您可以使用数组来跟踪有多少个地址实例。

父组件

addressForms: Array<any> = [{ valid: false }];
addAddressForm() {
  this.addressForms.push({ valid: false ));
}
checkValid() {
  // somehow loop through the addressForms, make sure all valid
  let allValid: boolean = false;
  for (var i = this.addressForms.length - 1; i >= 0; i--) {
    if (this.addressForms[i].value && allValid === false)
      allValid = true;
  }
  return allValid;
}

父模板

<div *ngFor="let form of addressForms; let i = index">
  <address (valid)="form.valid = true" (invalid)="form.valid = false"></address>
</div>
<button [disabled]="checkValid()">Next</button>

地址组件

@Output() valid: EventEmitter<any> = new EventEmitter();
@Output() invalid: EventEmitter<any> = new EventEmitter();
isValid: boolean = false;
check() {
  // call this check on field blurs and stuff
  if ("it's valid now" && !this.isValid) {
    this.isValid = true;
    this.valid.emit(null);
  }
  if ("it's not valid anymore" && this.isValid) {
    this.isValid = false;
    this.invalid.emit(null);
  }
}

无论如何,这是一个基本的想法,有一些明显足以填补的漏洞。希望这与你正在做的事情有一些相关性,我开始理解这个问题。祝你好运!