7

I'm trying to use JavaScript Maps in ng-repeat angular's directive while searching I found that it could be done this way :

<ul ng-controller="MyCtrl">
    <li ng-repeat='(key, value) in {"First Name":"John", "Last Name":"Smith"}'>
        <span>Key: {{key}}, value: {{value}}</span>
    </li>
</ul>

but this works only with normal JSON objects, when I create a real Map the following way, it doesn't work.

I have this controller for example

function MyCtrl($scope) {
    $scope.items = new map();
    $scope.items.set('adam',{'age':10,'fav':'doogie'});
    $scope.items.set('amalie',{'age':12});
}

and this html code

<ul>
    <li ng-repeat='(key, value) in items'>
        <span>Key: {{key}}, value: {{value}}</span>
    </li>
</ul>
1

3 Answers 3

11

since ng-repeat not support Map iteration, you could use a custom filter fromMap like below

angular.module('app', []).filter('fromMap', function() {
  return function(input) {
    var out = {};
    input.forEach((v, k) => out[k] = v);
    return out;
  };
}).controller('ctrl',function ($scope) {
    $scope.items = new Map();
    $scope.items.set('adam',{'age':10,'fav':'doogie'});
    $scope.items.set('amalie',{'age':12});
    
    $scope.logAdamAge = function() { console.log($scope.items.get("adam").age) };
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
  <div>with Map</div>
  <ul>
    <li ng-repeat="(key, value) in items | fromMap">
      <div>{{key}}</div>
      <div ng-repeat="(k, v) in value">{{k}}: <input ng-model="value[k]" /></div>
    </li>
  </ul>
  <pre>{{items | fromMap}}</pre>
  <button ng-click="logAdamAge()">log Adam age from Map</button>
</div>

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

1 Comment

Great implementation. This is the best way, as you will keep the data updated on every digest cycle
4

this won't work because a javascript Map is an iterable object (you can loop over only through the entries iterator, calling a next() each time) and is not directly supported by angular ng-repeat.

what you can do if you have a pre-existent code is to transform the iterable object (map) to a plain array

$scope.itemsToArray = Array.from($scope.items.entries()); 

this will make your iterable object an array , at this point you can loop over in the standard way

<ul>  
  <li ng-repeat='item in itemsToArray'>
     <span>Key: {{item[0]}}, value: {{item[1]}}</span>
  </li>
</ul>

working plnkr here

Comments

1

Although I like Jag's answer, I've built a more extensive snippet with different implementations to achieve the same, and I took out the most of ES6 arrow functions and spread operator to implement the filter as a one-liner: .filter('fromMap', () => mapObject => [...mapObject])

One setting a watcher and even unsuscribing to that watcher.

Here is how you can implement ng-repeat on Map class objects:

angular.module('ui.bootstrap.demo', ['ngAnimate', 'ngSanitize', 'ui.bootstrap']);
angular.module('ui.bootstrap.demo')
  .filter('fromMap', () => mapObject => [...mapObject])
  .controller('myCtrl', function($scope, $log) {
    $scope.myMap = new Map([
      ['key1', 'value1'],
      ['key2', 'value2'],
      ['key3', 'value3'],
      ['key4', 'value4'],
    ]);
    const unsuscribeWatcher1 = $scope.$watch(() => $scope.myMap.size, (newVal, oldVal) => {
      if (newVal !== oldVal) {
        //Splicing and pushing keeps the reference to the Array instead of reassigning.
        $scope.myLoopedMap.splice(0);
        $scope.myLoopedMap.push(...[...$scope.mySet].filter(k => $scope.myMap.has(k)).map(k => $scope.myMap.get(k)))
        $scope.myArrFromMap.splice(0);
        $scope.myArrFromMap.push(...[...$scope.myMap])
      }
    })
    const unsuscribeWatcher2 = $scope.$watch(() => $scope.mySet.size, (newVal, oldVal) => {
      if (newVal !== oldVal) {
        $scope.myLoopedMap.splice(0);
        $scope.myLoopedMap.push([...$scope.mySet].filter(k => $scope.myMap.has(k)).map(k => $scope.myMap.get(k)));
      }
    })
    $scope.mySet = new Set(['key1', 'key3']);
    $scope.mySet.add('not in the Map')
    $scope.mySet.add('key7')
    $scope.myKeys = ['key1', 'key3'];
    $scope.myArr = [
      ['key1', 'value1'],
      ['key2', 'value2'],
      ['key3', 'value3'],
      ['key4', 'value4'],
    ];
    $scope.myArrFromMap = [...$scope.myMap]
    $scope.myLoopedMap = [...$scope.mySet].filter(k => $scope.myMap.has(k)).map(k => $scope.myMap.get(k))
    $scope.myLoopedMap2 = [...$scope.myKeys].reduce((acc, k) => {
      return $scope.myMap.has(k) ? acc.concat($scope.myMap.get(k)) : acc;
    }, [])

    // added to the map later
    setTimeout(() => {
      $scope.$apply(() => {
        // this will be updated as we are watching the Map
        $scope.myMap.set('key7', 'value7')
      })
      setTimeout(() => {
        unsuscribeWatcher1(); // this breaks the watching of the Map
        $scope.$apply(() => {
          // this will not be updated as the watcher is not working anymore
          $scope.myMap.set('key8', 'value8')
        })
      }, 3000)
    }, 3000)


  });
<!doctype html>
<html ng-app="ui.bootstrap.demo">

<head>
  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.js"></script>
  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular-animate.js"></script>
  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular-sanitize.js"></script>
  <script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-2.5.0.js"></script>
  <script src="example.js"></script>
</head>

<body>

  <div ng-controller="myCtrl">
    <h3>Some test data data</h3>
    <div>Get 'key1'======> {{myMap.get('key1')}}</div>
    <div>Arr ==========> {{myArr}}</div>
    <div>filtered ======> {{myMap | fromMap}}</div>
    <div>myArrFromMap=> {{myArrFromMap}}</div>
    <div>myLoopedMap==> {{myLoopedMap}}</div>
    <div>myLoopedMap2=> {{myLoopedMap2}}</div>

    <h3>ng repeat in myLoopedMap</h3>
    <div ng-repeat="item in myLoopedMap">
      Item:`{{item}}`
    </div>
 
    
    <h3>ng repeat in myArrFromMap</h3>
    <div ng-repeat="(index, value) in myArrFromMap">
      value[0]:`{{value[0]}}`, value[1]:`{{value[1]}}`, index: `{{index}}`
    </div>
    
  </div>
</body>

</html>

And the corresponding Plnkr

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.