Can I access a form in the controller?
Asked Answered
T

9

154

I'm currently using the following.

$scope.$$childHead.customerForm[firstName], so that:

<form name="customerForm">
  <input type="text" name="firstName" 
         ng-model="data.customer.firstName" 
         tabindex="1"  
         ng-disabled="!data.editable" 
         validationcustomer />
</form>

But this only works in Chrome. Now I tried the following:

$scope.editCustomerForm[firstName], so that:

<form name="customerForm" ng-model="editCustomerForm">
  <input type="text" name="firstName" 
         ng-model="data.customer.firstName" tabindex="1"  
         ng-disabled="!data.editable" 
         validationcustomer />
</form>

Which doesn't work. Note my form is inside a Foundation Tab. How can I access firstName?

EDIT: It looks like the form isn't added to the scope when it's inside a Foundation Tab.

Anyone has got a solution for this?

Taxiplane answered 24/10, 2013 at 14:29 Comment(0)
L
214

The reason you can't access the input is because the Foundation tab (or ng-repeat?) creates an isolate scope. One way to address this is using the controller as syntax:

<div ng-controller="MyController as ctrl">

<!-- an example of a directive that would create an isolate scope -->
<div ng-if="true">

<form name="ctrl.myForm">
    ...inputs
    Dirty? {{ctrl.myForm.$dirty}}

    <button ng-click="ctrl.saveChanges()">Save</button>
</form>

</div>

</div>

Then you can access the FormController in your code like:

