0

Sample controller data:

$scope.items = [{ id: 1, name: 'First'}, { id: 2, name: 'Second'}];

Is there something in angular to make the following code work like "with variable"?

<ul>
    <li data-ng-repeat="items">{{id}} {{name}}</li>
</ul>

Instead of:

<ul>
    <li data-ng-repeat="i in items">{{i.id}} {{i.name}}</li>
</ul>

Please feel free to make a more understandable title/question.

gremo
  • 47,186
  • 75
  • 257
  • 421
  • You can actually write a custom directive for that. – Shashank Agrawal Feb 14 '15 at 12:36
  • I would attempt a full answer if I were less tired but I think what you'd need to do is create a directive that uses `transclude: 'element'` and that calls `$scope.$watchCollection`. `ng-repeat` does these things. – Andrew Magee Feb 14 '15 at 13:06

3 Answers3

0

Referring to Angular ngRepeat document, currently only followings expressions are supported.

  1. variable in expression
  2. (key, value) in expression
  3. variable in expression track by tracking_expression
  4. variable in expression as alias_expression

This means, you can't simply use ng-repeat="items" to iterate the collection.

BTW, ng-repeat will create a separate scope for each element and bind variable or (key, value) to the newly created scope. So "with variable" you refer to is not Angular built-in. You need to create a customized directive for this functionality.

Rebornix
  • 5,272
  • 1
  • 23
  • 26
0

My preferred answer would be "don't do this" but failing that, and because it's interesting, here's a proof of concept, assisted by this question and mostly adapted from this blog post:

app.directive('myRepeat', function(){
  return {
    transclude : 'element',
    compile : function(element, attrs, linker){
      return function($scope, $element, $attr){
        var collectionExpr = attrs.myRepeat;
        var parent = $element.parent();
        var elements = [];

        // $watchCollection is called everytime the collection is modified
        $scope.$watchCollection(collectionExpr, function(collection) {
          var i, block, childScope;

          // check if elements have already been rendered
          if(elements.length > 0){
            // if so remove them from DOM, and destroy their scope
            for (i = 0; i < elements.length; i++) {
              elements[i].el.remove();
              elements[i].scope.$destroy();
            };
            elements = [];
          }

          for (i = 0; i < collection.length; i++) {
            // create a new scope for every element in the collection.
            childScope = $scope.$new();

            // ***
            // This is the bit that makes it behave like a `with` 
            // statement -- we assign the item's attributes to the
            // child scope one by one, rather than simply adding
            // the item itself.
            angular.forEach(collection[i], function(v, k) {
              childScope[k] = v;
            });
            // ***

            linker(childScope, function(clone){
              // clone the transcluded element, passing in the new scope.
              parent.append(clone); // add to DOM
              block = {};
              block.el = clone;
              block.scope = childScope;
              elements.push(block);
            });
          };
        });
      }
    }
  }
});

And then this will do what you want:

app.controller("myController", function($scope, $http) {
  $scope.items = [
    {a: 123, b: 234},
    {a: 321, b: 432}
  ];
});

With the HTML structure you want:

<div ng-controller="myController">
  <ul>
    <li my-repeat="items">
      {{ a }} {{ b }}
    </li>
  </ul>
</div>

Notice that given the attributes are copied into the child scopes, rather than referenced, if changes are made to the view, they won't affect the model (ie. the parent items list), severely limiting the usefulness of this directive. You could hack around this with an extra scope.$watch but it'd almost certainly be less fuss to use ng-repeat as it's normally used.

Community
  • 1
  • 1
Andrew Magee
  • 6,506
  • 4
  • 35
  • 58
0

I can't see why other users are telling you that what you want has to be done via a new directive. This is a working snippet.

angular.module("Snippet",[]).controller("List",["$scope",function($scope){
    $scope.items = [{ id: 1, name: 'First'}, { id: 2, name: 'Second'}];
  }]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>

<body ng-app="Snippet" ng-controller="List as list">

  <ul>
    <!-- Iterating the array -->
    <li ng-repeat="item in items">
      <!-- Iterating each object of the array -->
      <span ng-repeat="(key,value) in item">{{value}} </span>
    </li>
  </ul>
  
</body>

Simply, you need to iterate the elements of the array via ng-repeat, then you can do what you want with the object item retrieved. If you want to show its values, for example, as it seems for your question, then a new ng-repeat gets the job done.

Alessandro Flati
  • 322
  • 5
  • 12