3

How to call main component function from a child component when I have deep nested hierarchy?

enter image description here

I have 3 components, button component I'm including inside browser component and browser component I'm including inside main component.

On click of button I need to call a function which is inside main component.

Button Component

@Component({
    selector: 'cb-button',
    templateUrl: 'cb-button.component.html',
    styleUrls: ['cb-button.component.scss']
})

export class CbButtonComponent {

     @Output() onClick: EventEmitter<any> = new EventEmitter();

     onBtnClick(): void {
        this.onClick.emit();
    }
}

button component html

<div (click)="onBtnClick()">
    <button>btn</button>
</div>

browser component

@Component({
    selector: 'topology-browser',
    templateUrl: 'topology-browser.component.html',
    styleUrls: ['topology-browser.component.scss']
})

export class TopologyBrowserComponent {

   @Input('campus') campus: Campus;
}

browser component html

<div>
<h1>browser title</h1>
<cb-button (click)="editCampus()"></cb-button>
</div>

and finally in main component i'm including browser component

main-component.ts

editCampus() {
  alert('clicked');
}

html

<topology-browser [campus]="campus"></topology-browser>

when I click button I'm getting below error

Errorself.parentView.context.editCampus is not a function

2
  • 2
    You should use a shared service if you want to pass data several layers deep Commented Mar 30, 2017 at 3:07
  • Preferred method is use a shared service and inject it in the child constructor. Duplicate of stackoverflow.com/a/42405146/3103979 Commented Mar 30, 2017 at 3:09

4 Answers 4

3
  • If you know what type the root component is, you can use Pengyy's method.
  • If you don't know what type in advance but you can customize the root component to your needs, a shared service is a good way.

  • A more generic way is to get the root component by injecting ApplicationRef (not actually tested myself):

constructor(app:ApplicationRef, injector: Injector) {
  app.components[0].someMethodOnMainComponent();
}

https://angular.io/docs/ts/latest/api/core/index/ApplicationRef-class.html

Sign up to request clarification or add additional context in comments.

Comments

2

As I know there are two ways for your situation:

  • inject the parent component to child component
  • use EventEmitter

Since you have more than two layers, injecting MainComponent to ButtonComponent is much more simply to implement:

import {Component, Inject, forwardRef} from '@angular/core';

// change the MainComponent to your real component name
constructor(@Inject(forwardRef(() => MainComponent)) private main:MainComponent)

6 Comments

You shouldn't need forwardRef if ButtonComponent and MainComponent are not declared within the same file.
The import you've used is for beta :-)
@echonax oops, sorry for I just copied from an simple test app which been made a long time ago, I'll update for that, thanks.
@GünterZöchbauer did i misunderstood something about circular dependency?, I have been keep this in mind after read this issue.github.com/angular/angular/issues/3216
@Pengyy according to the doc skipping self will resolve the circular dependency with @SkipSelf and you can put an @Optional for a null check. So it would be something like constructor(@Skipself() @Optional() private main:MainComponent) More info on: angular.io/docs/ts/latest/cookbook/…
|
0

You may try the below. I have not tried this but am guessing it might work. The button component emits an event which is being caught by the main component.

You may also pass data via a common service and injecting that service into the child component and calling a service function which updates a service observable which is subscribed by the main component

Your main component

@Component({
  template `
    <button-comp (onClick)="mainMethod($event)"></button-comp>
  `,
  directives: [ ButtonComponent ]
})
export class MainComponent {
  (...)

  mainMethod(event) {
    console.log(event)
  }
}

your button component

@Component({
    selector: 'cb-button',
    templateUrl: 'cb-button.component.html',
    styleUrls: ['cb-button.component.scss']
})

export class CbButtonComponent {

     @Output() onClick: EventEmitter<any> = new EventEmitter();

     onBtnClick(): void {
        this.onClick.emit('hello');
    }
}

button component html

<div (click)="onBtnClick()">
    <button>btn</button>
</div>

Comments

0
export class CbButtonComponent {

     @Output() onClick: EventEmitter<string> = new EventEmitter<string>();

     onBtnClick(): void {
        this.onClick.emit('clicked');
    }
}

In your browser component

HTML

<div>
    <h1>browser title</h1>
    <cb-button (onclick)="buttonclicked($event)"></cb-button>
</div>

Component

@Output() buttonClick: EventEmitter<string> = new EventEmitter<string>();

buttonclicked(clickedEvent){
   this.buttonClick.emit('clicked');
}

In your parent Main component

<topology-browser [campus]="campus" (buttonClick)="buttonComponentClicked($event)"></topology-browser>

Component code

buttonComponentClicked(buttonClicked){
       /// method calls goes her

}

By this way you are notifying the browser component with the click which is captured again in the main component and handled

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.