function MyController () {
    var vm = this;
    vm.saveChanges = saveChanges;

    function saveChanges() {

       if(vm.myForm.$valid) { 
            // Save to db or whatever.
            vm.myForm.$setPristine();
       }
}
Limon answered 25/11, 2014 at 1:10 Comment(7)
As far as I can see, the template cannot call the "saveChanges" method, as it is not exposed to the templateArnaldo
The "saveChanges" method is exposed in line 3 of the javascript or am I misunderstanding?Limon
this is good as it means you can avoid injecting the whole $scope, which is cleaner in my opinionHymnist
How do you test this in jasmine? In my spec, vm.myForm is undefinedBeshrew
This should be noted in the official docs for 1.5.X that is the way to do components and es6. thank you sirDermot
tried this but $valid doesnt seem to work with this solutionDysteleology
Worked for me, but I had to change my ng-form to an form tag.Tong
N
94

You can attach the form to some object which is defined in a parent controller. Then you can reach your form even from a child scope.

Parent controller

$scope.forms = {};

Some template in a child scope

<form name="forms.form1">
</form>

Problem is that the form doesn't have to be defined in the moment when to code in the controller is executed. So you have to do something like this

$scope.$watch('forms.form1', function(form) {
  if(form) {
    // your code...
  }
});
Nabokov answered 9/4, 2014 at 14:18 Comment(1)
I'd suggest using var watcher = $scope.$watcher and inside of the if statement you would excute watcher() to unbind the watch. This makes it a 1 time watch so you're not watching every digest after it is setElson
S
91

If you want to pass the form to the controller for validation purposes you can simply pass it as an argument to the method handling the submission. Use the form name, so for the original question it would be something like:

<button ng-click="submit(customerForm)">Save</button>
Shiflett answered 9/6, 2014 at 15:19 Comment(2)
To clarify for future readers, if say your form is named/defined similar to this <form name="myform"></form>, or even <div ng-form name="myform"></div>, then your click event would be as follows: ng-click="submit(myform)". Then you can access the Angular form object in your click function like: $scope.submit = function (form) { if (form.$valid) { etc.Pugnacious
I find a problem here - let's say there's a dropdown list in the form. Using the above method only gives me the view value and not the exact value that I need. Or am I doing anything wrong, will add a fiddle.Transarctic
N
85

Bit late for an answer but came with following option. It is working for me but not sure if it is the correct way or not.

In my view I'm doing this:

<form name="formName">
    <div ng-init="setForm(formName);"></div>
</form>

And in the controller:

$scope.setForm = function (form) {
    $scope.myForm = form;
}

Now after doing this I have got my form in my controller variable which is $scope.myForm

Neapolitan answered 26/5, 2014 at 3:46 Comment(5)
The only thing I'd add to this is to be sure this is at the bottom of the form.Brinn
Position of <div ng-init="setForm(formName);"></div> does not matter. Just be careful that it in under form.Scutellation
good, but I would prefer a simpler solution: ng-init="$parent.myForm = formName" Whithout the need to change the controller Note: it's only working with direct controller, contrary to the solution abovePika
After trying the other methods, I settled on this one because it allows the name attribute to be exactly what I want it to be. The problem with the other dummy-object solutions is if this component is used in another component with an ng-form, that other ng-form uses this forms name literally. So it will have a field with a string-literal (NOT nested properties) name of "dummy.myForm", I found this unacceptable.Intermit
I tried and failed many times to use the controllerAs syntax (I'm working with $mdDialog). Finally settled for this and it did a great job. Only note is that any controller initializations need to be run on a $timeout as the form isn't available when the controller first runsYoungyoungblood
O
24

To be able to access the form in your controller, you have to add it to a dummy scope object.

Something like $scope.dummy = {}

For your situation this would mean something like:

<form name="dummy.customerForm">

In your controller you will be able to access the form by:

$scope.dummy.customerForm

and you will be able to do stuff like

$scope.dummy.customerForm.$setPristine()

WIKI LINK

Having a '.' in your models will ensure that prototypal inheritance is in play. So, use <input type="text" ng-model="someObj.prop1"> rather than <input type="text" ng-model="prop1">

If you really want/need to use a primitive, there are two workarounds:

1.Use $parent.parentScopeProperty in the child scope. This will prevent the child scope from creating its own property. 2.Define a function on the parent scope, and call it from the child, passing the primitive value up to the parent (not always possible)

Omalley answered 19/11, 2014 at 9:28 Comment(2)
Where is the effective area to define the form binding?Metz
its worth mentioning that dummy.customerForm will be undefined until the conditions of ng-if are met should the form element have an ng-if conditional upon itLapointe
O
23

This answer is a little late, but I stumbled upon a solution that makes everything a LOT easier.

You can actually assign the form name directly to your controller if you're using the controllerAs syntax and then reference it from your "this" variable. Here's how I did it in my code:

I configured the controller via ui-router (but you can do it however you want, even in the HTML directly with something like <div ng-controller="someController as myCtrl">) This is what it might look like in a ui-router configuration:

views: {
            "": {
                templateUrl: "someTemplate.html",
                controller: "someController",
                controllerAs: "myCtrl"
            }
       }

and then in the HTML, you just set the form name as the "controllerAs"."name" like so:

<ng-form name="myCtrl.someForm">
    <!-- example form code here -->
    <input name="firstName" ng-model="myCtrl.user.firstName" required>
</ng-form>

now inside your controller you can very simply do this:

angular
.module("something")
.controller("someController",
    [
       "$scope",
        function ($scope) {
            var vm = this;
            if(vm.someForm.$valid){
              // do something
            }
    }]);
Olsson answered 21/3, 2016 at 15:35 Comment(1)
Although this is mostly just the same technique as several other answers suggest, it's the best variation and should be the accepted answer, especially since everybody uses controllerAs anyway now.Pogue
M
6

Yes, you can access a form in the controller (as stated in the docs).

Except when your form is not defined in the controller scope and is defined in a child scope instead.

Basically, some angular directives, such as ng-if, ng-repeat or ng-include, will create an isolated child scope. So will any custom directives with a scope: {} property defined. Probably, your foundation components are also in your way.

I had the same problem when introducing a simple ng-if around the <form> tag.

See these for more info:

Note: I suggest you re-write your question. The answer to your question is yes but your problem is slightly different:

Can I access a form in a child scope from the controller?

To which the answer would simply be: no.

Moneybags answered 2/4, 2014 at 22:27 Comment(2)
... unless you set up your forms and controller as described in @ondrs' answer (using $scope.forms = {} and name="forms.form1")Wassyngton
Please see the answer immediately above yours by KhalilRavanna. You can access the form from $scope.formName. He provides a working examplePhiz
C
3

add ng-model="$ctrl.formName" attribute to your form, and then in the controller you can access the form as an object inside your controller by this.formName

Capstone answered 7/3, 2018 at 12:5 Comment(0)
C
-2

Definitely you can't access form in scope bec. it is not created. The DOM from html template is loaded little bit slowly like controller constructor. the solution is to watch until DOM loaded and all the scope defined!

in controller:

$timeout(function(){
    console.log('customerForm:', $scope.customerForm);
    // everything else what you need
});
Christology answered 8/2, 2017 at 15:3 Comment(1)
a watch to access a form inside a controller, is it the worst answerHouppelande

© 2022 - 2024 — McMap. All rights reserved.