Reputation: 17527
Here is an HTML fragment that I would like to replace with an AnguarJS directive:
<li ng-click="ctrl.navigate('home')"
ng-mouseenter="ctrl.mouseOver = 'home'"
ng-mouseleave="ctrl.mouseOver = ''"
ng-class="{'over' : (ctrl.mouseOver === 'home'), 'selected' : (ctrl.selected === 'home')}">Home</li>
Having defined my directive I was hoping that the above code would become
<li navigation-selection selection-name="home" selection-content="Home" />
To achieve this I added a directive template file navigationselection.html
containing this HTML fragment
<li ng-click="ctrl.navigate('{{selectionName}}')"
ng-mouseenter="ctrl.mouseOver = '{{selectionName}}'"
ng-mouseleave="ctrl.mouseOver = ''"
ng-class="{'over' : (ctrl.mouseOver === '{{selectionName}}'), 'selected' : (ctrl.selected === '{{selectionName}}')}">{{selectionTitle}}</li>
and this directive:
.directive('navigationSelection', [function () {
return {
templateUrl: '/navigationselection.html',
restrict: 'A',
scope: {
selectionName: '@',
selectionTitle: '@'
}
};
}]);
But the resulting HTML has one list item nested within another:
<li navigation-selection="navigation-selection" selection-name="home" selection-title="Thing 1" class="ng-isolate-scope">
<li ng-click="ctrl.navigate('home')" ng-mouseenter="ctrl.mouseOver = 'home'" ng-mouseleave="ctrl.mouseOver = ''" ng-class="{'over' : (ctrl.mouseOver === 'home'), 'selected' : (ctrl.selected === 'home')}" class="ng-binding">Home</li>
</li>
(N.B. I have added the line breaks for readability.)
If I add replace: true
to my directive definition object then it works: the inner list item completely replaces its parent. But, as the AngularJS directive documentation points out, replace
is depricated and will be removed in next major release.
In his answer to adam0101’s question How to set a native attribute from AngularJS directive? Mark Rajcok shows how to do this using the directive's link
function. And so if I replace the directive template with just
{{selectionTitle}}
and extend the directive with this link function
.directive('navigationSelection', [function () {
return {
templateUrl: '/navigationselection.html',
restrict: 'A',
scope: {
selectionName: '@',
selectionTitle: '@'
},
link: function (scope, element, attrs) {
attrs.$set('ng-click', "ctrl.navigate('" + attrs.selectionName + "')");
attrs.$set('ng-mouseenter', "ctrl.mouseOver = '" + attrs.selectionName + "'");
attrs.$set('ng-mouseleave', "ctrl.mouseOver = ''");
attrs.$set('ng-class', "{'over' : (ctrl.mouseOver === '" + attrs.selectionName + "'), 'selected' : (ctrl.selected === '" + attrs.selectionName + "')}");
}
};
}]);
It should work. But it doesn't. The resulting HTML looks very similar to my pre-directive version above:
<li navigation-selection="navigation-selection" selection-name="home" selection-title="Home" class="ng-binding ng-isolate-scope" ng-click="ctrl.navigate('home')" ng-mouseenter="ctrl.mouseOver = 'home'" ng-mouseleave="ctrl.mouseOver = ''" ng-class="{'over' : (ctrl.mouseOver === 'home'), 'selected' : (ctrl.selected === 'home')}">Home</li>
but the events, e.g. ng-click
, no longer work. I wonder if this is because I've lost access to ctrl
when I provided a scope
in my directive definition object.
How do I use an AngularJS directive, without replace
to add attributes and content to a list item such that the event directives like ng-click
can still call methods on the parent's controller?
Upvotes: 1
Views: 823
Reputation: 17527
There are scope issues; I needed to keep the original scope and not add an isolated scope without passing in the controller.
But I had also misunderstood the life-cycle and so the directives added as attributes by my custom directive are never themselves compiled and I need to do that explicitly. This code fixes things:
.directive('navigationSelection', ['$compile', function ($compile) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
element.removeAttr('navigation-selection'); // necessary to avoid infinite compile loop
element[0].innerText = attrs.selectionTitle;
attrs.$set('ng-click', "ctrl.navigate('" + attrs.selectionName + "')");
attrs.$set('ng-mouseenter', "ctrl.mouseOver = '" + attrs.selectionName + "'");
attrs.$set('ng-mouseleave', "ctrl.mouseOver = ''");
attrs.$set('ng-class', "{'over' : (ctrl.mouseOver === '" + attrs.selectionName + "'), 'selected' : (ctrl.selected === '" + attrs.selectionName + "')}");
$compile(element)(scope);
}
};
}]);
Upvotes: 1