I'm new to Angular and still painfully wrapping my head around custom directives.
I'd like to reuse this bit of HTML
<ui-select ng-model="model.selectedLanguages" multiple search-enabled="true" theme="select2" style="width: 300px;">
<ui-select-match placeholder="Pick one...">{{$item.name}}</ui-select-match>
<ui-select-choices repeat="lang.id as lang in langs |filter: { name : $select.search }">
<div ng-bind-html="lang.name | highlight: $select.search" ></div>
</ui-select-choices>
</ui-select>
by wrapping it into my custom directive:
<language-picker ng-model="model.selectedLanguages"/>
something like this:
app.directive('languagePicker', function() {
return {
template : '<ui-select ng-model="**PARENT'S NGMODEL**" multiple search-enabled="true" theme="select2" style="width: 300px;"><ui-select-match >{{$item.name}}</ui-select-match><ui-select-choices repeat="lang.id as lang in langs | filter: { name : $select.search }"><div ng-bind-html="lang.name | highlight: $select.search"></div></ui-select-choices></ui-select>',
restrict : 'E',
require : 'ngModel',
replace : true
....
};
});
But how do I pass the ngModel from my language-picker
to the ui-select
directive ?
UPDATE
Using the suggestions below, I got it work with ui-select, but the outer model doesn't get updated at all,see plnkr.co/edit/Y43dmMGIc5GxM9fLoNPW, probably because it's child scope and parent scope remains the same?
UPDATE 2
I got it to work in a convoluted way that looks horrible to me, because I've no idea why it "works" in the first place (see the weird stuff happening in the controller):
app.directive('languagePicker', function(LanguageService) {
return {
templateUrl : 'LanguagePickerTpl.html',
restrict : 'E',
scope : {
languages : '='
},
controller : function($scope, LanguageService) {
console.log($scope);
$scope.langs = LanguageService.get();
$scope.model = $scope;
}
};
})
template:
<ui-select ng-model="model.languages" multiple search-enabled="true"
theme="select2" style="width: 300px;">
<ui-select-match>{{$item.name}}</ui-select-match>
<ui-select-choices repeat="lang.id as lang in langs | filter: { name : $select.search }">
<div ng-bind-html="lang.name | highlight: $select.search"></div>
</ui-select-choices>
</ui-select>
I would be very happy if anyone could explain what's going on (the "working" example is here http://plnkr.co/edit/B53F9sc7UGkj0uxUpC17 )
The ng-model
has some special handling, see here under the heading "Custom Control Example". The steps are:
I suggest you use isolated scope; it makes the interface to your component clearer and saves you from side-effects. In this case you want to pass the list of available options (languages):
scope: {
langs: '='
}
Usage would be:
<language-picker ng-model="model.selectedLanguages" langs="langs"/>
Your directive requires (maybe optionally) the ngModel
:
require: ['ngModel']
You override ngModel
's $render
method, e.g.:
link: function(scope,elem,attrs,ctrls) {
var ngModelCtrl = ctrls[0];
ngModelCtrl.$render = function() {
...
};
}
The logic of render is responsible for transferring the model value (the one here: <language-picker ng-model="model.selectedLanguages"/>
, i.e. model.selectedLanguages
) to the view. The simplest thing I can think of, is to use isolated scope and transfer the outer model value to a variable of the isolated scope as:
ngModelCtrl.$render = function() {
scope.innerSelection = ngModelCtrl.$viewValue;
};
Bind this variable in the template as:
<ui-select ng-model="innerSelection" ...>
...
</ui-select>
Last you have to make sure that changes to the inner selection will be propagated to the outer model:
// still inside link()
scope.$watch('innerSelection', function(newval, oldval) {
if( newval != oldval ) { // skip the first time
ngModelCtrl.$setViewValue(newval);
}
});
This solution may be a bit more involved than others, but lets you use all the features of the ngModel
, e.g. validation, parsing/formatting (i.e. data conversion).
You need to use the "equals" syntax on you directive's scope. This will keep hold of the values populated in the parent scope. So your directive becomes:
app.directive('languagePicker', function() {
return {
template : '<ui-select ng-model="**PARENT'S NGMODEL**" multiple search-enabled="true" theme="select2" style="width: 300px;"><ui-select-match >{{$item.name}}</ui-select-match><ui-select-choices repeat="lang.id as lang in langs | filter: { name : $select.search }"><div ng-bind-html="lang.name | highlight: $select.search"></div></ui-select-choices></ui-select>',
restrict : 'E',
require : 'ngModel',
replace : true,
scope: {
ngModel: "=ngModel"
}
...
};
});
I am confident this will work for you :) Let me know if it does not!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With