javascript - 如何协调 Angular 的 "always use dots with ngModel"规则与隔离范围?

标签 javascript angularjs twitter-bootstrap angularjs-scope

我正在使用 Bootstrap 开发 Angular 应用。

为了尽量减少 Bootstrap 在我的 HTML 上的占用空间,我引入了两个表单指令:

form-control.js

module.directive('formControl', function() {
  return {
    restrict : 'E',
    templateUrl : 'form-control.tmpl.html',
    scope: {
      label: '@'
    },
    transclude : true
  };
});

form-control.tmpl.html

<div class="form-group">
  <label class="control-label col-sm-2">
    {{ label }}
  </label>
  <div class="col-sm-10"
       ng-transclude>
  </div>
</div>

我还对该指令进行了一些“扩展”,用于各种表单输入字段。例如:

表单输入文本.js

module.directive('formInputText', function() {
  return {
    restrict : 'E',
    templateUrl : 'form-input-text.tmpl.html',
    scope: {
      label: '@',
      value: '=ngModel'
    }
  };
});

form-input-text.tmpl.html

<form-control label="{{label}}">
  <input type="text"
         class="form-control"
         ng-model="value">
</form-control>

app.html

<form-input-text label="Name"
                 ng-model="person.name">
</form-input-text>

这里我遇到了一个问题。此示例中有许多作用域:

appScope = { person : { name : "John" } };
isolateScope = {
  label: "Name",
  value: "John" // bound two-way with appScope.person.name
};
transcludeScope = {
  __proto__: isolateScope,
  label: "Name", // inherited from isolateScope
  value: "John" // inherited from isolateScope
};

如果我改变输入文本框中的文本,那么只有transcludeScope被修改:

appScope = { person : { name : "John" } };
isolateScope = {
  label: "Name",
  value: "John" // bound two-way with appScope.person.name
};
transcludeScope = {
  __proto__: isolateScope,
  label: "Name", // inherited from isolateScope
  value: "Alice" // overrides value from isolateScope
};

这是因为 <input>直接绑定(bind)到 transcludeScope 的属性. transcludeScope.value直接改了,父作用域isolateScope不受影响。因此,输入中的任何模型更改都不会返回到 appScope。 .

我想做的是在 appScope.person.name 之间创建双向绑定(bind)和 isolateScope 的嵌套属性,例如isolateScope.model.value .

理想情况下,我想这样声明我的指令:

表单输入文本.js

module.directive('formInputText', function() {
  return {
    restrict : 'E',
    templateUrl : 'form-input-text.tmpl.html',
    scope: {
      model: {
        label: '@',
        value: '=ngModel'
      }
    }
  };
});

这将允许嵌入的部分绑定(bind)到 model.value ,这将使更改对 isolateScope 可见,而 isolateScope 又会在 isolateScope 中传播更改返回appScope .

Angular 似乎不直接支持这种用法。

谁能指出支持此用例的 Angular 功能,或者如果不支持,请提供解决方法?

编辑:

目前,我的解决方案是内联 form-control模板进入 form-input-text模板。

form-input-text.tmpl.html

<div class="form-group">
  <label class="control-label col-sm-2">
    {{ label }}
  </label>
  <div class="col-sm-10">
    <input type="text"
           class="form-control"
           ng-model="value">
  </div>
</div>

这消除了引入的子范围 ng-transclude ,但它也复制了标记,这是我希望重构到一个地方的。

最佳答案

考虑作用​​域实际上有点走错了路,我认为嵌入与它没有太大关系。要“正确地”执行此操作,您应该与 ngModelController 集成。这允许任何以后集成的解析器和格式化程序(可能包含验证逻辑)在适当的时间运行。这有点复杂,因为你有 2 个:应用程序中的父级和指令模板中的父级,每个都有 2 个“管道”要集成:

  • 模型值(value) -> 观点值(value)
  • View 值 -> 模型值

父 ngModelController 的 View 值然后用作内部 ngModelController 的模型值。所以整体管道看起来像

  • 父模型值 -> 父 View 值 -> 内部模型值 -> 内部 View 值
  • 内部 View 值 -> 内部模型值 -> 父 View 值 -> 父模型值

为此:

  • 确保您在指令定义中require: 'ngModel',以访问父级 ngModelController

  • 从父级 ngModelController 到内部的更改是使用父级 ngModelController$render 方法完成的,使用其 $viewValue。这可确保父级 $formatters 中的任何函数都已运行。

  • 用户从内部指令发起的更改是通过向其 $viewChangeListeners 数组添加一个函数来完成的,该函数调用父 ngModelController 上的 $setViewValue 。要从链接函数范围访问它,您需要一个命名的表单和输入元素。一个小麻烦是,表单仅在指令的链接功能运行后才在指令范围内注册,因此您需要观察者才能访问它。

  • 以防万一,请确保 formInputText 中的模型在一个对象中。 (我不确定这在技术上是否必要)

  • 然后您不需要在内部指令的 scope 对象中拥有模型。

综合来看,

app.directive('formInputText', function() {
  return {
    restrict : 'E',
    templateUrl : 'form-input-text.tmpl.html',
    scope: {
      label: '@'
    },
    require: 'ngModel',
    link: function(scope, element, attrs, ngModelController) {
      scope.model = {};

      // Propagate changes from parent model to local
      ngModelController.$render = function() {
        scope.model.value = ngModelController.$viewValue;
      };

      // Propagate local user-initiated changes to parent model
      scope.$watch('form', function(form) {
        if (!form) return;
        form.input.$viewChangeListeners.push(function() {
          ngModelController.$setViewValue(form.input.$modelValue);
        });       
      });
    }
  };
});

它的模板看起来像

<form-control label="{{label}}" ng-form name="form">
  <input type="text"
         class="form-control"
         name="input"
         ng-model="model.value">
</form-control>

这可以在 http://plnkr.co/edit/vLGa6c55Ll4wV46a9HRi?p=preview 上看到

关于javascript - 如何协调 Angular 的 "always use dots with ngModel"规则与隔离范围?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26459958/

相关文章:

javascript - PHP 和 Ajax : Illegal invocation

javascript - AngularJS:提交时重置表单

html - 响应式图像环绕 div

javascript - angularjs - 基于先前选择选项的嵌套 json 的 ng-options

html - 为什么我的 Bootstrap 文本框的宽度缩小了

css - 当我使用少于 12 列时,如何避免将 Bootstrap 列分解为行?

javascript - 如何使用 $q.all(promises) 并在网页上显示响应?

javascript - 使用 Symfony 和 Twig 从 href 中的外部 JavaScript 文件调用函数

javascript - 使用 ID 动态显示输入字段值

javascript - 在 Angular 中链接和滚动到顶部的标准方法?