0

I am new to Angular 2 and I am trying to bind an "onclick" event to a #sitenav object in a parent component/template.

menu.html (Child template)

<md-list-item *ngFor="let entry of entries">
    <button md-menu-item  (click)="sidenav.close()">
      <md-icon>{{entry.icon}}</md-icon>
      <span><a routerLink="{{entry.route}}">{{entry.link}}</a></span>
    </button>
</md-list-item>

menu.ts (Child template component file)

import { Component } from '@angular/core';

@Component({
    moduleId:module.id,
    selector: 'navlinks',
    templateUrl: 'menubtn.component.html'
})
export class MenuBtnComponent {
    entries = [];

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

   close(value:any){
    this.buttonClicked.emit("close");
   }

  constructor() {}

    ngOnInit() {
    this.entries = [
        {
            icon: 'home',
            route: '/',
            link:  'Home'
        },
        {
            icon: 'sentiment_satisfied',
            route: '/about',
            link:  'About Us'
        },
        {
            icon: 'content_paste',
            route: '/work',
            link:  'Out Work'
        },
        {
            icon: 'mail',
            route: '/contact',
            link:  'Get In Touch'
        },
    ];

   }
}

sitenav.ts (parent template component)

import {Component, ViewChild, ViewEncapsulation, Directive} from '@angular/core';
import { MdMenuModule, MdMenuTrigger} from '@angular/material';

import { MenuBtnComponent } from './menubtn.component';


@Component({
  moduleId:module.id,
  encapsulation: ViewEncapsulation.Emulated,
  selector: 'sitenav',
  templateUrl: './sitenav.component.html',
  styleUrls: [ './sitenav.component.css']
})
export class SitenavComponent {
 clickedButton(val){
    console.log(val);
 }
}

sitenav.html (parent template)

 <!--sidenav-->
  <md-sidenav  
    #sidenav 
    class="app-sidenav md2 md-sidenav md-sidenav-side md-sidenav-closed"
    mode="side">
    <md-list flex="" role="list" class="flex">
      <navlinks (buttonClicked)="close($event)"></navlinks>
    </md-list>
  </md-sidenav>
 <!--//sidenav-->

The (click)="sidenav.close()" close #sitenav upon clicking, however it does not seem to be visible from the menu.html template so I am getting the error:

Cannot read property 'close' of undefined

How do I bin close to #sidenav?

HGB
  • 2,157
  • 8
  • 43
  • 74

2 Answers2

1

You cannot access the parent components method from the child directly. Output() parameter is the best way to achieve your requirement.

You code has to be modified as

    import { Component, Output, EventEmitter } from '@angular/core';

@Component({
    moduleId:module.id,
    selector: 'navlinks',
    templateUrl: 'menubtn.component.html'
})
export class MenuBtnComponent {
    @Output() buttonClicked : EventEmitter<string> = new EventEmitter<string>();
    entries = [];

  constructor() {}

    ngOnInit() {
        ....

   }
   close(value:any){
    this.buttonClicked.emit("close");
   }
}

Your HTML markup should be

 <md-list-item *ngFor="let entry of entries">
        <button md-menu-item  (buttonClicked)="close($event)">
      <md-icon>{{entry.icon}}</md-icon>
      <span><a routerLink="{{entry.route}}">{{entry.link}}</a></span>
    </button>
</md-list-item>

You parent component should be as

<md-list-item *ngFor="let entry of entries">
        <button md-menu-item  (buttonClicked)="close($event)">
          <md-icon>{{entry.icon}}</md-icon>
          <span><a routerLink="{{entry.route}}">{{entry.link}}</a></span>
        </button>
</md-list-item>

Parent component ts file should have the below method

 clickedButton(val){
    console.log(val);
 }

Note: The way in which you are accessing the close() method is valid for the reverse case. ChildComponent methods from ParentComponent It is pretty obvious that #sideNav is not related to the child component and how you can access the close() method?

Aravind
  • 40,391
  • 16
  • 91
  • 110
  • HTML markup and parent component are the same in your code? – HGB Feb 19 '17 at 14:37
  • I have updated the code above but nothing happens. No errors. Shouldn't ` – HGB Feb 19 '17 at 14:40
  • And how is it particularly targetting the #sidenav element to close it? – HGB Feb 19 '17 at 14:46
  • Have not used it before, any alternatives? – HGB Feb 19 '17 at 15:22
  • google remote desktop. I can fix your problem remotely – Aravind Feb 19 '17 at 15:22
  • I will be back at my desktop in 30mins. Would you be able to use plunker instead? – HGB Feb 19 '17 at 15:24
  • ok, but your code is concerned about the **navlinks** or? @HGB, come back and notify me. – Aravind Feb 19 '17 at 15:24
  • I am back at my desk and have updated my code with yours above. However when I add `(buttonClicked)="close($event)"` to my child button, nothing happens and when I go back to `(click)="sidenav.close()"` It brings up the error again. I really don't understand why it is so complicated just to target the #sidenav element from the `menu.html` template. – HGB Feb 19 '17 at 16:42
  • are you available in teamviewer? or remote connection? I can fix it in few mins instead of lengthy conv – Aravind Feb 19 '17 at 16:44
  • What's your teamviewer ID? – HGB Feb 19 '17 at 16:58
  • @HGB shared in the chat – Aravind Feb 19 '17 at 17:05
0

One other possible way of doing this is by a Event bus Service

eventbusservice.ts

import { Subject } from 'rxjs/Rx';
import { Observable } from 'rxjs/Observable';

export class EventBusService
{
    bus:Subject<Event> = new Subject<Event>();

    dispatch(data:Event){
        this.bus.next(data);
    }

    listen(type:string):Observable<Event> {
       return this.bus.filter(event=>event.type === type);
    }
}

menu.ts

  import { Component, Output, EventEmitter } from '@angular/core';
  import { EventBusService } from './eventbusservice'
@Component({
    moduleId:module.id,
    selector: 'navlinks',
    templateUrl: 'menubtn.component.html'
})
export class MenuBtnComponent {
    entries = [];

  constructor(private eventbus:EventBusService) {}

    ngOnInit() {
        ....

   }
   close(){
    this.eventbus.dispatch("close");
   }
}

sitenav.ts

import { Component, OnInit } from '@angular/core';
import {EventBusService} from './eventbusservice'

@Component({
    moduleId: module.id,
    selector: 'site-nav',
    templateUrl: 'sitenav.html'
})
export class SiteNavComponent implements OnInit {
    constructor(private eventbus:EventBusService) {
this.eventbus.listen("close").subscribe((e)=>{this.closeSomething()})

     }
closeSomething(){

}
    ngOnInit() { }
}

menu.html

<md-list-item *ngFor="let entry of entries">
    <button md-menu-item  (click)="close()">
      <md-icon>{{entry.icon}}</md-icon>
      <span><a routerLink="{{entry.route}}">{{entry.link}}</a></span>
    </button>
</md-list-item>

This process helps when change in one component has impact on many other components(not just parent component).

  • Interesting. Is there a reason to not use a Bus Service? I have just started with Angular 2 and want to keep as close to the standards as possible, can eventbusservice.ts be imported every time you need to listen for an event on any component? – HGB Feb 19 '17 at 15:59
  • https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service – Sai Krishna Tej Feb 19 '17 at 16:31
  • Thanks Sai, but would you be able to tell what's wrong with my method above using the EventEmitter option? – HGB Feb 20 '17 at 11:37