Home > Enterprise >  How can I pass data from child component to parent component?
How can I pass data from child component to parent component?

Time:07-31

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.

  1. 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>
  • Related