如何在 Angular 指令中使用 $compile?
How to use $compile inside an Angular directive?
我在 Angular ng-repeat 指令中呈现了 500 个项目(人)的集合。每个项目都有三个字段(名字、姓氏、公司)。我希望用户能够查看详细信息/编辑每个呈现行下方的项目。我有一个按钮(Font Awesome square-plus),单击该按钮需要显示项目详细信息/编辑。我不想将此 markup/logic 包含在控制器中,因为呈现但隐藏它非常慢...... Chrome 中的多秒。我想这是因为所有的手表。
为了解决这个问题,我创建了一个指令,在 运行 时将详细信息/编辑项注入当前记录下。我尝试将标记 $compile 到 link 它到 ng-repeat 行的当前范围。
问题..我想我的范围有问题。该指令是 ng-repeat 块(Persons 中的 p)中的引用,因此我认为我会在指令 link 函数中传递记录范围。但是我只能通过获取父范围(scope.$parent.p 而不是 scope.p)来获取记录对象。没看懂。
此外,执行 $compile 函数后,我确实在新的详细信息块中看到了人员信息。但是更改不会反映在当前记录数据中,也不会反映在详细信息块中。
有什么建议吗?
标记:
<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:
(函数(){
'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;
}
};
})()
笨蛋:http://plnkr.co/edit/64TcuhaNi2JcC1hzon15
我也对其他选择持开放态度...
好的,我认为你的代码有很多问题。
我建议不要让指令修改自身以外的东西。
正如我之前评论的那样,不要使用 $parent。只需将数据作为属性传递。并在调用 $compile 时创建一个新范围以避免污染现有范围。
我修改了你的代码使其可以工作,但它仍然不够漂亮:
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;
}
我在 Angular ng-repeat 指令中呈现了 500 个项目(人)的集合。每个项目都有三个字段(名字、姓氏、公司)。我希望用户能够查看详细信息/编辑每个呈现行下方的项目。我有一个按钮(Font Awesome square-plus),单击该按钮需要显示项目详细信息/编辑。我不想将此 markup/logic 包含在控制器中,因为呈现但隐藏它非常慢...... Chrome 中的多秒。我想这是因为所有的手表。
为了解决这个问题,我创建了一个指令,在 运行 时将详细信息/编辑项注入当前记录下。我尝试将标记 $compile 到 link 它到 ng-repeat 行的当前范围。
问题..我想我的范围有问题。该指令是 ng-repeat 块(Persons 中的 p)中的引用,因此我认为我会在指令 link 函数中传递记录范围。但是我只能通过获取父范围(scope.$parent.p 而不是 scope.p)来获取记录对象。没看懂。
此外,执行 $compile 函数后,我确实在新的详细信息块中看到了人员信息。但是更改不会反映在当前记录数据中,也不会反映在详细信息块中。
有什么建议吗?
标记:
<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:
(函数(){
'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;
}
};
})()
笨蛋:http://plnkr.co/edit/64TcuhaNi2JcC1hzon15
我也对其他选择持开放态度...
好的,我认为你的代码有很多问题。
我建议不要让指令修改自身以外的东西。
正如我之前评论的那样,不要使用 $parent。只需将数据作为属性传递。并在调用 $compile 时创建一个新范围以避免污染现有范围。
我修改了你的代码使其可以工作,但它仍然不够漂亮:
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;
}