I have a collection of 500 items (persons) that I am rendering in an Angular ng-repeat directive. Each item has three fields (FirstName, LastName, Company). I want the user to be able to see details / edit the items below each rendered row. I have a button (Font Awesome square-plus) that when clicked needs to show the item details / edit. I do not want to include this markup/logic within the controller because having it rendered but hidden is very slow...multiple seconds in Chrome. I assume this is due to all the watches.
To resolve the issue, I created a directive that injects the details / edit items under the current record at run-time. I attempt to $compile the markup to link it to the current scope of the ng-repeat row.
Problems.. I think I am having issues with the scope. The directive is references within the ng-repeat block (p in Persons), so I would think I would be passed the record scope in the directive link function. But I can only get the record object by getting the parent scope (scope.$parent.p instead of scope.p). I don't understand.
Also, once the $compile function is executed, I do see the person info displayed in the new details blocks. But changes are not reflected in the current record data, nor an I dismiss the details block.
Any suggestions?
Markup:
<div class="row" ng-repeat="p in Persons">
<div class="col-lg-1">
<i class="fa fa-plus-square" ng-show="!p.showDetail" manage-details></i>
</div>
<div class="col-lg-2">{{::p.FirstName}}</div>
<div class="col-lg-2">{{::p.LastName}}</div>
<div class="col-lg-2">{{::p.Company}}</div>
<div id="marker{{$index}}"></div>
<hr ng-if="!$last" />
</div>
JS:
(function () {
'use strict';
angular
.module('ngRepeatMystery', [])
.controller('TestController', TestController)
.directive('manageDetails', manageDetails);
TestController.$inject = ['$scope'];
function TestController($scope) {
$scope.Persons = [
{ 'FirstName': 'Joe', 'LastName': 'Delbridge', 'Company': 'Dow', 'showDetail': false },
{ 'FirstName': 'Tony', 'LastName': 'Ingram', 'Company': 'Samtec', 'showDetail': false },
{ 'FirstName': 'Mike', 'LastName': 'Smolinski', 'Company': 'HCHB', 'showDetail': false },
{ 'FirstName': 'Lee', 'LastName': 'Shipman', 'Company': 'Cummins', 'showDetail': false },
{ 'FirstName': 'Eric', 'LastName': 'ONeal', 'Company': 'MSD', 'showDetail': false },
];
$scope.DismissDetails = function (index) {
$scope.Persons[index].showDetail = false;
var wrappedMonkey = angular.element($document[0].getElementById('details' + index));
angular.element(wrappedMonkey).hide();
}
};
manageDetails.$inject = ['$compile', '$document', '$timeout'];
function manageDetails($compile, $document, $timeout) {
return {
restrict: 'A',
scope: {},
link: function (scope, element, attrs) {
element.bind('click', function () {
// scope.$parent !!? WAT!
scope.$parent.p.showDetail = !scope.$parent.p.showDetail;
if (scope.$parent.p.showDetail) {
var index = scope.$parent.$index;
var wrappedMarker = angular.element($document[0].getElementById('marker' + index));
var details = getDetailsTemplate(index);
wrappedMarker.replaceWith(details);
var wrappedDetails = angular.element($document[0].getElementById('details' + index));
$compile(wrappedDetails.contents())(scope.$parent);
};
});
}
};
function getDetailsTemplate(index) {
var detailsTemplate =
"<div id=\"details" + index + "\" style=\"padding: 20px;\">" +
"<div class=\"row\">" +
"<div class=\"col-lg-2\"></div>" +
"<div class=\"col-lg-8\">" +
"<label>Last Name</label>" +
"<input ng-model=\"p.LastName\" placeholder=\"Last Name\"><br/>" +
"<label>First Name</label>" +
"<input ng-model=\"p.FirstName\" placeholder=\"First Name\"><br/>" +
"<label>Company</label>" +
"<input ng-model=\"p.Company\" placeholder=\"Company\"><br/>" +
"<button class=\"btn btn-primary\" ng-click=\"p.DismissDetails($index);\">Done</button><br/>" +
"</div>" +
"<div class=\"col-lg-2\"></div>" +
"</div>" +
"</div>";
return detailsTemplate;
}
};
})()
Plunker: http://plnkr.co/edit/64TcuhaNi2JcC1hzon15
I am also open to other alternatives...
Ok, I think there are a lot of issues with your code.
I would advise against having the directive modify something outside of itself.
As I commented earlier, don't use $parent. Just pass data as attributes. And create a new scope when you call $compile to avoid polluting the existing scope.
I modified your code so it works, but it's still not pretty:
http://plnkr.co/edit/eLNxewwFzobqTkQ4867n
HTML:
<div class="row" ng-repeat="p in Persons">
<div class="col-lg-1">
<i class="fa fa-plus-square" ng-show="!showDetail" manage-details monkey="p" index="$index" show-detail="showDetail"></i>
</div>
<div class="col-lg-2">{{p.FirstName}}</div>
<div class="col-lg-2">{{p.LastName}}</div>
<div class="col-lg-2">{{p.Company}}</div>
<div id="marker{{$index}}"></div>
<hr ng-if="!$last" />
</div>
JS:
return {
restrict: 'A',
scope: {
monkey: '=',
index: '=',
showDetail: '='
},
link: function (scope, element, attrs) {
var childScope;
element.bind('click', function () {
scope.showDetail = !scope.showDetail;
if (scope.showDetail) {
childScope && childScope.$destroy();
childScope = scope.$new();
var index = scope.index;
var wrappedMarker = angular.element($document[0].getElementById('marker' + index));
wrappedMarker.html(getDetailsTemplate(index));
childScope.p = angular.copy(scope.monkey);
childScope.dismissDetails = function () {
scope.showDetail = false;
scope.monkey = angular.copy(childScope.p);
wrappedMarker.html('');
};
$compile(wrappedMarker.contents())(childScope);
};
});
}
};
function getDetailsTemplate(index) {
var detailsTemplate =
"<div id=\"details" + index + "\" style=\"padding: 20px;\">" +
"<div class=\"row\">" +
"<div class=\"col-lg-2\"></div>" +
"<div class=\"col-lg-8\">" +
"<label>Last Name</label>" +
"<input ng-model=\"p.LastName\" placeholder=\"Last Name\"><br/>" +
"<label>First Name</label>" +
"<input ng-model=\"p.FirstName\" placeholder=\"First Name\"><br/>" +
"<label>Company</label>" +
"<input ng-model=\"p.Company\" placeholder=\"Company\"><br/>" +
"<button class=\"btn btn-primary\" ng-click=\"dismissDetails();\">Done</button><br/>" +
"</div>" +
"<div class=\"col-lg-2\"></div>" +
"</div>" +
"</div>";
return detailsTemplate;
}