Current Situation:
I have a parent
and a child
component.
The parent
initializes the child
's data using its @Input
. And the child
notifies the parent, when the user edited the data using the @Output
. And because the data is immutable, the child
has to send the data along with that notification.
When the parent
got notified, it will check if the submitted data is valid, and then will set it (this will propagate the new value to some other child components as well).
The Problem:
When setting the new data inside the parent
, it will of course also give it to the child
component which just submitted the data. This will trigger the child
's ngOnChanges
, which then triggers a repaint of the UI.
Some Background:
The parent
has several different child
components, which all rely on the same myItem
data and can edit this data and then notify the parent
on change.
Here's a simplified version of the code, that should show up the problem.
Parent Component:
template:
<child [input]="myItem" (output)="onMyItemChange($event)">
code:
ngOnInit() {
this.myItem = getDataViaHTTP();
}
onMyItemChange($event) {
if($event.myItem.isValid()) {
this.myItem = $event.myItem;
}
}
Child Component:
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())
}
As you can see, the child
component takes the data from the @Input
and makes it mutable. And before sending it back to the parent
it will make it immutable again.
Is there any pattern to prevent these circular events?
I can't think of a way to break the away from the circle if we stick with bi-directionaly event trigger. Especially with multiple children.
One way I can think of is both parent and children use a share data service. Data is change once and for all, as all parties are using the same data.
globaldata.service.ts
import { Injectable } from '@angular/core';
interface ShareObj {
[id: string]: any;
}
@Injectable()
export class GlobalDataService {
shareObj: ShareObj = {};
}
app.module.ts(assume this is your root module)
import { GlobalDataService } from './globaldata.service';
//
// skip ..
//
@NgModule({
//
// skip ..
//
provider:[GlobalDataService]
})
export class AppModule {}
parent.component.ts (assuming non-root, multiple instances, part of 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;
Use RxJs subject subscription, like a broadcast queue. I have actually created a package with example:
https://github.com/J-Siu/ng2-simple-mq
https://github.com/J-Siu/ng2-simple-mq-example
The idea:
Parent (assuming non-root, multiple instances, part of 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);
}
}
}
Child
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);
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With