角向父级到子级绑定更改检测

时间:2019-07-29 12:23:46

标签: javascript angular angular-template

简而言之,我有一个可用作文本输入的组件。父组件使用@Input绑定将数据传递到此组件。当通过@Output绑定触发change事件时,我将在父组件中执行一些自动验证。这是为了删除错误的值并将其替换为合理的默认值,以提供更好的用户体验。

这是父组件的基本版本

@Component({
    selector: 'parent',
    template: `
    <div>
        <child [value]="item.key1"
               (valueChange)="onValueChange($event)">
            </child>
    </div>
    `
})
export class Parent {
    item = {
        key1: 'test value',
        key2: 'another test value'
    };

    onValueChange(newValue) {
        // Perform some validation and set default
        if (newValue.length > 4) {
            newValue = newValue.substring(0, 4);
        }

        this.item.key1 = newValue;
    }
}

子组件

@Component({
    selector: 'child',
    template: `
    <div>
        <input type="text"
               [(ngModel)]="value"
               (blur)="onBlur($event)" />
    </div>
    `
})
export class Child {
    @Input() value: string;
    @Output() valueChange = new EventEmitter();

    onBlur() {
        this.valueChange.emit(this.value);
    }
}

请参见here for a Plunker example

我遇到的问题如下:

在子输入中输入值并触发blur事件时,新值会冒泡传给父项,并应用验证-如果验证导致值被修改,则会冒出回去正确地对待孩子的价值-快乐的日子。

但是,如果前4个字符保持不变,并且您追加了其他字符,则blur仍将应用验证,并且父级的值将正确更新,但子级将保留“无效”价值-不再有快乐的日子。

因此,在我看来,Angular并未检测到父级数据中的更改(足够公平,因为它在技术上没有更改),因此也没有将“最新”值返回给孩子。

我的问题是,即使技术上没有“更改”,如何从子文本输入中始终显示出正确的值呢?

4 个答案:

答案 0 :(得分:2)

  

但是,如果前四个字符保持不变,则您追加   其他字符,模糊后仍将应用验证   父级的值将正确更新,但子级将   保留“无效”值-不再有快乐的日子。

     

因此,在我看来,Angular并未检测到   父母数据(足够公平,因为它在技术上没有改变)因此   不会将“最新”值发送回给孩子。

正确。事实是,angular会“缓存”通过value输入属性传递的最新值,并且如您所知,如果您不更改前4个字符,就不会真正将新值推入其中。

您可以通过向孩子添加ngOnChanges(changes)并将 changes 值记录到控制台中来进行检查;在上述情况下,不会记录任何内容,因为不会推送value的新值。

您可以通过以下方式克服这一点:

  • 通过包装对象中的值并在子组件中解包来强制始终通过输入属性推送新值。 (IMO的处理方法不正确)
  • 委派将“验证”逻辑委派给子组件,以便仅通过输出属性发出“已验证”值。

答案 1 :(得分:1)

更好的解决方案

@ Jota.Toledo的好评论让我意识到我的方法虽然在当时对我来说是一种快速的解决方法,但它不是一个好方法,所以我实际上对项目进行了一些更改以使其可行对您也一样,也遵循他对

的建议
  

将“验证”逻辑委托给子组件

同时将验证定义保留在父级中,并将其作为一个作为@Input参数传递给子级的函数。

这样,我可以给父母2个公共变量

  • 一个对象(项目)
  • 函数(验证)

并更改onValueChange函数以仅更新item.key1,因为它将已经通过验证。

在子级中添加一个新的@Input类型的Function参数(验证),并使用该函数在onBlur内部验证newValue,然后再将值发送给父级。

我觉得我在这里写的内容可能会“听起来”有些混乱,因此我要添加要解释的代码。

父母

@Component({
selector: 'parent',
template: `
<div>
    <p><b>This is the parent component</b></p>
    <child [value]="item.key1"
           [validation]="validation"
           (valueChange)="onValueChange($event)">
        </child>
    <p><b>Variable Value:</b> {{item | json}} </p>
</div>
`
})
   export class Parent {
   item = {
       key1: 'test value',
       key2: 'another test value'
   };

   validation = (newValue) => {
       if (newValue.length > 4) {
           newValue = newValue.substring(0, 4);
       }

       return newValue;
   }

   onValueChange(newValue) {
       this.item.key1 = newValue;
   }
}

孩子(将模板部分保留,因为它没有变化)

export class Child {
    @Input() value: string;
    @Input() validation: Function;
    @Output() valueChange = new EventEmitter();

    onBlur() {
        this.value = this.validation(this.value)
        this.valueChange.emit(this.value);
    }
}

上一个答案(不是一个好的方法)

过去,我遇到了类似的问题,而解决的方法是清除var,然后在setTimeout内将其最终值赋予而不指定毫秒。

在您的父组件中,它看起来像这样:

onValueChange(newValue) {
    console.log('Initial: ' + newValue);

    if (newValue.length > 4) {
        newValue = newValue.substring(0, 4);
    }
    console.log('Final: ' + newValue);
    this.item.key1 = '';
    setTimeout(() => {
        this.item.key1 = newValue;
    });

}

答案 2 :(得分:-1)

您应该在Subject上创建一个ParentComponent

export class ParentComponent {
  parentSubject:Subject<Item> = new Subject();

  notifyChildren() {
    this.parentSubject.next('new item value');
  }
 }

将其作为Input()传递到ChildComponent上:

<child [parentSubject]="parentSubject"></child>

最后在subscribeChildComponent

export class ChildComponent {
@Input() parentSubject:Subject<Item>;

ngOnInit() {
 this.parentSubject.subscribe(event => {
   //do your stuff with the updated value
 });
}

答案 3 :(得分:-1)

您可以通过以下操作更新parent.ts中onValueChange方法的代码

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
  <link rel="stylesheet" href="/assets/vendor/libs/c3/c3.css">
    <div id="chart"></div>  
 <script type="text/javascript" >
 $(function() {
    var chart = c3.generate({
    bindto: '#chart',
    data: {
      columns: [
        ['data1', 30, 200, 100, 400, 150, 250],
        ['data2', 50, 20, 10, 40, 15, 25]
         ]
     }
    }); 
});
</script>
  <script src="/assets/vendor/libs/d3/d3.js"></script>
  <script src="/assets/vendor/libs/c3/c3.js"></script>