事件处理-Angular 2 Components:如何处理循环输入和输出



当前情况:

我有一个parentchild组件。

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

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

问题:

当在parent中设置新数据时,它当然也会将其提供给刚刚提交数据的child组件。这将触发childngOnChanges,然后触发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之前,它将再次使其不可变。


有什么模式可以防止这些循环事件吗?

如果我们坚持使用双向事件触发器,我想不出一种方法来打破这个循环。尤其是多个孩子。

方法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';
    }

子组件.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. 回调将检查发件人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. childmodelChange 中存储最后发射值

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

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


简化代码示例:

@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 s来处理@Input() input

相关内容

  • 没有找到相关文章

最新更新