angularjs pass ngModel from wrapper directive to wrapped directive

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!


The ng-model has some special handling, see here under the heading "Custom Control Example". The steps are:

  1. 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"/>
    
  2. Your directive requires (maybe optionally) the ngModel:

    require: ['ngModel']
    
  3. 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>
    
  4. 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).