Angular (4): pass a class object from child to parent component - html

I can pass a class object like Person into a child component from parent component without any problems. But I would like to also manipulate that object in child component and pass it back to parent component.
This is the child component class:
export class ActionPanelComponent {
#Input('company') company: Company;
#Output() notify: EventEmitter = new EventEmitter();
constructor() {
}
public deleteCompany() {
console.log('display company');
console.log(this.company);
// FIXME: Implement Delete
this.company = new Company();
}
public onChange() {
this.notify.emit(this.company);
}
}
This is the html of this component (excerpt):
<div class="row" (change)="onChange()">
<div class="col-xs-2">
<button md-icon-button >
<md-icon>skip_previous</md-icon>
</button>
</div>
This is the parent component class (excerpt):
public onNotify(company: Company):void {
this.company = company;
}
And the parent component html (excerpt):
<action-panel [company]="company" (notify)="onNotify($event)"></action-panel>
I am doing something wrong because I cannot pass my company object inside the .emit and nothing works.
What is the correct way of achieving two way object binding between components?
Thanks in advance!

You were missing the type on the initialization of the EventEmitter.
You could use the Output binding to implement the two way object binding:
Child component (ts)
export class ActionPanelComponent {
#Input('company') company: Company;
#Output() companyChange: EventEmitter = new EventEmitter<Company>();
constructor() {
}
public deleteCompany() {
console.log('display company');
console.log(this.company);
// FIXME: Implement Delete
this.company = new Company();
}
public onChange() {
this.companyChange.emit(this.company);
}
}
Parent component (html)
<action-panel [(company)]="company"></action-panel>
So like this you don't need to declare an extra function onNotify. If you do need the onNotify function, use another name for the output binding:
export class ActionPanelComponent {
#Input('company') company: Company;
#Output() notify: EventEmitter = new EventEmitter<Company>();
constructor() {
}
public deleteCompany() {
console.log('display company');
console.log(this.company);
// FIXME: Implement Delete
this.company = new Company();
}
public onChange() {
this.notify.emit(this.company);
}
}

Change it like this to tell TS which Type the EventEmitter should emit:
export class ActionPanelComponent {
#Input('company') company: Company;
#Output() notify = new EventEmitter<Company>(); //<---- On this line!
constructor() {
}
public deleteCompany() {
console.log('display company');
console.log(this.company);
// FIXME: Implement Delete
this.company = new Company();
}
public onChange() {
this.notify.emit(this.company);
}
}

It is a workaround that worked for me, if it is helpful for anyone.
Your parent parent-component.ts would be like;
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'parent',
templateUrl:'./parent.component.html',
styleUrls: ['./parent.component.css']
})
export class Parent implements OnInit {
let parentInstance= this; //passing instance of the component to a variable
constructor() { }
parentMethod(var:<classtyepyourchoice>){
console.log(var);
}
ngOnInit() {
}
}
In you parent.component.html, you would have your child
<child [parent]="parentInstance" ></child>
This object will be available in the child component
Now, in your child component you will receive this like
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'child',
templateUrl:'./child.component.html',
styleUrls: ['./child.component.css']
})
export class Child implements OnInit {
#Input('parent') parent;
constructor() { }
childMethod(yourClassObject){
this.parent.parentMethod(yourClassObject);
}
ngOnInit() {
}
}
Thus, you can pass classobject from your child, like this, it worked for me.

Related

How to use private properties in my html-file?

