During experiments with angular, a strange problem with transclude was discovered. It feels like the item that will be included in the directive through the transkey is cached somewhere.

In my example, it turns out that the input element, which is included in a certain way in the span element, when using ng-repeat is always the same. The result is that it moves down the list each time it is turned on.

example code and it is on plunker

 // Code goes here angular.module('app', []) .controller('Ctrl', function($scope) { $scope.items = [{ id: 1 }, { id: 2 }, { id: 3 }, ]; $scope.$on('remove', function(event, item) { var index = $scope.items.indexOf(item); $scope.items.splice(index, 1); }); }) .directive('closable', function(){ return { restrict: 'E', require: 'ngModel', transclude: true, replace: true, template: '<span> <a ng-click="close()">x</a></span>', link: function(scope, elem, attrs, ngModel, transcludeFn){ scope.close = function(){ scope.$emit('remove', ngModel.$modelValue); } var transclusion = transcludeFn(scope); console.log(transclusion, elem.find('span')); elem.prepend(transclusion); } } }) .directive('test', function() { return { restrict: 'E', require: 'ngModel', template: '<closable ng-model="item"><input type="text" ng-model="id"></closable>', scope: {}, link: function(scope, elem, attrs, ngModel) { ngModel.$formatters.push(function(modelValue) { console.log('$formatters', modelValue); return { id: modelValue.id + 1 }; }); ngModel.$parsers.push(function(viewValue) { ngModel.$modelValue.id = viewValue.id - 1; console.log('$parsers', ngModel.$modelValue, viewValue); return ngModel.$modelValue; }); ngModel.$render = function() { scope.id = ngModel.$viewValue.id; scope.item = ngModel.$modelValue; } scope.$watch('id', function() { ngModel.$setViewValue({ id: scope.id }); }); } }; }) 
 <!DOCTYPE html> <html ng-app="app"> <head> <link data-require="bootstrap@3.3.2" data-semver="3.3.2" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" /> <script data-require="angular.js@*" data-semver="1.3.15" src="https://code.angularjs.org/1.3.15/angular.js"></script> <link rel="stylesheet" href="style.css" /> <script src="script.js"></script> </head> <body ng-controller="Ctrl"> <pre>{{ items|json }}</pre> <ul class="list-unstyled"> <li ng-repeat="item in items"> <test ng-model="item"></test> </li> </ul> </body> </html> 

    2 answers 2

    Yes, interesting behavior, and I think you are right - the input is the same. For the solution I can offer two options: 1) change the code in the link function

     transcludeFn(scope, function(clone){ elem.prepend(clone); }); 

    2) You can add the ngTransclude directive to the template, i.e. template: <span><span ng-transclude></span><a ng-click="close()">x</a></span>

    • ng-transclude helps, though, so that everything works, you need to change ng-model="id" to ng-model="$parent.id" in input; oddly enough, this method with an empty function also worked :) transcludeFn(scope, function(){}); - beh
     template: '<span> <a ng-click="close()">x</a></span>', var transclusion = transcludeFn(scope); console.log(transclusion, elem.find('span')); elem.prepend(transclusion); 

    And why didn’t please the normal version without any tricks with manual addition?

     template: '<span><span ng-transclude></span><a ng-click="close()">x</a></span>',