Reputation: 1427
In my AngularJs (v1.5.9) app, I have a kind of list view directive that itself depends on a directive to render the individual, complex items. What exactly should be rendered inside the list items is decided by the consumer and passed in via transclusion.
So the basic structure looks something like this:
<list-directive>
<list-item>
<some more stuff />
<transcluded content />
</list-item>
</list-directive>
I am now trying to add another directive to this structure, that takes data that can be passed in via an attribute from the outside into the top-level element and then does stuff depending on that input for each of the list-items.
The structure is somewhat complex and I tried to reduce the code snippet below to the bare minimum.
// controller
(function () {
'use strict';
function FcDataListCtrl($scope, $q, $element) {
var that = this;
initVars();
init();
function initVars() {
that.actionButtons = that.actionButtons || [];
}
function init() {
}
}
angular
.module('controls.fcDataList.controller', [])
.controller('fcDataListCtrl', FcDataListCtrl);
})();
(function () {
'use strict';
function FcDataList() {
return {
restrict: 'E',
transclude: true,
template: `<fc-data-list-item ng-repeat="item in ::fcDataList.items" item="::item">
<div ng-transclude></div>
</fc-data-list-item>`,
scope: {
items: '=?',
actionButtons: '=?'
},
controller: 'fcDataListCtrl',
controllerAs: 'fcDataList',
bindToController: true
};
}
angular
.module('controls.fcDataList', [
'controls.fcDataList.controller',
'controls.fcDataList.item'
])
.directive('fcDataList', FcDataList);
})();
(function () {
'use strict';
function FcDataListItem() {
return {
restrict: 'E',
replace: true,
require: '^fcDataList',
transclude: 'element',
template: `<div>
<div ng-transclude></div>
<fc-item-menu items="fcDataList.actionButtons"></fc-item-menu>
</div>`,
scope: {
item: '=?'
},
link: {
pre: FcDataListItemLink
}
};
function FcDataListItemLink(scope, elem, attrs, fcDataListCtrl) {
initVars();
init();
function initVars() {
}
function init() {
console.log('FcDataListItem')
console.dir(fcDataListCtrl.actionButtons);
}
}
}
angular
.module('controls.fcDataList.item', [
'components.fioControlsExtensions.fcDataList.menu'
])
.directive('fcDataListItem', FcDataListItem);
})();
(function () {
'use strict';
function FcItemMenu() {
return {
restrict: 'E',
template: `<div ng-repeat="item in items">
<div>{{ item.icon }}</div>
</div>`,
scope: {
items: '=?'
},
link: {
pre: FcItemMenuLink
}
};
function FcItemMenuLink(scope, elem, attrs) {
scope.open = open;
initVars();
init();
function initVars() {
console.log('MenuItem');
console.dir(scope.items);
}
function init() {
}
function open(event) {
}
}
}
angular
.module('components.fioControlsExtensions.fcDataList.menu', [])
.directive('fcItemMenu', FcItemMenu);
})();
(function () {
'use strict';
function AppCtrl() {
var that = this;
init();
function init() {
that.fcDataList = {
buttons: [
{ icon: 'ff-show' }
],
items: [
{ firstName: 'Ivan', lastName: 'Petrov', jobPosition: 'Zookeeper' },
{ firstName: 'Andrei', lastName: 'Müller', jobPosition: 'Pilot' },
{ firstName: 'Christian', lastName: 'Klein', jobPosition: 'Cook' },
{ firstName: 'Peter', lastName: 'Stoyanov', jobPosition: 'Fuller' },
{ firstName: 'Nadine', lastName: 'Wolf', jobPosition: 'Driving Instructor' },
{ firstName: 'Amya', lastName: 'Krüger', jobPosition: 'Military' }
],
}
}
}
angular
.module('controls.example', [
'controls.fcDataList'
])
.controller('AppCtrl', AppCtrl)
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
<html lang="en" ng-app="controls.example">
<body ng-controller="AppCtrl as app">
<b>Test for data list n stuff</b>
<fc-data-list items="app.fcDataList.items"
action-buttons="app.fcDataList.buttons">
<div class="row">
<span> This is a list item </span>
</div>
</fc-data-list>
</body>
</html>
here is alsow a Codepen with the same example: https://codepen.io/lyioth/pen/LbqWLz/
Please note that the transclusion of the actual item content is not shown here (but that is working without a problem, so I skipped it).
The actual problem is that, the items
in fc-item-menu
stay undefined. I added some log statements to show that at the levels above this component the array in question is not in fact empty.
If I change the directive to also require
the controller and access the actionButtons
property directly, it seems to work. But I'd rather not do that.
So the question is, why doesn't this work as expected? What am I missing?
Upvotes: 1
Views: 121
Reputation: 615
The items
at fc-item-menu
are bound to fcDataList.actionButtons
. That means that the fc-item-menu
directive will look for them at scope.fcDataList.actionButtons.
Let's go one step back - in your fc-data-list
directive you have the same type of binding - fcDataList.item
. That works, because you have used bindToController
and controllerAs
, which puts the isolated scope in a scope field, with the name defined in controllerAs
(scope.fcDataList).
Back to fc-item-menu
: although you have require: ^fcDataList in the directive, the above expression will be undefined, because no scope.fcDataList exist. In addition that is true, because with the transclusion, an isolated scope has been created, which does not carry the scope.fcDataList. The way to make it work is define a scope property yourself with the values from the controller, like so:
function FcDataListItemLink(scope, elem, attrs, fcDataListCtrl) {
scope.buttons = fcDataListCtrl.actionButtons;
}
Here's a working plnkr: https://codepen.io/anon/pen/GNzELy
Upvotes: 1