Angular 2组件:如何处理循环输入和输出

时间:2016-08-12 14:18:41

标签: angular event-handling immutability angular2-template

现状:

我有一个parent和一个child组件。

parent使用child初始化@Input的数据。当用户使用child编辑数据时,@Output会通知父级。由于数据是不可变的child必须与该通知一起发送数据。

parent收到通知后,它会检查提交的数据是否有效,然后设置它(这会将新值传播给其他一些子组件)。

问题:

parent中设置新数据时,它当然也会将其提供给刚刚提交数据的child组件。这将触发child ngOnChanges,然后触发重新绘制UI。

一些背景:

parent有几个不同的child组件,这些组件都依赖于相同的myItem数据,可以编辑此数据,然后在更改时通知parent

这是代码的简化版本,应该会显示问题。

父组件:

template:
    <child [input]="myItem" (output)="onMyItemChange($event)">

code:
    ngOnInit() {
        this.myItem = getDataViaHTTP();
    }

    onMyItemChange($event) {
        if($event.myItem.isValid()) {
            this.myItem = $event.myItem;
        }
    }

子组件:

template:
    <input [(ngModel)]="myItem.name" (ngModelChange)="modelChange($event)">

code:
    @Input() input;
    @Output() output = new EventEmitter();

    myItem;

    ngOnChanges(changes) {
        this.myItem = changes.input.currentValue.toMutableJS();
    }

    modelChange($event) {
        this.output.emit(this.myItem.toImmutableJS())
    }

如您所见,child组件从@Input获取数据并使其可变。在将其发送回parent之前,它将再次使其成为不可变的。

是否有任何模式可以阻止这些循环事件?

2 个答案:

答案 0 :(得分:6)

如果我们坚持使用bi-directionalaly事件触发器,我想不出一种打破圆圈的方法。特别是有多个孩子。

方法1

我能想到的一种方式是父母和孩子都使用共享数据服务。数据一劳永逸地变化,因为所有各方都在使用相同的数据。

<强> globaldata.service.ts

import { Injectable } from '@angular/core';

interface ShareObj {
  [id: string]: any;
}

@Injectable()
export class GlobalDataService {
  shareObj: ShareObj = {};
}

app.module.ts(假设这是您的根模块)

import { GlobalDataService } from './globaldata.service';
//
// skip ..
//

@NgModule({
  //
  // skip ..
  //

  provider:[GlobalDataService]

})
export class AppModule {}

parent.component.ts(假设非root,多个实例,app.module的一部分)

template:
    <child [parent]="myId"></child>

code:
    import { GlobalDataService } from './globaldata.service';
    //
    // skip ..
    //

    // use uuid to generate unique id
    private uuid = require('node-uuid');
    myId = this.uuid.v1();

    constructor(private gd: GlobalDataService){
        // This can be string, array or object
        this.gd.shareObj[myId]='data';
    }

<强> child.component.ts

template:
    <input [(ngModel)]="gd.shareObj[parent]">

code:
    import { GlobalDataService } from './globaldata.service';
    //
    // skip ..
    //

    constructor(private gd: GlobalDataService){}

    @Input() parent;

方法2 - 广播队列

使用RxJs主题订阅,如广播队列。我实际上创建了一个包含示例的包:

https://github.com/J-Siu/ng2-simple-mq

https://github.com/J-Siu/ng2-simple-mq-example

这个想法:

  1. 父和所有孩子将订阅同一个队列
  2. 在广播到队列时包含发件人ID,如果您使用我的包,则可以使用订阅ID作为发件人ID,因为它是一个uuid。
  3. 如果消息是来自
  4. ,则回拨将检查发件人ID并且不执行任何操作

    父级(假设非root用户,多个实例,app.module的一部分)

    import {Component, OnInit} from '@angular/core';
    import {SimpleMQ} from 'ng2-simple-mq';
    
    template:
        <child [parent]="myId"></child>
    
    code:
      export class SomeComponent implements OnInit {
        title = 'Some Component';
    
        // use uuid to generate unique id
        private uuid = require('node-uuid');
        myId = this.uuid.v1();
        myItem = {};
    
        constructor(private smq: SimpleMQ) { }
    
        ngOnInit() {
            this.smq.subscribe(this.myId, e => this.receiveBroadcast(e));
        }
    
        broadcast() {
    
            let msg = {
                id: this.myId,
                msg: 'some messages or object go here'
            };
    
            // Publish to queue name 'this.myId'
            this.smq.publish(this.myId, msg);
        }
    
        receiveBroadcast(m) {
            if (m.id !== this.myId) {
                // msg from soneone else, lets do something
    
                this.myItem = m.msg; // Update local data
    
                console.log(m.Id + ' received: ' + m.msg);
            }
        }
    }
    

    儿童

    import {Component, Input, OnInit} from '@angular/core';
    import {SimpleMQ} from 'ng2-simple-mq';
    
    template:
    <input [(ngModel)]="myItem.name" (ngModelChange)="broadcast()">
    
    code:
      export class SomeComponent implements OnInit {
        title = 'Some Component';
    
        @Input() parent;
        // use uuid to generate unique id
        private uuid = require('node-uuid');
        myId = this.uuid.v1();
    
        myItem = {};
    
        constructor(private smq: SimpleMQ) { }
    
        ngOnInit() {
            this.smq.subscribe(parent, e => this.receiveBroadcast(e));
        }
    
        broadcast() {
    
            let msg = {
                id: this.myId,
                msg: this.myItem // send the whole object
            };
    
            // Publish to queue name = parent id
            this.smq.publish(parent, msg);
        }
    
        receiveBroadcast(m) {
            if (m.id !== this.myId) {
                // msg from soneone else, lets do something
    
                this.myItem = m.msg; // Update local data
    
                console.log(m.Id + ' received: ' + m.msg);
            }
        }
    }
    

答案 1 :(得分:0)

我现在提出了一个有效的(但非常<强>丑陋的解决方案):

  1. child最后一次发布的值存储在modelChange

  2. 调用ngOnChanges时,我们可以比较这些对象的引用

  3. 如果引用相等,则此特定child组件创建值

  4. 简化代码示例:

    @Input() input;                            // most recent version (immutable)
    @Output() output = new EventEmitter();
    
    myItem;                                    // mutable version
    lastMyItem;                                // last emitted version (immutable)
    
    ngOnChanges(changes) {
        // this will only trigger, if another component
        // created the data instance
    
        if(changes.input.currentValue !== lastMyItem) {
            this.myItem = changes.input.currentValue.toMutableJS();
        }
    }
    
    modelChange($event) {
        this.lastMyItem = this.myItem.toImmutableJS();
        this.output.emit(this.lastMyItem);
    }
    

    它按预期工作,但我仍然想知道是否有更好的方式使用更少的代码。

    也许可以使用Observable的{​​{1}}解决此问题?