【问题标题】:Delay in Two-way binding in a nested directive with isolate scope具有隔离范围的嵌套指令中的双向绑定延迟
【发布时间】:2025-12-13 23:50:02
【问题描述】:

我有一个控制器,其中包含一个具有隔离范围的指令。每当用户更改指令下拉列表中的值时,我需要通知控制器有关更改。我通过为指令提供回调函数并在指令下拉列表中的 ng-change 事件上调用此函数来做到这一点。我不能(也不想)在父控制器中使用手表,因为我只对用户生成的更改感兴趣。

我的问题是,在调用回调时,控制器范围上的值尚未通过 2-way 绑定更新为指令中的值(我猜这是因为回调是在同一已检测到下拉模型更改的摘要循环,但来自父控制器的模型将仅在后续循环中更新。

这是一个说明问题的 plunker 示例(注意控制台输出):

http://plnkr.co/edit/igW4WiV2niFyrMX2zrjM?p=preview

摘自plunker的相关部分:

控制器:

  ...
  vm.countryChanged = function() {
    console.log('*** countryChanged callback ***');
    console.log('country:', vm.country); // old value here

    $timeout(function() {
      console.log('country (inside timeout):', vm.country); // new value here
    });
  };
  ...

指令:

    ...
    template: '<select ng-model="vm.country" ng-change="vm.onChange()"  ' +
              '        ng-options="country for country in vm.countries">' +
              '</select>',
    scope: {
      country: '=',
      onChange: '&'
    },
    ...

在父控制器回调中获取更新数据的最正确方法是什么?我可以看到 $timeout 有所帮助,但感觉就像是代码异味,可能应该有一种方法可以强制模型在父控制器上更新。

【问题讨论】:

  • 尝试指令国家参数作为两种方式country: '&amp;'

标签: angularjs angular angular-directive angular-components


【解决方案1】:

我已经检查并修复了您的 plnkr,http://plnkr.co/edit/8XoNEq12VmXKkyBmn9Gd?p=preview

它不起作用,因为您将原始值传递给指令,一个字符串,两种方式绑定仅适用于引用。

将国家/地区值更改为对象解决了问题

vm.country = 'Belarus';

vm.country = {name: 'Belarus'};

然后:

template: '<select ng-model="vm.country.name" ng-change="vm.onChange()"  ' +
  ' ng-options="country for country in vm.countries">' +
'</select>'

【讨论】:

    【解决方案2】:

    我猜这是因为回调是在同一个摘要周期中调用的,其中下拉模型的更改已被检测到,但来自父控制器的模型只会在后续周期中更新。

    你是对的。在 ng-change 指令评估其 Angular 表达式之后, 将值从指令范围更新到父控制器的观察程序在摘要循环上触发。

    在指令和父控制器之间共享一个公共对象引用是使其工作的一种方法。由于控制器和指令共享相同的引用,因此指令和父控制器都会立即看到任何更改。

    另一种方法是将新值公开为 on-change 函数的本地参数:

    <country-chooser 
        country="vm.country" 
        country-change="vm.countryChange($event)">
    </country-chooser>
    

    <country-chooser 
        country="vm.country" 
        country-change="vm.country=$event">
    </country-chooser>
    

    在指令中:

    app.directive('countryChooser', function() {
      return {
        template: `<select ng-model="$ctrl.country"
                           ng-change="$ctrl.countryChange({'$event': vm.country})"
                           ng-options="country for country in $ctrl.countries">
                  </select>
                  `,
        scope: {
          country: '<',
          countryChange: '&'
        },
        bindToController: true,
        controllerAs: '$ctrl',
    

    在父控制器中:

      vm.countryChange = function(country) {
          console.log('*** countryChange callback ***');
          console.log('country:', country);
      };
    

    这样,父控制器内部的函数不需要等待双向绑定上的观察者的摘要周期来将值从指令范围更新到父控制器范围。

    DEMO on PLNKR


    AngularJS v1.5 组件并使它们为 Angular2+ 做好准备

    app.component('countryChooser', {
        template: `<select ng-model="$ctrl.country"
                           ng-change="$ctrl.countryChange({'$event': vm.country})"
                           ng-options="country for country in $ctrl.countries">
                  </select>
                  `,
        bindings: {
          countries: '<'
          country: '<',
          countryChange: '&'
        }
    });
    

    AngularJS v1.5 引入了components,使过渡到 Angular2+ 更容易。

    为了更轻松地过渡到 Angular2+,请避免双向 (=) 绑定。而是使用单向&lt; 绑定和表达式&amp; 绑定。

    在 Angular2+ 中,双向绑定语法实际上只是属性绑定和事件绑定的语法糖。

    <country-chooser [(country)]="vm.country">
    </country-chooser>
    

    Angular2+ 将双向绑定脱糖:

    <country-chooser [country]="vm.country" (countryChange)="vm.country=$event">
    </country-chooser>
    

    欲了解更多信息,请参阅@Angular Developer Guide - Template Syntax (two-way)

    【讨论】:

    • > 这样父控制器内部的函数确实需要等待一个摘要循环您的意思是它不需要需要等待,因为我们将新值作为回调中的参数?
    • 谢谢。我更新了答案并修复了解释。当新值作为指令调用的函数的参数给出时,父控制器不必等待。