Parent component: .ts:
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, OnInit, Input } from '@angular/core';
import { MainInteractComponent } from '../main-interact/main-interact.component';
import { HotKeys } from '../Enums/hot-keys';
import { NodeElement, NodeParameter } from '../Models/Node';
@Component({
selector: 'node-interactive-list',
templateUrl: './node-interactive-list.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrls: ['./node-interactive-list.component.css']
})
export class NodeInteractiveListComponent implements OnInit {
nodes: NodeElement[] = [];
activeIndex = -1;
@Input() activeElement: NodeElement = null;
constructor(private parent: MainInteractComponent,
private http: HttpClient,
private ref: ChangeDetectorRef
) {
}
ngOnInit(): void {
}
@HostListener('document:keydown', ['$event'])
handleKeyboardEvent(e: any) {
if (e.altKey && e.keyCode === HotKeys.PressN) {
e.preventDefault();
this.ref.detectChanges();
this.addEmptyNode();
}
if (e.altKey && e.keyCode === HotKeys.PressS) {
//e.preventDefault();
//const model: any = {
// id: this.activeElement.id,
// properties: Object.assign({}, ...this.activeElement.parameters.concat([new NodeParameter("name", this.activeElement.name.toString())], this.activeElement.parameters)
// .map((x) => ({ [x.name.toString()]: x.value.toString() })))
//};
//if (!this.activeElement.id) {
// const headers = new HttpHeaders({ 'Content-Type': 'application/json' });
// return this.http.post('https://localhost:44350/Node',
// JSON.stringify(model), { headers }).subscribe((x: any) => {
// this.activeElement.id = x.id;
// });
//}
//else {
// const headers = new HttpHeaders({ 'Content-Type': 'application/json' });
// return this.http.put('https://localhost:44350/Node/' this.activeElement.id,
// JSON.stringify(model), { headers }).subscribe((x: any) => { });
//}
}
if (e.altKey && e.keyCode === HotKeys.PressR) {
//if (this.activeElement.id != null) {
// this.http.delete('https://localhost:44350/Node/' `${this.activeElement.id}`)
// .subscribe(() => {
// this.activeElement = new NodeElement();
// this.ref.detectChanges();
// });
//}
console.log(this.activeElement.id);
}
if (e.keyCode === HotKeys.ArrowUp) {
if (this.activeIndex > 0) {
this.activeIndex--;
this.activeElement = this.nodes[this.activeIndex];
this.ref.detectChanges();
}
}
if (e.keyCode === HotKeys.ArrowDown) {
if (this.activeIndex < this.nodes.length - 1) {
this.activeIndex ;
this.activeElement = this.nodes[this.activeIndex];
this.ref.detectChanges();
}
}
}
addEmptyNode() {
if (this.nodes.length == 0 || !this.nodes[this.nodes.length - 1].isClean()) {
let newNode = new NodeElement();
this.nodes.push(newNode);
this.activeIndex = this.nodes.length - 1;
this.activeElement = this.nodes[this.activeIndex];
this.ref.detectChanges();
}
}
}
.html:
<app-node-item *ngIf="activeElement" [(currentNode)]="activeElement"></app-node-item>
Child component: .ts:
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { ChangeDetectorRef, Component, HostListener, Input, Output, EventEmitter, OnInit } from '@angular/core';
import { HotKeys } from '../Enums/hot-keys';
import { NodeElement, NodeParameter } from '../Models/Node';
@Component({
selector: 'app-node-item',
templateUrl: './node-item.component.html',
styleUrls: ['./node-item.component.css']
})
export class NodeItemComponent implements OnInit {
@Input() currentNode: NodeElement = new NodeElement();
@Output() currentNodeChanged: EventEmitter<NodeElement> = new EventEmitter();
public currentSelectedIndex: number = -1;
public searchResults: any[] = [];
constructor(private http: HttpClient,
private ref: ChangeDetectorRef ) { }
ngOnInit(): void {
}
@HostListener('document:keydown', ['$event'])
handleKeyboardEvent(e: any) {
if ((e.srcElement.classList[0] == 'name-input' || e.srcElement.classList[1] == 'name-input') && !this.currentNode.id) {
setTimeout(() => {
const model: any = {
properties: {
'name': this.currentNode.name.toString()
}
};
const headers = new HttpHeaders({ 'Content-Type': 'application/json' });
return this.http.post('https://localhost:44350/Node/specs',
JSON.stringify(model), { headers }).subscribe((x: any) => {
this.searchResults = x.map(node => {
var result: [string, string][] = Object.entries(node.properties)
var resultNode: NodeElement = new NodeElement();
resultNode.parameters = result.map(y => new NodeParameter(y[0], y[1])).filter(z => z[0] != 'name');
resultNode.id = node.id;
resultNode.name = result.filter(z => z[0] == 'name')[0][1];
return resultNode;
});
this.ref.detectChanges();
});
}, 0)
}
if (e.altKey && e.keyCode === HotKeys.PressF) {
e.preventDefault();
if (this.currentSelectedIndex == -1) {
this.currentNode.parameters.push(new NodeParameter('', ''));
}
else {
this.currentNode.parameters.splice(this.currentSelectedIndex 1, 0, new NodeParameter('', ''));
}
setTimeout(() => {
if (this.currentSelectedIndex != -1) {
e.srcElement.parentNode.nextSibling.children[1].focus()
}
}, 0)
}
if (e.altKey && e.keyCode === HotKeys.PressD) {
e.preventDefault();
this.currentNode.parameters = this.currentNode.parameters.filter((x, i) => i != this.currentSelectedIndex);
if (this.currentSelectedIndex != -1 && this.currentSelectedIndex != -1 && !!e.srcElement.parentNode.previousSibling.children[1]) {
e.srcElement.parentNode.previousSibling.children[1].focus()
}
}
this.currentNodeChanged.emit(this.currentNode);
}
public getHints(option: any) {
if (!!option)
return (option.name);
else
return ''
}
public getName() {
if (!!this.currentNode?.name) return !!this.currentNode?.name; else new NodeElement();
}
public setName(event) {
if (!!this.currentNode?.name)
this.currentNode.name = event;
}
public getParameters() {
if (!!this.currentNode?.parameters)
return this.currentNode?.parameters
else return [];
}
public onParameterFocus(i: number) {
setTimeout(() => {
this.currentSelectedIndex = i;
}, 0)
}
public onParameterBlur() {
this.currentSelectedIndex = -1;
}
displayFn = state => {
if (!!state) {
this.currentNode = state;
return state?.name;
}
}
}
.html:
<p>name:</p>
<input type="text" matInput [ngModel]="currentNode" (ngModelChange)="currentNode.name = $event" [matAutocomplete]="auto"/>
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
<mat-option *ngFor="let option of searchResults" [value]="option">
{{getHints(option)}}
</mat-option>
</mat-autocomplete>
<li *ngFor="let item of currentNode.parameters; index as i;">
<br />
<input [ngModel]="item.name" (ngModelChange)="item.name = $event" (focus)="onParameterFocus(i)" (blur)="onParameterBlur()" />
<input [ngModel]="item.value" (ngModelChange)="item.value = $event" (focus)="onParameterFocus(i)" (blur)="onParameterBlur()" />
<br />
</li>
Property currentNode which's in child component has id, name, parametrs, e.t.c., and activeElement must have it also. I mean, when currentNode changes, activeElement also must. It's about data binding. But I don't understand how to make it correctly. I want that every time currentNode changes, activeElement also changed (i mean it must be two similar variables, when it comes to changing currentNode).
CodePudding user response:
Try to add two way binding:
Parent component HTML:
<app-node-item *ngIf="activeElement" [(node)]="activeElement"></app-node-item>
Maybe you'll need to handle it a little bit different with the *ngIf
Child component: .ts:
Add this:
@Output() currentNodeChange = new EventEmitter<NodeElement>();
And after all the possible changes (condition and all the manipulation) in the HostListener
add:
this.currentNodeChange.emit(this.currentNode);
CodePudding user response:
There are two ways to pass data from child to parent component.
Through Event Emitter: In child component.ts,
@Output() currentNodeChanged = new EventEmitter<NodeElement>();
Wherever you want to emit the output, there we have to emit the changes,
this.currentNodeChanged.emit(this.currentNode);
In parent component.html,
<app-node-item [currentNodeChanged]="currentNode.name = $event" *ngIf="activeElement" [(node)]="activeElement"></app-node-item>