javascript - How to detect a change in an ng-model object inside a custom directive? -


consider plunker has behaviour i'm after.

the problem 1) think i'm doing wrong, , 2) uses deep watch, expensive , should unnecessary.

the example bit contrived. have in application featured typeahead/autocomplete control binds objects , renders them according expressions. plunker stripped down show parts should relevant question; how implement sort of thing correctly?

the main difficulty ensuring entering "something" in input , clicking button should propagate directive , cause span.outlet update accordingly. ie, clicking alpha, , attempting change description should lead "selected: - something" showing on page.

if remove deep watch, won't happen unless replace $scope.selected new object reference rather changing property on existing object reference (see comment in mainctrl of plunker).

so, first requirement custom directive deals objects, , not strings.

the second requirement directive must able update span.outlet whenever $scope.selected object changes outside of custom directive.

thirdly directive should performant possible. , why i'm raising question. ng-model allready has shallow $watch internally, , i'm adding deep $watch on top of it, bad perf. there way without such deep $watch?

finally nice if didn't have have scope.ngmodel binding. feels dirty.

relevant markup:

<input ng-model="newdescription"> <button ng-click="setnewdescription(newdescription)">set description</button> <hr/> <my-list ng-model="selected" expression="{{::expression}}" options="options"></my-list> 

relevant main controller code:

  $scope.options = [     {key:'a', desc: 'alpha'},     {key:'b', desc: 'beta'},     {key:'g', desc: 'gamma'},     {key:'d', desc: 'delta'}   ];   $scope.selected = $scope.options[1];   $scope.expression = '{{key}} - {{desc}}'; 

mylist directive:

app.directive('mylist', ['$interpolate', function($interpolate) {   return {     restrict: 'e',     require: 'ngmodel',     replace: true,     template: '<div>selected: <span class="outlet"></span><ul><li ng-repeat="item in vm.items"><a href="" ng-click="vm.select(item)">{{vm.render(item)}}</a></li></ul></div>',     scope: {       ngmodel: '=',       items: '=options'     },     link: link,     controller: ctrl,     controlleras: 'vm',     bindtocontroller: true    };    function link(scope, element, attrs, ngmodelctrl) {      var outlet = element.children()[0];      scope.vm.render = $interpolate(attrs.expression);       ngmodelctrl.$formatters.push(function(modelvalue) {        if (ngmodelctrl.$isempty(modelvalue))             return '';         else             return scope.vm.render(modelvalue);      });       ngmodelctrl.$render = function() {        console.log('rendering', ngmodelctrl.$viewvalue);        outlet.textcontent = scope.vm.render(ngmodelctrl.$modelvalue);      };      ngmodelctrl.$parsers.push(function(value) {         // gets called due $setviewvalue call in deep $watch.         // don't have way of going string object, $modelvalue contains right thing.         console.log('parsing', value);         return ngmodelctrl.$modelvalue;     });      // prefer if solve without deep watch on ngmodel!     scope.$watch('vm.ngmodel', function(n, o) {       if (angular.equals(n, o)) return;       console.log('ngmodel $watch\r\n', o, '->\r\n', n);       ngmodelctrl.$setviewvalue(scope.vm.render(n));       ngmodelctrl.$render();     }, true);    }    function ctrl($scope) {     this.select = function(item) {       console.log('selecting', item);       this.ngmodel = item;     }.bind(this);   } }]); 

any appreciated i've been trying wrap head around problem while now. thanks!

i not sure understand needs, prepared sample

app.directive('mylist', ['$interpolate', function($interpolate) {   return {     restrict: 'e',     replace: true,     template: '<div>selected: <span class="outlet"></span><ul><li ng-repeat="item in vm.items"><a href="" ng-click="vm.select(item)">{{vm.render(item)}}</a></li></ul></div>',     scope: {       selected: '=',       items: '=options'     },     link: link,     controller: ctrl,     controlleras: 'vm',     bindtocontroller: true    };    function link(scope, element, attrs, ctrl) {      var outlet = element.children()[0];      scope.vm.render = $interpolate(attrs.expression);      scope.$watch(function () {       return ctrl.selected;     }, function (value) {       console.log('rendering', value);       outlet.textcontent = scope.vm.render(value);     });   }    function ctrl($scope) {     this.select = function(item) {       console.log('selecting', item);       this.selected = item;     }.bind(this);   } }]); 

html:

 <input ng-model="newdescription">  <button ng-click="setnewdescription(newdescription)">set description</button>  <hr/>  <my-list selected="selected" expression="{{::expression}}" options="options"></my-list> 

update 2

sample ng-model. don't need call $render function in $watch. it's called after model changing automatically.

also example how create directive ngmodel here.


Comments