最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Angular directive data binding not working properly - Stack Overflow

programmeradmin0浏览0评论

I have been trying to write a shared directive for my angular apps that turns an input field into jquery timepicker.

I have two input fields, fromTime and toTime, and I want to update the toTime when the fromTime is changed.

My code is as follows:

HTML

<input type="text"
       time-picker="{ timeFormat: 'H:i'}"
       class="input-block-level"
       name="fromTime"
       ng-model="filter.fromTime"
       ng-change="update()" 
/>

<input type="text"
       time-picker="{ timeFormat: 'H:i'}"
       class="input-block-level"
       name="toTime"
       ng-model="filter.toTime"
       ng-change="update()" 
/>

Directive

sharedServices.directive('TimePicker', function () {
return {
    restrict: 'A',
    scope: {
        time: "=ngModel"
    },
    link: function(scope, element, attrs) {
                    //Initialize the timepicker
        $(element).timepicker(scope.$eval(attrs.TimePicker));
                    //watch for changes
        scope.$watch('time', function(stuff) {
            console.log('some change', stuff);
        });
    }
};
});

sharedServices included

var roomsApp = angular.module("roomsApp", ["ngResource",'ui', 'sharedServices','ui.bootstrap']).config(function($routeProvider) { ...

The directive loads and the timepicker is initalized and attached to the element, but when I change the input fields nothing happens, not even the ng-change event that is attached to the element. The input fields are also empty when the page loads, even though the model is set to contain some value in the app's controller. Can anyone shed some light on the matter?

*Update this demonstrates my problem. Nothing is console logged when the date is changed

I have been trying to write a shared directive for my angular apps that turns an input field into jquery timepicker.

I have two input fields, fromTime and toTime, and I want to update the toTime when the fromTime is changed.

My code is as follows:

HTML

<input type="text"
       time-picker="{ timeFormat: 'H:i'}"
       class="input-block-level"
       name="fromTime"
       ng-model="filter.fromTime"
       ng-change="update()" 
/>

<input type="text"
       time-picker="{ timeFormat: 'H:i'}"
       class="input-block-level"
       name="toTime"
       ng-model="filter.toTime"
       ng-change="update()" 
/>

Directive

sharedServices.directive('TimePicker', function () {
return {
    restrict: 'A',
    scope: {
        time: "=ngModel"
    },
    link: function(scope, element, attrs) {
                    //Initialize the timepicker
        $(element).timepicker(scope.$eval(attrs.TimePicker));
                    //watch for changes
        scope.$watch('time', function(stuff) {
            console.log('some change', stuff);
        });
    }
};
});

sharedServices included

var roomsApp = angular.module("roomsApp", ["ngResource",'ui', 'sharedServices','ui.bootstrap']).config(function($routeProvider) { ...

The directive loads and the timepicker is initalized and attached to the element, but when I change the input fields nothing happens, not even the ng-change event that is attached to the element. The input fields are also empty when the page loads, even though the model is set to contain some value in the app's controller. Can anyone shed some light on the matter?

*Update http://plnkr.co/edit/t3BzShezEdh29ZAlI8ZI?p=preview this demonstrates my problem. Nothing is console logged when the date is changed

Share Improve this question edited Jun 6, 2013 at 15:49 Bjarki asked Jun 6, 2013 at 14:47 BjarkiBjarki 2311 gold badge4 silver badges12 bronze badges 3
  • Show us the code where the directive is included. I see a few things that are really odd. – TheHippo Commented Jun 6, 2013 at 14:50
  • @TheHippo , done. The directive loads and the timepicker is initialized. But the binding between the scope and the directive is not working properly. – Bjarki Commented Jun 6, 2013 at 15:12
  • Please keep taking advantage of the ngModelController but not as a prop of your isolated scope, set it as a required controller injected as the forth argument of your linkibg function, overloading its $render method using its $viewValue and using its $setViewValue on your UI widget changes bined with $apply called on the scope. – atondelier Commented Jun 6, 2013 at 22:51
Add a ment  | 

2 Answers 2

Reset to default 6

To fix the problem

You'll need to listen to changes to the input field and update scope.time.

First change I did was to include Angular after jQuery, that way the element parameter will be a jQuery object.

Then listen to the change event and update scope.time. Wrap the call in scope.$apply to tell Angular that something has changed.

element.on('change', function () {
    scope.$apply(function () {
        scope.time = element.datepicker('getDate');
    });
});

Plunker

Alternative approach

Personally when writing directives that are applied on input elements I want to allow the user to use ngModel directive to keep the data in sync, since that is a known convention. By using ngModel you could also (optionally) use it's controller to add custom validation or other logic, e.g. parsers/formatters (not needed in this case).

By using the $parse service we can read/write data back to the ngModel field.

I also changed so that datetime options can be passed if needed.

Lastly I moved the first datetime() call to the pile function since that's the place where DOM manipulation should be made.

.directive("datetime", ['$parse', function($parse) {
  return {
      require: '?ngModel',
      restrict: 'A',
      pile: function (element, attrs) {
        var options = $parse(attrs.datetime)() || {};
        console.log('Passed options:', options);

        element.datepicker(options);

        // Return the link function
        return function (scope, element, attrs, ngModel) {
          var getter, setter;

          // If the ngModel directive is used, then set the initial value and keep it in sync
          if (ngModel) {
            getter = $parse(attrs.ngModel);
            setter = getter.assign;

            console.log('ngModel directive used, setting initial value')
            element.datepicker("setDate", getter(scope));

            scope.$watch(attrs.ngModel, function(val, prev) {
              console.log('ngModel changed from', prev, 'to', val);
            });

            element.on('change', function () {
              console.log('change');
              var newValue = element.datepicker('getDate');
              scope.$apply(function () {
                setter(scope, newValue);
              });
            });
          }
        };
      }
  };
}])

And the directive is used like this:

<input type="text" datetime="{showButtonPanel: true}" ng-model="parentTime" />

Plunker

Update: Here is a very rudimentary implementaion using jquery datepicker - http://plnkr.co/edit/4JZIWo6mJqg59F5o7xdh?p=preview

angular
.module("demo", [])
.directive("test", function() {
  return {
      restrict: 'A',
      scope: {
          time: "="
      },
      link: function(scope, element, attrs) {
        console.log("scope.time:" + scope.time);
        $(element).datepicker();
        $(element).datepicker("setDate", scope.time);
      }
  };
})
.controller("demoCtl", function($scope){
  $scope.parentTime = new Date();
});

Have a look at angular-ui calendar module. If you don't want to use it, you can look at their code to help you.


Also, the way you set up your scope looks strange. I think you want to bind you directive's time property, to a property managed by the directive's parent scope, not the ng-model.

try this

return {
    restrict: 'A',
    scope: {
        time: "="
    },
    link: function(scope, element, attrs) {
                    //Initialize the timepicker
        $(element).timepicker(scope.$eval(attrs.TimePicker)); 
                    //watch for changes
        scope.$watch('time', function(stuff) {
            console.log('some change', stuff);
        });
    }
};

and use it

<input type="text"
       time-picker="{ timeFormat: 'H:i'}"
       class="input-block-level"
       name="toTime"
       time="filter.toTime"
       ng-change="update()" 
/>

also you don't need to wrap attrs.TimePicker in $scope.eval.

发布评论

评论列表(0)

  1. 暂无评论
ok 不同模板 switch ($forum['model']) { /*case '0': include _include(APP_PATH . 'view/htm/read.htm'); break;*/ default: include _include(theme_load('read', $fid)); break; } } break; case '10': // 主题外链 / thread external link http_location(htmlspecialchars_decode(trim($thread['description']))); break; case '11': // 单页 / single page $attachlist = array(); $imagelist = array(); $thread['filelist'] = array(); $threadlist = NULL; $thread['files'] > 0 and list($attachlist, $imagelist, $thread['filelist']) = well_attach_find_by_tid($tid); $data = data_read_cache($tid); empty($data) and message(-1, lang('data_malformation')); $tidlist = $forum['threads'] ? page_find_by_fid($fid, $page, $pagesize) : NULL; if ($tidlist) { $tidarr = arrlist_values($tidlist, 'tid'); $threadlist = well_thread_find($tidarr, $pagesize); // 按之前tidlist排序 $threadlist = array2_sort_key($threadlist, $tidlist, 'tid'); } $allowpost = forum_access_user($fid, $gid, 'allowpost'); $allowupdate = forum_access_mod($fid, $gid, 'allowupdate'); $allowdelete = forum_access_mod($fid, $gid, 'allowdelete'); $access = array('allowpost' => $allowpost, 'allowupdate' => $allowupdate, 'allowdelete' => $allowdelete); $header['title'] = $thread['subject']; $header['mobile_link'] = $thread['url']; $header['keywords'] = $thread['keyword'] ? $thread['keyword'] : $thread['subject']; $header['description'] = $thread['description'] ? $thread['description'] : $thread['brief']; $_SESSION['fid'] = $fid; if ($ajax) { empty($conf['api_on']) and message(0, lang('closed')); $apilist['header'] = $header; $apilist['extra'] = $extra; $apilist['access'] = $access; $apilist['thread'] = well_thread_safe_info($thread); $apilist['thread_data'] = $data; $apilist['forum'] = $forum; $apilist['imagelist'] = $imagelist; $apilist['filelist'] = $thread['filelist']; $apilist['threadlist'] = $threadlist; message(0, $apilist); } else { include _include(theme_load('single_page', $fid)); } break; default: message(-1, lang('data_malformation')); break; } ?>