How to emit an event from grandchildren to grandparent in modern angular? - javascript

If I have multiple levels of angular components, how can I use #Output to emit an event from child to the grand parent?
Grandparent:
<parent (handleClick)="grandmaHandleClick($event)">
<parent>
...
grandmaHandleClick(event) {
console.log('grandma knows you clicked')
}
Parent:
<child (handleClick)="handleClick($event)">
</child>
Child:
<div (click)="onClick()">Click button
</div>
...
#Output() handleClick = new EventEmitter
onClick() {
this.handleClick.emit('clicked a button')
}
I am trying to have it so that #Output can prop drill a few components deep, whats the best way to accomplish this, and can you provide example?

There could be 2 ways:
Using #output:
Grandparent
<parent (notifyGrandParent)="grandmaHandleClick($event)">
<parent>
...
grandmaHandleClick(event) {
console.log('grandma knows you clicked')
}
Parent:
<child (handleClick)="childEvent($event)">
</child>
#Output() notifyGrandParent= new EventEmitter();
childEvent(event) {
this.notifyGrandParent.emit('event')
}
Child is implemented properly in the code so it is good to go.
Using BehaviorSubject via Service: With this much level of nesting, you can actually create some service like EventService, and then create BehaviorSubject which can directly be subscribed by the GrandParent. Also, to make this service more component specific, you can keep this service in a module which will have other 3 components (GrandParent, Parent and Child)
export class EventService{
private childClickedEvent = new BehaviorSubject<string>('');
emitChildEvent(msg: string){
this.childClickedEvent.next(msg)
}
childEventListner(){
return this.childClickedEvent.asObservable();
}
}
and then in components:
ChildComponent
export class ChildComponent{
constructor(private evtSvc: EventService){}
onClick(){
this.evtSvc.emitChildEvent('clicked a button')
}
}
GrandParent
export class GrandComponent{
constructor(private evtSvc: EventService){}
ngOnInit(){
this.evtSvc.childEventListner().subscribe(info =>{
console.log(info); // here you get the message from Child component
})
}
}
Please note that, with #output event, you create a tight coupling of components and so a strong dependency (parent-child-grandchild) is created. If the component is not reusable and is only created to serve this purpose, then #output will also make sense because it'll convey the message to any new developer that they have parent-child relationship.
Creating a service to pass data also exposes the data to other components which can inject service in constructor.
So, the decision should be taken accordingly.

Use rxjs/subject, it can be observer and observable in the same time.
Usage:
Create Subject property in service:
import { Subject } from 'rxjs';
export class AuthService {
loginAccures: Subject<boolean> = new Subject<boolean>();
}
When event happens in child page/component use:
logout() {
this.authService.loginAccures.next(false);
}
And subscribe to subject in parent page/component:
constructor(private authService: AuthService) {
this.authService.loginAccures.subscribe((isLoggedIn: boolean) => {
this.isLoggedIn = isLoggedIn;
})
}

Related

How to pass data from child component to parent component when button clicked on parent component

