1

I have a search-results component composed by a list of items and a map. On the map component I have markers, and I want that when the user clicks one of the markers, the corresponding element in the list components gets animated.

Therefore, I have an @output and an event emitter on the map component that emits the id of the item clicked to the parent, and the parent sends this id to its child "list" component in order to to do its stuff. This input is a primitive type, a number.

The problem is that even though the property is changing in the parent when the event takes place, the input doesnt change in the child:

search-results.component.html

<div class="container">
  <div class="left-side">
    <app-search-addreses-form></app-search-addreses-form>
    <app-results-list [travelToAnimate]="travelToAnimate" [travels$]="travels$"></app-results-list>
  </div>
  <div class="right-side">
    <app-results-map
      (propagateTravelId)="handleClickedMarker($event)"
      [userOrigin]="userOrigin"
      [userDestination]="userDestination"
      [travels$]="travels$"
    ></app-results-map>
  </div>
</div>

search-results.component.ts

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { Travel } from 'src/app/core/interfaces/travel';
import { TravelsService } from 'src/app/core/services/travels.service';
import { ActivatedRoute } from '@angular/router';
import { GeoPosition } from 'src/app/core/interfaces/travel-payload';

@Component({
  selector: 'app-search-results',
  templateUrl: './search-results.component.html',
  styleUrls: ['./search-results.component.scss'],
})
export class SearchResultsComponent implements OnInit {
  travels$: Observable<Travel[]>;
  userOrigin: GeoPosition;
  userDestination: GeoPosition;
  travelToAnimate: number;
  constructor(private travelsService: TravelsService, private route: ActivatedRoute) {}

  ngOnInit(): void {
    this.route.queryParams.subscribe((params) => {
      this.userOrigin = {
        address: '',
        latitude: params.origin_latitude,
        longitude: params.origin_longitude,
      };
      this.userDestination = {
        address: '',
        latitude: params.destination_latitude,
        longitude: params.destination_longitude,
      };
      this.travels$ = this.travelsService.getTravelsNearOfDestination(
        params.destination_latitude,
        params.destination_longitude
      );
    });
  }
  handleClickedMarker(id: number): void {
    this.travelToAnimate = id;
    console.log('doing click', this.travelToAnimate); // "doing click 421" it changes when a marker is clicked
  }
}
import { Component, Input, OnChanges } from '@angular/core';
import { Observable } from 'rxjs';
import { Travel } from 'src/app/core/interfaces/travel';

@Component({
  selector: 'app-results-list',
  templateUrl: './results-list.component.html',
  styleUrls: ['./results-list.component.scss'],
})
export class ResultsListComponent implements OnChanges {
  @Input() travels$: Observable<Travel[]>;
  @Input() travelToAnimate: number;
  constructor() {}

  ngOnChanges(): void {
    console.log('change detected: ', this.travelToAnimate); // "change detected :undefined", fired only once
  }
}
Ernesto G
  • 525
  • 6
  • 20
  • 1
    weird, everything looks good 2 me. have you tried with setter and getter for travelToAnimate ? just curious if angular's change detection for some reason not being triggered – ihor.eth Nov 26 '20 at 01:22
  • I didn't know about the getter/setter way, I searched for it, found and implemented the solution as explained here: https://stackoverflow.com/questions/38571812/how-to-detect-when-an-input-value-changes-in-angular, but it behaves the same. I also have tried with ngDoCheck with no luck. I wonder if the fact that the event comes from a Leaflet map have something to do with it, but I reckon that once I can "see" the change in the parents property it should fire the change in the child that contains the @input... – Ernesto G Nov 26 '20 at 23:11

2 Answers2

1

In your search-results.component.html, you are capturing the @output event (propagateTravelId) with a function that receives an $event as an argument:

(propagateTravelId)="handleClickedMarker($event)"

To capture the data contained in the $event object (when using @Output you are firing an event through EventEmitter, not a string), you can access event.target.value:

handleClickedMarker(event): void {
    this.travelToAnimate = event.target.value;
    console.log('doing click', this.travelToAnimate); // your recently clicked travel id
}

You can read more on the event binding in Angular documentation.

Santironhacker
  • 722
  • 6
  • 11
  • It doesn't work. event.target.value raises a "can't read property value from undefined". If I do console.log of the event that I get as parameter of handleClickMarker, it is a number with the id. The problem is that the child component is not detecting the change even though the value of the property in the parent is changing as I can see. – Ernesto G Nov 26 '20 at 22:15
0

Just in case is helpful for someone in the future, this behavior actually happened because of how ngx-leaflet manage the events and change detection. It confused me the fact that the changes were being propagated correctly to the parent.

It works by following the recommendations here: https://github.com/Asymmetrik/ngx-leaflet#running-inside-of-angulars-zone:

 addDestinationMarker(lat, long, address, id): Marker {
    return new Marker([lat, long])
      .setIcon(
        icon({
          iconSize: [25, 41],
          iconAnchor: [13, 41],
          iconUrl: 'assets/marker-icon.png',
        })
      )
      .bindPopup(`<h3>Salida desde: ${address}</h3>`)
      .on('click', () => {
        this.zone.run(() => this.propagateTravelId.emit(id));
      });
  }
Ernesto G
  • 525
  • 6
  • 20