Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access to form controller hidden due to angular ui tab isolated/inherited scope

I have a simple case:

<div ng-controller="myController">
  <tabset>
    <tab>
      <form name="myForm"></form>
    </tab>
  </tabset>
</div>

and now, in myController method, I would like to access myForm to call $setPristine:

$scope.myForm.$setPristine()

but I can not. tabset / tab creates isolated/inherited scope. It's just a sample, but I run into this problems when using angular js directives that create isolated scopes many times.

How can I get over this issue? In the past I did something like this (with ng-table that also creates new scope):

ng-init="$parent.saataaTable = this"

but it's far from perfect.

like image 494
dragonfly Avatar asked Apr 03 '14 10:04

dragonfly


2 Answers

This was one of the most difficult concepts for me to get around and my solution is simple but kind of difficult to explain so bear with me.

Solution 1: Isolate Scopes

When you are only dealing with only isolate scopes (scope: {...}) or no scope (scope: false), you're in luck because the myForm will eventually be there. You just have to watch for it.

$scope.$watch('myForm', function(val) {
  if (myForm) {
    // now I can call $setPristine
  }
});

Solution 2: Child Scopes

This is when you set scope: true or transclude: true. Unless you perform a custom/manual transclusion you will not get myForm on the controller's scope.

The trick is to access the form's controller directly from the form element. This can be done by the following:

// at the form element
element.data('$formController');

// or at the control (input, select, etc.)
element.inheritedData('$formController');

// where 'element' is a jqLite element (form or ng-form)

This sets you up for a new issue: how do we know when and how we can get that element and it's data.

A quick answer is that you need to set up a dummy $watch on your controller's scope to look for (in your case) myForm. When this watch is processed you will then be able to attempt to locate the form. This is necessary due to the fact that typically when your controller first executes the FormController won't yet be on the element's data object.

A quick and simple way to find the form is to simply get all of the forms. NOTE: if there are multiple forms within the element you'll have to add some logic to find the right one. In this case our form is a form element and it's the only one. So, locating it is fairly easy:

// assuming you have inject $element into your controller

$element.find('form').data('$formController');

// where $element is the root element the controller is attached to
// it is injected just like '$scope'

Once you have the controller you can access everything you would normally. It is also important to note that Solution 2 will always work once that FormController is on the element.

I have set up a Plunk to demonstrate the code here, but please note that is a demonstration so not all best practices were kept in mind.

EDIT

I found it important to note that if you don't want to worry about the scopes of the nested directives you can just watch the form name on the scope and handle things there.

$scope.$watch('myForm', function(val) {
  if (angular.isDefined(val)) {
    // now I have access
  } else {
    // see if i can `find` the form whose name is 'myForm'
    // (this is easy if it is a form element and there's only one)
    // then get the FormController for access
  }
}
like image 158
Stephen J Barker Avatar answered Oct 24 '22 23:10

Stephen J Barker


I could not make it work using the answer above, but I found a work-around.

In the form, I created a hidden input field with a ng-model and ng-init that set its value to the form. Then in my submit function in the controller I can access the formController via this ng-model

So, in the HTML, I create a hidden field inside the form:

<input id="test" ng-model="data.myForm" ng-init="data.myForm=myForm" hidden>

And in the Controller I can get hold of the formController via data.myForm

$scope.data.myForm.$setPristine();

It is probably not very good, so I will instead avoid to rely on the $pristine and $dirty properties of the formController and find another way to detect if the form has changed (using a master copy of the object, like they do in the sample in the documentation)

like image 28
Saule Avatar answered Oct 24 '22 23:10

Saule