I need to pass input's value from child component to parent component when user click on a submit button that exists in parent component.
childComp template
<input
type="password"
[(ngModel)]="userPasswordForm.inputId"
class="mr-password-field k-textbox"
/>
childComp TS file
export class PasswordInputComponent{
constructor() { }
#Output() inputValue = new EventEmitter<string>();
userPasswordForm:any={'input':''};
emitValue(value: string) {
this.inputValue.emit(value);
}
}
Parent Component Template
<child-component (inputValue)="" > </child-component>
<button (click)="getValueFromChild()"> </button>
Parent Component TS file
tempUserFormPasswords:any=[];
.
.
.
getValueFromChild(receivedVal){
this.tempUserFormPasswords.push(receivedVal);
}
It would easy to dio it if the button exists inside the child component. but in this case the value should be passed when the button in the parent component is clicked!
For single ChildComponent:
Use ViewChild
For multiple ChildComponent use: ViewChildren
Parent Component TS file
Single Child Component:
tempUserFormPasswords:any=[];
#ViewChild(ChildComponent) child: ChildComponent;
.
.
.
getValueFromChild(receivedVal){
var data = child.getData();
this.tempUserFormPasswords.push(data);
}
Multiple Child Component:
tempUserFormPasswords:any=[];
#ViewChildren(ChildComponent) child: ChildComponent;
#ViewChildren(ChildComponent) children: QueryList<ChildComponent>;
.
.
.
getValueFromChild(receivedVal){
let data;
children.forEach(child => (data = this.updateData(child.data));
this.tempUserFormPasswords.push(data);
}
Create a BehaviorSubject in service file
#Injectable()
export class dataService {
data: BehaviorSubject<any> = new BehaviorSubject<any>(null);
public setData(data: any){
this.data.next(data);
}
public getData(): Observable<any> {
return this.data.asObservable();
}
}
You need to subscribe the data in your child component
PasswordInputComponent
export class PasswordInputComponent{
constructor(private service: dataService) {
this.service.getData().subscribe((data) => {
//Emit the event here
this.inputValue.emit(value);
});
}
#Output() inputValue = new EventEmitter<string>();
userPasswordForm:any={'input':''};
emitValue(value: string) {
this.inputValue.emit(value);
}
}
ParentComponent.ts
tempUserFormPasswords:any=[];
.
.
.
constructor(private service: dataService) { }
getValueFromChild(receivedVal){
this.service.setData('');
this.tempUserFormPasswords.push(receivedVal);
}
When a button clicked on the parent component we are setting the data behaviour subject, when a new value added to that it will automatically subscribed in child component.so, on that time we need to emit a event.
I think this will help you..
Read about Input and Output decorators in angular!
documentation: sharing-data.
Examples: examples
You can do it with ViewChild as already said in the other answer from #Raz Ronen. But keep in mind that depending on the Angular version, you might need to wait for the AfterViewInit lifecycle hook to be executed to interact with the child (or the child won't be available since it's not initialized).
Also, you can do it with a BehaviorSubject, like #Msk Satheesh just answered, and it's perfectly fine too. But it might be considered a bit overkill for such a simple use case.
(this is what we usually do when we don't have a relation between the components e.g one component is not children of the other one)
What I suggest is I think the simplest of all (again, their answers are not bad by any means);
It is basically the same of #Msk Satheesh (but under the hood), just a bit more Angular styled: Output + EventEmitter:
Parent component:
import { Component } from '#angular/core';
#Component({
selector: 'app-parent',
template: `
Message: {{message}}
<app-child (messageEvent)="receiveMessage($event)"></app-child>
`,
styleUrls: ['./parent.component.css']
})
export class ParentComponent {
constructor() { }
message:string;
receiveMessage($event) {
this.message = $event
}
}
Children Component:
import { Component, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-child',
template: `
<button (click)="sendMessage()">Send Message</button>
`,
styleUrls: ['./child.component.css']
})
export class ChildComponent {
message: string = "a string from child component"
#Output() messageEvent = new EventEmitter<string>();
constructor() { }
sendMessage() {
this.messageEvent.emit(this.message)
}
}
With the code, the parent will always be subscribed to the messageEvent that’s outputted by the child component, and it will run the function (the message function) after the child emits. Handling this with Angular has the advantage that we are sure that we don't have any memory leak in our app (e.g missing unsubscriptions).
When the component that is listening (the subscribed parent) gets destroyed, Angular will unsubscribe automatically to avoid potential memory leaks.

Output from child to parent component doesn't work properly

I want to pass data from child to parent component but it doesn't work in a proper way: function isn't invoked.
<router-outlet (activeElement)='getActive($event)'></router-outlet>
<div class="myDIV">
<button *ngFor="let i of numberOfButtons" (click)="navigate(i)" [ngClass]="(i === active) ? 'btn active': 'btn'">{{i}}</button>
</div>
child component ts.file
#Output() activeElement = new EventEmitter();
constructor(private activatedRoute:ActivatedRoute,private getPlanets:GetPlanetsService,private router: Router, private renderer:Renderer2,private elRef: ElementRef) {
activatedRoute.params.subscribe(value=>{
this.fivePlanetIndex=value.id;
this.activeElement.emit(value.id);
});
}
parent component .ts file
getActive(i){
console.log(i); //it is not invoked
}
Output will work by using component selector, not with router-outlet.
Because router-outlet used to render multiple components and it doesn't make sense to set all Inputs and Outputs on there.
If you wanna use router-outlet and catch events from children components, you can use Observables; where child component send result and parent component subscribe to it.
sample.service.ts
// you will use subject to emit event from child
subject = new Subject<any>();
// you will use observable to listen to events from parent
observable = this.subject.asObservable();
child.component.ts
constructor(service: SampleService) {}
// instead of emit function
this.service.subject.next();
parent.component.ts
constructor(service: SampleService) {}
// instead of event listener
this.service.observable.subscribe();

Angular; Sharing data between this components

