将表格传递给指令


75

我想将表单字段封装在指令中,所以我可以简单地做到这一点:

<div ng-form='myForm'>
  <my-input name='Email' type='email' label='Email Address' placeholder="Enter email" ng-model='model.email' required='false'></my-input>

</div>

我如何myForm在指令中访问,以便进行验证检查,例如myForm.Email.$valid


根据angular下方的答案不支持动态表单元素名称。
Emad

但是,有一种解决方法。在这里看到它Plunk,它基于@tanguy_k答案
Ruslans Uralovs 2015年

Answers:


154

要在指令中访问FormController:

require: '^form',

然后它将用作链接函数的第四个参数:

link: function(scope, element, attrs, formCtrl) {
    console.log(formCtrl);
}

fiddle

您可能只需要访问NgModelController即可:

require: 'ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
     console.log(ngModelCtrl);
}

fiddle

如果您都需要访问以下两者:

require: ['^form','ngModel'],
link: function(scope, element, attrs, ctrls) {
    console.log(ctrls);
}

fiddle


3
如果您不介意回答,那么为什么可以这样做'^form'却不可以'^ngForm'呢?我试图让它第一次起作用,但是它出现了“无控制器”错误。我比你更喜欢你的回答。
OverZealous

9
@OverZealous,显然是在HTML中找到formorng-form指令时Angular使用的指令名称form而不是ngForm。我花了几次努力才知道名字是form。我认为这是我们所使用的Angular源代码form
Mark Rajcok

2
@ eibrahim,Angular不支持动态表单元素名称。
Mark Rajcok

1
@MarkRajcok访问指令内的父表单仅完成一半。然后,您想使用$ invalid,$ error和朋友。~~这不起作用,唯一的解决方案是在指令内创建ng-form ~~ /!\我错了,请参见下面的答案:stackoverflow.com/questions/17618318/pass-form-to-directive /…
tanguy_k 2014年

3
@mtpultz,把它放在你的链接功能范围:scope.formCtrl = formCtrl;,那么你可以使用访问它在你的控制器$scopecontroller: function($scope) { ... }。但是请注意,指令控制器将首先运行,因此在控制器功能首次执行时该引用将不存在。
Mark Rajcok 2014年

32

这是一个完整的示例(使用Bootstrap 3.1设置样式)

它包含一个带有多个输入(姓名,电子邮件,年龄和国家/地区)的表单。名称,电子邮件和年龄是指令。国家/地区是“常规”输入。

当用户输入的值不正确时,将为每个输入显示一条帮助消息。

表单包含一个保存按钮,如果表单包含至少一个错误,该按钮将被禁用。

<!-- index.html -->
<body ng-controller="AppCtrl">
  <script>
    var app = angular.module('app', []);

    app.controller('AppCtrl', function($scope) {
      $scope.person = {};
    });
  </script>
  <script src="inputName.js"></script>
  <script src="InputNameCtrl.js"></script>
  <!-- ... -->

  <form name="myForm" class="form-horizontal" novalidate>
    <div class="form-group">
      <input-name ng-model='person.name' required></input-name>
    </div>

    <!-- ... -->

    <div class="form-group">
      <div class="col-sm-offset-2 col-sm-4">
        <button class="btn btn-primary" ng-disabled="myForm.$invalid">
          <span class="glyphicon glyphicon-cloud-upload"></span> Save
        </button>
      </div>
    </div>
  </form>

  Person: <pre>{{person | json}}</pre>
  Form $error: <pre>{{myForm.$error | json}}</pre>
  <p>Is the form valid?: {{myForm.$valid}}</p>
  <p>Is name valid?: {{myForm.name.$valid}}</p>
</body>

// inputName.js
app.directive('inputName', function() {
  return {
    restrict: 'E',
    templateUrl: 'input-name.html',
    replace: false,
    controller: 'InputNameCtrl',
    require: ['^form', 'ngModel'],

    // See Isolating the Scope of a Directive http://docs.angularjs.org/guide/directive#isolating-the-scope-of-a-directive
    scope: {},

    link: function(scope, element, attrs, ctrls) {
      scope.form = ctrls[0];
      var ngModel = ctrls[1];

      if (attrs.required !== undefined) {
        // If attribute required exists
        // ng-required takes a boolean
        scope.required = true;
      }

      scope.$watch('name', function() {
        ngModel.$setViewValue(scope.name);
      });
    }
  };
});

// inputNameCtrl
app.controller('InputNameCtrl', ['$scope', function($scope) {
}]);

带有指令的AngularJS形式


2
这是一个很好的例子。我更改了您的Plunk,使您的指令更通用,因为它现在可以支持任何类型的输入,例如文本,密码或电子邮件等。请在此处查看:plnkr.co/edit/13rqpfrTiTwDMpCPmT7X?p=preview
Ruslans Uralovs

当您在同一视图上多次使用同一指令时,这似乎不起作用-这是因为“名称”在指令中进行了硬编码
Marty 2015年

我最后不得不有嵌套形式,因为我需要在视图中相同的指令多次有,请参阅:stackoverflow.com/questions/14378401/...
马蒂

效果很好,感谢您的发布。尽管现在我正在处理一个相反的问题:从指令范围之外设置对象值时,它不会更新UI并“冻结”数据,不再更改。此处的示例:plnkr.co/edit/HLKKY1ZH0Kla93P2SmGj ? p=preview关于如何解决此问题的任何线索?
Rodrigo Brancher 2015年

抱歉,我忘了提到Plunk的(小)更改:我在Submit按钮的右侧添加了一个链接,该链接以编程方式更改了人员姓名。
Rodrigo Brancher 2015年

