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
  }
}
2
  • 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 Commented Nov 26, 2020 at 1:22
  • I didn't know about the getter/setter way, I searched for it, found and implemented the solution as explained here: stackoverflow.com/questions/38571812/…, 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... Commented Nov 26, 2020 at 23:11

2 Answers 2

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.

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

1 Comment

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.
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));
      });
  }

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.