6

I'm wrestling with the way angular watches arrays. I have the following markup:

<map name="theMap" center="{{mapInfo.center.toUrlValue()}}" zoom="{{mapInfo.zoom}}" on-dragend="dragEnd()" on-zoom_changed="zoomChanged()">
    <marker ng-repeat="pin in pins() track by pin.iconKey()" position="{{pin.latitude}}, {{pin.longitude}}" title="{{pin.streetAddress}}" pinindex="{{$index}}" on-click="click()" 
            icon="{{pin.icon()}}"></marker>
</map>

Each individual pin returned by pins() has a number of properties, sub-properties, etc. One of those sub-properties controls the marker color. When that subproperty changes I want the UI to update.

Because ng-repeat appears to $watch based on simply changes to the collection it's not obvious how to achieve that. I thought that my tracking function, iconKey(), would do it because it returns a different value depending upon the subproperty's value. But that didn't work.

One other thing: the subproperty gets changed in the callback from an $interval that runs under a directive. I mention this because, in an earlier post, someone thought that there might be a context/scope problem.

However, even when I make the change in an event listener within the UI's controller (where the event is raised within the "success" clause of the $interval callback) it still doesn't work.

That's why I think the problem is just angular not noticing the change in iconKey(); it's acting like all it $watches for ng-repeat is the array's length. Which doesn't change when the subproperty changes.

Update

I've created a plunker to demonstrate the issue I'm facing. You can find it at http://plnkr.co/edit/50idft4qaxqw1CduYkOd

It's a cut down version of the app I'm building, but it contains the essential elements (e.g., a data context service to hold information about the map pins and an $interval service to toggle the subproperty of one of the pin array elements).

You start the update cycle by clicking Start in the menu bar (you may want to drag the map down slightly to put both pins into full view). It should toggle the color of each pin, alternatively, 5 times each, once every 2 seconds. It does this by toggling the value of the isDirty property of the pin object in a listener defined in the controller. The event is raised by the $interval service.

If you break on line 22 during the test cycle you'll see the pin's icon being returned. So something within angular is calling for the information...but the pin color doesn't change.

I look forward to someone quickly pointing out the bone-headed mistake that has nothing to do with any of my theories :). Apologies in advance for whatever blinders I'm wearing.

Update 2

After checking out the code snippet in the response I simplified my plnkr and demonstrated that angular is, in fact, updating the UI when a subproperty changes. So this appears to be a limitation or bug in ng-map.

Mark Olbert
  • 6,584
  • 9
  • 35
  • 69
  • Even if your `track by` doesn't update the array, the watchers under such as `pin.icon()` are still active and refreshed on each digest cycle. Your problem is elsewhere, we'd need a jsfiddle or a way to reproduce it to understand it. – floribon May 14 '15 at 23:35
  • Can you show the code to your marker directive? – Joe Enzminger May 15 '15 at 04:35

1 Answers1

5

What you are missing here is the concept of array and function your function pins() passes an array and that array is been bound with ng-repeat. But the brutal fact here is that no matter what that array is never changed, because you do not have ANY reference to that array hence the rg-repeat will always remain as is...

I'll suggest to try get the array be referenced two ways to do that

ng-init="pinsArray = pins()"

and second inside controller

$scope.pinsArray = $scope.pins()

then make changes to $scope.pinsArray inside controller

ng-repeat will be changed to

ng-repeat="pin in pinsArray"

also read about filters I am guessing that's what you where trying to do with "track by"

hope this helps..

Edit: different story with ngMap markers

seems like it doesn't watch sub-property. so here's a work around

add following statement to you update the pinsArray after making changes to its properties.

pinsArray = angular.copy(pinsArray);

the solved plnkr example: http://plnkr.co/edit/EnW1RjE9v47nDpynAZLK?p=preview

Kanak Singhal
  • 3,074
  • 1
  • 19
  • 17
  • I appreciate your quick response, but I don't understand it. I understand that the array is never changed; it's just some of the properties on some of the elements of that array which change. How does ng-init catch that? – Mark Olbert May 15 '15 at 00:09
  • Or you can see it this way - ***the ng-repeat will update itself on update of properties of the array it is been bound to***, but it wont call the function again and again, the function is called only once, i.e it cannot be bound to a function. so what i was trying to suggest, is that you initiate an array by calling pins() [say pinsArray] and bind that array in ng-repeat, then you update the properties of that array [pinsArray] inside controller whenever you want ng-repeat to update. – Kanak Singhal May 15 '15 at 06:27
  • see edited code snippet how lowercase 'a' is changed to upper case on button click – Kanak Singhal May 15 '15 at 06:53
  • I've marked this as the answer, although it didn't solve my problem. Which appears to be related to a bug or limitation in ng-map. Thanx for bearing with me. – Mark Olbert May 17 '15 at 00:25
  • I just saw your plnkr link. m new to ngMaps, seems like a bug to me but anyway I managed to get you the result. check it out. http://plnkr.co/edit/EnW1RjE9v47nDpynAZLK?p=preview – Kanak Singhal May 17 '15 at 02:43
  • Nice. A little expensive for larger marker arrays, but my app generally doesn't have more than 20 or 30 items in the array at any one time. And hopefully ngmap will be updated. Thanx! – Mark Olbert May 17 '15 at 04:25