I am trying to show a list of Animals in my html page with their corresponding name and color.
My frontend gets the data from a spring backend that returns a list of Animals.
And I stumbled upon 2 questions that I have:
1)
I made the name and color properties private in the Animal class.
Code of the animal class:
interface AnimalJson {
name: string;
color: string;
}
export class Animal {
constructor(private name: string, private color: string) {}
static fromJSON(json: AnimalJson): Animal {
const a = new Animal(json.name, json.color);
return a;
}
}
code of my animal-component:
import { Component, OnInit } from '#angular/core';
import { Observable } from 'rxjs';
import { DataServiceService } from '../data-service.service';
import { Animal } from '../models/Animal';
#Component({
selector: 'app-animal',
templateUrl: './animal.component.html',
styleUrls: ['./animal.component.css'],
})
export class AnimalComponent implements OnInit {
public animals: Observable<Animal[]>;
constructor(private dataService: DataServiceService) {
this.animals = new Observable<Animal[]>();
}
ngOnInit(): void {
this.animals = this.dataService.getAnimals();
}
}
code of the service:
#Injectable({
providedIn: 'root',
})
export class DataServiceService {
constructor(private http: HttpClient) {}
getAnimals(): Observable<Animal[]> {
return this.http
.get<Animal[]>('http://localhost:8080/animals')
.pipe(map((animals: any[]): Animal[] => animals.map(Animal.fromJSON)));
}
}
code of the html-page:
<div *ngFor="let animal of animals | async">
<p>{{ animal.name }}</p>
</div>
Now when I try to get the animal.name, it gives an error that the name is private so I cant use it in my html page. How should I fix this? Should I just make it public? Or is there something I forget?
2)
Is this how you work with observables? Or am I using my observables in a wrong way?
Using the http get methode to get the observable and than call it in my animal-component and use async in my html-file to go over all the values in it?
If you use private then it should not be used in the html, am not sure why you are using a class for initializing the array. Just use a simple map statement.
If you are going to show it in the HTML then don't make the property private.
So the changes are.
interface Animal {
name: string;
color: string;
}
Service will be.
#Injectable({
providedIn: 'root',
})
export class DataServiceService {
constructor(private http: HttpClient) {}
getAnimals(): Observable<Animal[]> {
return this.http
.get<Animal[]>('http://localhost:8080/animals')
.pipe(map((animals: any[]): Animal[] => animals.map((item: Animal) => ({name: item.name, color: item.color}))));
}
}
Note: class can also be used as an interface, so when using animal you defined the properties as private, so you are unable to use in the HTML.

How to pass a function from a parent to a deep nested child and use an #input value into the passed function in Angular 8?

I have 3 components in this situation:
-OuterComponent
--MiddleComponent
---InnerComponent
I need to pass a function from OuterComponent to InnerComponent through MiddleComponent.
It is important to mention that the function I need to pass does take an input: DoSomething(node)
I don't know if it is relevant but I am already passing a NodeTree from the OuterComponent to the MiddleComponent and then I am unpacking the NodeTree into a Node and passing it InnerComponent. This Node is what I need to use as an input for the function being passed.
So, I need to be able to use an #Input as the input for the function being passed to the InnerCompoenent, which I assume will need to be an #output.
Method 1:
You can call the parent component function(OuterComponent) from the child component(InnerComponent) using #Output.
OuterComponent HTML:
<MiddleComponent (updateOuterComponent)="parentFunction($event)" />
OuterComponent TS:
export class OuterComponent implements OnInit {
constructor() {}
ngOnInit() {}
parentFunction(para) {
console.log(para);
// operations you want to do in parent component
}
}
MiddleWare HTML:
<InnerComponent (updateMiddleComponent)="middleFunction($event)" />
MiddleComponent TS:
export class MiddleComponent implements OnInit {
#Output() updateOuterComponent = new EventEmitter();
constructor() {}
ngOnInit() {}
middleFunction(para) {
this.updateOuterComponent.emit(para);
}
}
InnerComponent HTML:
It can be whatever you want to write
InnerComponent TS:
export class InnerComponent implements OnInit {
#Output() updateMiddleComponent = new EventEmitter();
constructor() {}
ngOnInit() {}
updateMiddleAndParent(para) {
this.updateMiddleComponent.emit(para);
}
}
Once you call updateMiddleAndParent function form Inner component using emitter, that will trigger middleFunction in the MiddleComponent. After triggering middleFunction, Similarly middleFunction will trigger parentFunction with the help of emitter.
Method 2:
You need to create a service and use it to call the parent function:
DataService:
import {BehaviorSubject} from "rxjs/BehaviorSubject"
export class DataService {
private state$ = new BehaviorSubject<any>('initialState');
changeState(myChange) {
this.state$.next(myChange);
}
getState() {
return this.state$.asObservable();
}
}
call DataService in both InnerComponent and OuterComponent:
In the OuterComponent call DataService and call getState(), this will return an observable whenever the value changes you can any function using data passed in observable response.
In the InnerComponent call DataService and use the changeState() to change the value.
once the value is changed in DataService, then in parent Component the value will be change as you are subscribed to the observable, You will get the updated data from there you can call any function in parent.

catch paste events on a dropdown

