Search code examples
javascriptangulartypescriptagm-map

Angular8 trackby not updating correctly


So firstly a little background. I am developing an application that is using agm maps to display in realtime the location of multiple users. I have recently been working on improving performance as there can be thousands of users each representing a map marker or dom element. I have changed the parent and child component to both use the onpush strategy, the child component renders the map markers using an ngFor loop, i have also added trackBy to this loop. Location updates are received via signalr, the object is updated correctly but even after change detection has ran the markers position is not being updated correctly on the map.

The two components below are cut down versions to help identify the problem.

Firstly the Parent Map component

     @Component({
      selector: 'app-map',
      template: '<agm-map>
                    <app-map-markers #mapMarkers [users]="users"></app-map-markers>
                </agm-map>',
      changeDetection: ChangeDetectionStrategy.OnPush
    })
    export class MapComponent implements OnInit, OnDestroy {
        users: MapUser[];

        private handleLocationUpdate(updatedUser: any) {
           const user = this.users.find(this.findIndexToUpdate, updatedUser.Id);

           if (user !== null && user !== undefined) {
              user.lat = updatedUser.Lat;
              user.lng = updatedUser.Lng;
              user.lastSeen = updatedUser.LastSeen;

              const userIndex = this.handledUsers.indexOf(user);
              this.handledUsers[userIndex] = user;
           }
        }

        findIndexToUpdate(newItem) {
           return newItem.id === this;
        }
    }

The users array is populated via subscibing to a http service but i left that out as it works fine. When a location update is received it is handled by the parent, ive left the function to show this, but again the function is called by subscribing to a signalr service.

Now the map markers component.

 @Component({
  selector: 'app-map-markers',
  template: '<agm-marker *ngFor="let user of users; let i = index; trackBy: trackByUser"  [latitude]="user.lat" [longitude]="user.lng" [title]="user.firstName">
             </agm-marker>',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class MapMarkersComponent {
    @Input() users: MapUser[];

    constructor() {}

    public trackByUser(indx: number, value: MapUser) {
        let identifier: string = indx.toString();

        if ('lat' in value && 'lng' in value) {
          identifier = identifier.concat(value.lastSeen.toString());
        }

        return identifier;
      }
}

I suspect the issue is that due to onpush its not detecting the changes, however for performance reasons onpush seems to be the best strategy in this case. I only need to update on element rather than redrawing the whole list of users. But maybe im getting my wires crossed as im still fairly new to angular. Thank you for any help or suggestion.


Solution

  • Try calling the Change Detection 'markForCheck()' which will make angular to run change detection in next cycle.

    @Component({
      selector: 'app-map-markers',
      template: '<agm-marker *ngFor="let user of users; let i = index; trackBy: trackByUser"  [latitude]="user.lat" [longitude]="user.lng" [title]="user.firstName">
                 </agm-marker>',
      changeDetection: ChangeDetectionStrategy.OnPush
    })
    export class MapMarkersComponent {
        public users: MapUser[]
        @Input() 
        set users(values: any[]){
          this.users = values;
          this.cdr.markForCheck();
        }
    
        constructor(private cdr: changeDetectorRef) {}
    
        public trackByUser(indx: number, value: MapUser) {
            let identifier: string = indx.toString();
    
            if ('lat' in value && 'lng' in value) {
              identifier = identifier.concat(value.lastSeen.toString());
            }
    
            return identifier;
          }
    }