I'm new to Angular and I have this problem here:
I want to pass the data that I have from this service+component to another component.
I have a service doing this:
getRecs() {
let recsSub = new Subject<any>();
let recsSubObservable = from(recsSub);
this.socket.on('recs', (recsStatus: any) => {
recsSub.next(recsStatus);
});
return recsSubObservable;
}
Then I have this parent component
private misRecs = null;
snackBarShown = false;
constructor (private appSocketIoService: AppSocketIoService, private snackbar: MatSnackBar) {
let recsObservable = this.appSocketIoService.getRecommendations();
recsObservable.subscribe((recsStatus: any) => {
console.log(recsStatus);
this.misRecs = {};
for(let property in recsStatus.output){
if (recsStatus.output[property]) {
this.misRecs[property] = recsStatus.output[property];
}
};
this.snackbar.openFromComponent (CustomSnackBar, { duration: 5000, });
});
}
What I need is to populate a list in another component with the values obtained from recsStatus but I don't know how to do it.
Thank you all for your help.
If the component is a child component of your component (parent) you describe in the listing you can use the #Input() annotation.
#Component({
selector: 'child-comp',
template: `
<div>
{{ localRecStatus | json }}
</div>
`
})
export class ChildComponent {
#Input()
localRecStatus: [];
}
Now you can use the component in HTML file of your parent component like this:
<child-comp [localRecStatus]="recStatus"></child-comp>
With this, you can use recStatus in your child component. However, recStatus must be a public variable of the parent component. With this technique, you can pass any data to child components. There is also an #Output() annotation you can use in combination with an EventEmitter to send data to the parent component. If the component is not a child, probably a better way is to communicate via a Service between both components.

What is the best way pass data to other components

I am trying to pass user info object to all low level component,
the issue is what is the best way to pass it to lover component even if they are grandchildren?
If the #input will work or have anther way to pass it?
my code for root component is:
constructor(private _state: GlobalState,
private _imageLoader: BaImageLoaderService,
private _spinner: BaThemeSpinner, public af: AngularFire) {
this._loadImages();
this._state.subscribe('menu.isCollapsed', (isCollapsed) => {
this.isMenuCollapsed = isCollapsed;
});
// this.af.auth.subscribe(
// user => this._changeState(user),
this.af.auth.subscribe( user => this._changeState(user));
}
Have you considered creating a service class? They're singletons, so the same instance of that class gets injected into each and every component that asks for it.
https://angular.io/docs/ts/latest/guide/dependency-injection.html
A simple one would look like this
import { Injectable } from '#angular/core';
#Injectable()
export class DummyService {
public userInfo: UserInfo = {
//User stuff goes here
};
}
And you would add it to a component like this.
import {DummyService} from 'dummy.service';
#Component({
selector: 'my-component',
templateUrl: 'my.component.html'
})
export class MyComponent{
constructor(private myDummyService: DummyService){}
}
At runtime, this would inject the same instance of the class into every component you inject it into. So it's a super handy way of syncronizing data across multiple components.

Executing a parent function from child doesn't update the parent's properties

In Angular 2 you work a lot with this, which is fine but I've found that it also creates an issue when you want to pass down a function down the component hierarchy.
Take this for example:
export class ParentComponent {
myFunctionFromParent() {
this.isActive = true;
}
}
Then we pass this function down to a child:
<parent>
<child [onClick]="myFunctionFromParent"></child>
</parent>
And let's say child is a simple button:
<button (click)="onClick()"></button>
Now, when myFunctionFromParent runs, this should be the ParentComponent but it's not.
Instead it's the ChildComponent that will have it's this.isActive property changed.
This creates a lot of issues as you can't execute parent functions from a child component and expect the parent properties to change.
Passing down functions works as you would expect them to do in Angular 1, but now it seems broken.
Is this no longer the way to do things like this? What is the correct way to to this in Angular 2?
Instead of passing functions around use default Angular data binding with inputs and outputs:
class ParentComponent {
myFunctionFromParent() {
this.isActive = true;
}
}
class ChildComponent {
#Output() onClick = new EventEmitter();
}
<parent>
<child (onClick)="myFunctionFromParent()"></child>
</parent>
<button (click)="onClick.emit()"></button>
I would use this instead:
<parent>
<child (onClick)="myFunctionFromParent()"></child>
</parent>
and define an #Output in the child component:
#Component({
selector: 'child',
template: `
<button (click)="onClick()"></button>
`
})
export class ChildComponent {
#Output('onClick')
eventHandler:EventEmitter<any> = new EventEmitter();
onClick() {
this.eventHandler.emit();
}
}

Categories