I'm trying to catch paste event occuring on input fields.
It works perfectly on textarea and input but not on dropdowns. Select.
Here is my directive, the console.log is never called.
import { Directive, HostListener } from '#angular/core';
#Directive({ selector: '[catchPasteEvents]' })
export class CatchPastEvent {
#HostListener('onpaste') onPaste(event) {
console.log('Paste', event);
}
}
I created this directive that set a listener on document paste (as suggested by Sergey) but only when the select element is focused.
#Directive({ selector: 'select[selectPaste]' })
export class SelectPasteDirective implements OnDestroy {
#Output()
public paste: EventEmitter<ClipboardEvent> = new EventEmitter();
private listener: (event: ClipboardEvent) => void = this.handlePaste.bind(
this,
);
public ngOnDestroy(): void {
document.removeEventListener('paste', this.listener);
}
#HostListener('focus')
public onFocusedItem() {
document.addEventListener('paste', this.listener);
}
#HostListener('blur')
public onBlurItem() {
document.removeEventListener('paste', this.listener);
}
private handlePaste(event: ClipboardEvent) {
this.paste.emit(event);
}
}

Angular View binding not updating with simple boolean

Goodday, This is probably a nooby question but I can't get it to work.
I have a simple service which toggles an boolean, if the boolean is true the class active should appear on my div and if false no class.. Simple as that. But the boolean gets updated, but my view doesn't react to it. Do I somehow have to notify my view that something has changed ?
Service:
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class ClassToggleService {
public menuActive = false;
toggleMenu() {
this.menuActive = !this.menuActive;
}
}
View (left menu component):
<div id="mainContainerRightTop" [class.active]="classToggleService.menuActive == true">
Toggle point (top menu component):
<a id="hamburgerIcon" (click)="classToggleService.toggleMenu()">
This because you are changing a value on a service not on the component, so Angular don't need to update the component, because it did not change. If you want to update the view of your component when a service element is modified, you have to use an Observables and Subjects, and subscribe to them. In this way when the element is changed, it automatically notify all the subscribed components.
#Injectable({
providedIn: 'root'
})
export class ClassToggleService {
public menuSubject: Subject<boolean> = new BehaviorSubject<boolean>(false);
public menuActive = this.menuSubject.asObservable();
toggleMenu(val : boolean) {
this.menuSubject.next(val);
}
}
And in your component just implement OnInit interface and subcribe to the observable in the your service:
public localBool = false;
ngOnInit() {
this._myService.menuActive.subscribe(value => this.localBool = value);
}
ComponentToggleMenu() {
this._myService.toggleMenu(!this.localBool);
}
Then your html:
<div id="mainContainerRightTop" [class.active]="localBool">
<a id="hamburgerIcon" (click)="ComponentToggleMenu()">
Why we need service, this should be integrated with component class. As a general rule, you are not supposed to call service method in template file.
export class TestComponent implements OnInit{
public menuActive = false;
toggleMenu() {
this.menuActive = !this.menuActive;
}
}
Template:
<div id="mainContainerRightTop" [class.active]="menuActive">

pass array from parent component to child angular

I have 2 components: parent and child.
I want to send an array from parent to child when I click on a button(from parent-component) and call a function generate() for every object in array(in child-component).
I have tried something with #Output() EventEmitter but I am not sure of this approach.
parent component
export class ViewCalendarsComponent implements OnInit {
#ViewChildren(MonthHeaderComponent) months: any[];
#Output() monthsList: EventEmitter<Date[]> = new EventEmitter();
selectedMonths: any[];
test() {
this.monthsList.emit(this.selectedMonths);
}
}
child component
export class MonthHeaderComponent implements OnInit {
ngOnInit() {
}
generate(date: Date) {
// code here....
}
show() {
for (let i = 0; i <= monthsList.length; i++)
{
generate(monthsList[i]);
}
}
}
and in parent html
<button class="primary" (click)="test()"> Show </button>
<div class="right view-calendar">
<child *ngFor="let selectedMonth of selectedMonths" [monthsList]="show($event)"></child>
</div>
How can I send this array and use it as parameter in a method?
Just use the #input instead of output. And when the input value get modifies, use the ngOnChanges to catch the changes.
child component
export class MonthHeaderComponent implements OnInit {
#Input monthsList: Date[]
ngOnChanges(){
// catch changes
}
}
In the parent template, pass the array.
<child *ngFor="let selectedMonth of selectedMonths" [monthsList]="monthArr"></child>