0

For Angular 9, I have a parent component in which I want to pass a function to a child component. I want to pass the "onNavigate" function to the child component ...

import { Router, ActivatedRoute } from '@angular/router';
...
export class StatsComponent implements OnInit {
    
  constructor(
    private router: Router,
    private route: ActivatedRoute,
    private apiService: ApiService
  ) { }

    ...

  onNavigate(event): void {
    const id = event.target.value;
    if(parseInt(id) !== this.itemId) {
      const url = `/stats/${this.itemId}`;
      this.router.navigateByUrl(url);
    }
  }

The parent component looks like this ...

<form id="statForm" method="get">
    <app-item-menu [default_selected]="itemId"
                        [value_change_callback]="onNavigate"></app-item-menu>
</form>

The child component looks pretty simple ...

<select id="item" (change)="valueChangeCallback($event)">
    <option *ngFor="let item of items"
        [selected]="item.id === default_selected">{{item.path}}</option>
</select>

But when I change an item in the menu, I get a

ERROR TypeError: this.router is undefined

in the "onNavigate" method. How else do I need to be defining my router?

satish
  • 703
  • 5
  • 23
  • 52
  • Yeah this is problematic.. probably not a good idea to pass functions around this way. You are loosing the ‘this’ scope.. and I can’t really see a way in your setup to re-bind this to the correct scope. Better to rethink this. – MikeOne Jan 23 '21 at 22:01

2 Answers2

0

You need to mark a property on the child component with the @Output() decorator, this allows you to send data from child to parent or just react to that event in any way. Add the property to you app-item-menu component

@Output() idChanged = new EventEmitter<string>();

then the child component (app-item-menu) needs to emit this event, you can do this in your change method for example.

public changeId($event: any): void {
  this.idChanged.next($event.id); // no idea where the id is, but I hope you get it.
}
HTML:
<select id="item" (change)="changeId($event)">
    <option *ngFor="let item of items"
        [selected]="item.id === default_selected">{{item.path}}</option>
</select>

In the parent component you can bind a method to the above event.

<form id="statForm" method="get">
    <app-item-menu [default_selected]="itemId"
                        (idChanged)="onNavigate($event)"></app-item-menu>
</form>

If you need more information, read the docs

sombrerogalaxy
  • 366
  • 2
  • 6
  • Thanks. I implemented everything as you have it, but the changeId function is never called when I select a different option from the dropdown. Is "(ngModelChange)" the right way to set that output on the child component? – satish Jan 24 '21 at 00:41
  • There [are different ways to do it](https://stackoverflow.com/a/33716321/5171325). You can change it back to ` – sombrerogalaxy Jan 24 '21 at 14:59
0

You should really read the angular guide at angular.io, but just to get you going as close to your implementation as possible.

The parent component:

<form id="statForm" method="get">
    <app-item-menu [default_selected]="itemId"
                        (valueChangeCallback)="onNavigate($event)"></app-item-menu>
</form>

The child component:

<select id="item" (change)="valueChangeCallback.emit($event)">
    <option *ngFor="let item of items"
        [selected]="item.id === default_selected">{{item.path}}</option>
</select>

And make your valueChangeCallback an output property, instead of an input:

//...
Output()
valueChangeCallback: EventEmitter<any> = new EventEmitter();
joeveiga
  • 78
  • 1
  • 5