16

I have the following code:

<div modal="modal.shouldBeOpen" close="close()" options="opts">
    <div class="modal-body">
        <form novalidate name="itemForm" style="margin-bottom: 0px;">

Which is contained inside the included file modal.html

<div data-ng-controller="AdminController">
   <ng-include src="'/Content/app/admin/partials/grid-subject.html'"></ng-include >
   <ng-include src="'/Content/app/admin/partials/modal.html'"></ng-include>
</div>

In my AdminController controller I am trying to use the following code to reset the form to pristine:

$scope.itemForm.$setPristine();

When I do this it tells me that "itemForm" is undefined.

Is there a way I can set the contents of the form to pristine. I assume this is a scope problem but I am not sure how to fix it. I tried the one solution of removing the second include and pasting the code in directly. This solution works.

However we want to be able to reuse code so I would like to be able to do this with an include for modal.html

Note that the reason we would like to do this is because we have something like the following on our modal.html:

    <button
        class="btn float-right"
        data-ng-disabled="itemForm.$pristine"
        data-ng-click="modalReset()"
        data-ng-show="modal.resetButton">
        Reset</button>
</form>

So we are actually inside of the itemForm and would like to set it to $pristine from the button inside.

Alan2
  • 23,493
  • 79
  • 256
  • 450

4 Answers4

10

This answer will break all the rules (i.e., DOM traversal inside a controller), but here it is anyway...

.controller('AdminController', ['$scope','$element',
function($scope, $element) {
  $scope.$on('$includeContentLoaded', function() {
    var childFormController = $element.find('form').eq(0).controller('form');
    console.log(childFormController);
    childFormController.$setPristine();
  });
}]);

We wait for the ng-included content to load, then from the $element where AdminController is defined, we look for form elements, pick the first one, then get its FormController.

Plunker

If you are only calling $setPristine() as a result of some user interaction, you won't need to look for the $includedContentLoaded event – I only had to do that because I didn't want to create any UI component to trigger the operation, and when the controller first runs, the form doesn't exist yet.

See also AngularJS: Access formController of a form placed inside transcluded directive from parent controller which deals with the similar problem of trying to access a child from a parent.

A cleaner solution: define a directive (use it on the ng-include element) and pass it an AdminController function as an attribute. In the directive's link function, call that method and pass the FormController as a parameter. Then the AdminController will have a reference to the desired FormController. (I did not bother coding this up, as I'm not sure you want a solution where you have to use a directive along with ng-include.)

Community
  • 1
  • 1
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • http://stackoverflow.com/users/215945/mark-rajcok - I added something to the question. Not sure if this makes a difference. The reason we would like to do this is because we want to be able to enable or disable a Reset and Save button on the form. Now I think more about this. Could I have a controller with the modalReset() function that just works for the ng-include? In other words can an ng-include have its own controller? – Alan2 Jul 06 '13 at 22:01
  • Yes, associating a controller with the modal would be a much better way of doing this: [plunker with modal controller](http://plnkr.co/edit/i2C2ihK3PoJ0fYel93vf?p=preview) – Mark Rajcok Jul 07 '13 at 01:50
  • +1 for "breaking all the rules." Sometimes you gotta do what you gotta do. – rGil Jul 09 '13 at 04:29
4

Well, one way to do it is to broadcast an event, like so:

angular.module('myApp',[])
    .controller('AdminCtrl',function($scope){
        $scope.modalReset = function(){
            $scope.$broadcast('modal-reset');
        };
    })
    .controller('ModalCtrl', function($scope){
        $scope.$on('modal-reset', function(){
            $scope.itemForm.$setPristine();
        });
    });

This way you don't have to traverse the dom.

Master Morality
  • 5,837
  • 6
  • 31
  • 43
1

Do not break the rules :) Just define the variable (empty object) in the controller and use it while defining your form. Since angular JS uses scope prototypes under the hood, when form will try to access the inner scope (to bootstrap the variable), it will first go via scope chain and try to find the same variable in the parent's scope.

<!—- The vars should live in the controller. I placed them here for the example. -—>
<div ng-controller=“controllerName” ng-init="form={}; model={}" >
    <div ng-include=“ ‘path-to-the-template’ ”></div>
</div>

<!—- Inside path-to-the-template -—>
<form name="form.createUser">
    <input name="name" ng-model="model.name" />
    <input name="email" ng-model="model.email" />
</form>

Link for reference http://blog.152.org/2014/07/angular-form-element-not-attaching-to.html

kirill.buga
  • 1,129
  • 2
  • 12
  • 26
0

If you want to achieve this as the result of some user interaction, in my opinion a much more cleaner and 'angular' way of doing it would be to use a custom directive which will set the form to pristine (i.e. when the user wants to clear the form by pressing esc or clicking a button or whatever).

app.directive("formCleaner",
  function () {
        return {
            restrict: 'E',
            require: '^form',
            scope: {
                callback: '&',
                defaultText:'@'
            },
            template: '<button type="button" ng-click="setFormToPristine()" class="btn btn-warning"  >{{defaultText}}</button>',
            link: function (scope, element, attrs, formCtrl) {
                scope.setFormToPristine = function () {
                    formCtrl.$setPristine();
                    scope.callback();
              };
            }
  };
});

and simply hook it up to some button in your form:

<form name="testForm">
      <input type="text" ng-model="someModel" />
      <hr/>
      <input type="button" value="submit form" class="btn btn-primary" ng-disabled="testForm.$pristine" 
        ng-click=submitForm(testForm) />
      <form-cleaner callback="resetFormCallback(testForm)" default-text="Clear Form"></form-cleaner>
</form>

And if you're looking to set the form to pristine directly from the controller, (not as a result of some user interaction) such as success response from a POST, then one way would be to assign a callback to the directive which will be responsible for clearing the form and then invoking that callback from the controller. In your view:

<form-cleaner callback="resetFormCallback(testForm)" default-text="Clear Form"></form-cleaner>

and the controller:

 $scope.resetFormOnSubmitCallback=function(cb){
    $log.warn("simulating $http POST call.....");
      $timeout(function() {
            cb();
            $scope.someModel=null;
        }, 3000)
  }

and the directive:

return {
            restrict: 'E',
            require: '^form',
            scope: {
                callback: '&',
                defaultText:'@',
                ngDisabled:'='
            },
            template: '<button type="button" ng-disabled="ngDisabled" ng-click="submitForm()" class="btn btn-primary"  >{{defaultText}}</button>',

            link: function (scope, element, attrs, formCtrl) {
            var setFormToPristine=function(){
              $log.log("setting form to prsitine....");
              formCtrl.$setPristine();
            };  

                scope.submitForm = function () {
                scope.callback({
                    onFormSubmittedCallback:setFormToPristine
                });
              };
            }
  };

See plunk

Mohammad Sepahvand
  • 17,364
  • 22
  • 81
  • 122