In Angular, I'm creating a directive for a reusable component that wraps ui-select
(to automate integration with REST services). My directive will be invoked roughly like this:
<rest-backed-selector selected-model="vm.selection"
service="abp.services.app.someservice"
on-select="vm.onSelect()">
In accordance with best practices for reusable components, this directive will isolate its scope (I'm omitting ancillary stuff like templateUrl for clarity):
app.directive(
'restBackedSelector',
[ function () {
return {
scope: {
selectedModel: '=',
service: '@',
onSelect: '&'
}
};
]);
Now here's the problem: $scope.selectedModel
needs to be passed, in turn, to ui-select
via the template:
<ui-select ng-model="selectedModel" ...>
This won't work because passing a model from the top level of $scope
will break the binding when the ui-select
controller changes its value, due to that well-known gotcha of Angular scope inheritance.
What's the recommended way of working around this?
Here's a demonstration of the problem: http://plnkr.co/edit/XjGuXSjWFEfG4eyZL6sR?p=preview
Changes made by selecting an item in the dropdown are not reflected in the scope of the directive nor the top-level app controller. One partial workaround is to uncomment paged-select-box.js line 26, which will explicitly update the outer scopes by handling the on-select event. However, even then, changes originating in the outer scopes (such as hitting the reset button) won't be reflected in the ui-select scope.
When you have a hierarchy of directives that inherit properties (or pass down properties) your first reflex should be to not use a scope
property but a bindToController
property.
The benefits are:
controllerAs: 'vm',
scope: {},
bindToController: {
selection: '=',
requestFormat: '&',
itemFormat: '&'
}
And with controllerAs
the template needs to follow:
inner selection: {{ vm.selection.full_name }}
<ui-select ng-model="vm.selection"
on-select="vm.onSelect($item)">
<ui-select-match placeholder="Enter search term">{{ vm.itemFormat({ item: $select.selected }) }}</ui-select-match>
<ui-select-choices repeat="item in vm.items"
refresh="vm.requestFirstPage($select.search)">
<span ng-bind-html="vm.itemFormat({ item: item })"></span>
</ui-select-choices>
</ui-select>