17

编辑2:我会留下我的答案,因为可能出于其他原因可能会有所帮助,但是Mark Rajcok的另一个答案是我本来想要做的,但未能上班。显然,这里的父控制器form不是ngForm


您可以使用指令上的属性来传递它,尽管这会变得很冗长。

这是一个工作的简化jsFiddle

HTML:

<div ng-form="myForm">
    <my-input form="myForm"></my-input>
</div>

指令的基本部分:

app.directive('myInput', function() {
    return {
        scope: {
            form: '='
        },
        link: function(scope, element, attrs) {
            console.log(scope.form);
        }
    };
});

发生了什么

我们已要求Angularform通过使用来将属性中命名的范围值绑定到隔离的范围'='

这样做可以使实际形式与输入指令分离。

注意:我尝试使用require: "^ngForm",但是ngForm指令未定义控制器,因此不能以这种方式使用(太糟糕了)。


话虽如此,我认为这是处理该问题的非常冗长和混乱的方法。您最好将新指令添加到form元素,并用于require访问该项目。我看看是否可以放一些东西。

编辑:使用父指令

好的,这是我可以使用父指令找出的最好的方法,我将在稍后解释:

使用父指令工作jsFiddle

HTML:

<div ng-app="myApp">
    <div ng-form="theForm">
        <my-form form="theForm">
            <my-input></my-input>
        </my-form>
    </div>
</div>

JS(部分):

app.directive('myForm', function() {
    return {
        restrict: 'E',
        scope: {
            form: '='
        },
        controller: ['$scope', function($scope) {
            this.getForm = function() {
                return $scope.form;
            }
        }]
    }
});

app.directive('myInput', function() {
    return {
        require: '^myForm',
        link: function(scope, element, attrs, myForm) {
            console.log(myForm.getForm());
        }
    };
});

这会将表单存储在父指令范围内(myForm),并允许子指令通过要求父表格(require: '^myForm')并在链接函数(myForm.getForm())中访问指令的控制器来访问它。

好处:

  • 您只需要在一个地方识别表格
  • 您可以使用父控制器来存放通用代码

负面因素:

  • 你需要一个额外的节点
  • 您需要将表单名称放入两次

我想要的

我试图使用form元素上的属性使其工作。如果此方法可行,则只需将指令添加到与相同的元素ngForm

但是,我在使用范围时遇到了一些奇怪的行为,该myFormName变量在中会是可见的$scope,但是undefined在我尝试访问它时会出现。那使我感到困惑。


谢谢您的回答...问题出在我的指令中,我可以得到scope.form,但我不能达到scope.form.Email
Emad

这就是我在做的jsfiddle.net/SwEM6/1(请注意,输入的名称是从指令范围属性{{name}}进行“计算”的,但是如果我将其更改为硬编码的值,例如“ Email”,则它有效吗...知道吗
Emad

您正在寻找一种制作动态表单名称的方法,即name="{{name1}}",而我还没有找到答案。
rGil

1
发言时间过早。 该解决方案可能对您有用,但是涉及到一点。@Overzealous,您可能需要将此添加到您的答案中。
rGil

我将在下面查看Mark Rajcok的回答。我显然没有针对正确的指令。
OverZealous

8

从AngularJS 1.5.0开始,有很多更清洁的解决方案(与link直接使用函数相反)。如果要访问FormController子组件的指令控制器中的表单,则只需require在指令上拍一下属性,如下所示:

return {
  restrict : 'EA',
  require : {
    form : '^'
  },
  controller : MyDirectiveController,
  controllerAs : 'vm',
  bindToController : true,
  ...
};

接下来,您将能够在模板或指令控制器中对其进行访问,就像处理任何其他范围变量一样,例如

function MyDirectiveController() {
  var vm = this;
  console.log('Is the form valid? - %s', vm.form.$valid);
}

请注意,要执行此操作,还需要bindToController: true在指令上设置属性。查看文档$compile这个问题的更多信息。

文档中的相关部分:

要求

需要另一个指令,并将其控制器作为链接函数的第四个参数注入。require属性可以是字符串数组对象

如果require属性是一个对象并且bindToController是真实的,则使用require属性的键将必需的控制器绑定到该控制器。如果所需控制器的名称与本地名称(键)相同,则可以省略该名称。例如,{parentDir: '^parentDir'}等效于{parentDir: '^'}


这看起来几乎是正确的,但对我来说却不是很有效。如果我只有必需的属性,它将被传递到链接函数中,但未绑定到控制器(bindToController为true)。是因为没有隔离范围吗?
达伦·克拉克

做工精细,非常优雅。对我而言,它不适用于{parentDir:'^'},而仅适用于{parentDir:'^ parentDir'}。
乔伊

对我来说,这是一个很好的起点。但是,我遇到了问题,因为我的指令具有隔离范围,在这种情况下,我没有使用bindToController而是链接块的第4个参数中使用了require控制器。
加布里埃尔·盖茨

2

使您的“我想要的”提琴奏效!出于某种原因,您可以在console.log中看到“ $ scope.ngForm”字符串,但是直接对其进行记录无法正常工作,从而导致未定义。但是,如果将属性传递给控制器​​函数,则可以获取它。

app.directive('myForm', function() {
return {
    restrict: 'A',
    controller: ['$scope','$element','$attrs', function($scope,$element,$attrs) {
        this.getForm = function() {
            return $scope[$attrs['ngForm']];
        }
    }]
}
});

http://jsfiddle.net/vZ6MD/20/

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.