Relative model reference? - angularjs

I'm writing a few components for a form, these will be included (via Grails <g:include> tags) in multiple places (a registration page, and an account page). The way Angular works, I have to specify the form name in order to get a reference to a particular field. For example:
<form name="myForm">
<input ng-model="username"/>
{{myForm.username}} -- right here
</form>
In the example above, I must use myForm in order to access username. This will not work when using the same field in multiple forms, as the form name will change.
Is there a way to access the field relatively, or maybe figure out the enclosing form name and inject that?

You shouldn't have to reference your form's name. Just bind it to a property on your controller's $scope and you should be good to go. Doing it this way, you won't have to care what your form name is, only that the controller has the property you need.
<form ng-controller="yourController">
<input ng-model="username"/>
{{ username }}
</form>
angular.controller('yourController', ['$scope', function($scope){
$scope.username = 'keanu reeves';
}]);
Here's a code pen.

You have to use some sort of dynamic form name code. e.g.
<div ng-repeat="myForm in forms">
<form name="myForm{{$index}}">
<input ng-model="myForm.username"/>
{{myForm.username}} -- right here
</form>
</div>
Although it's difficult to solve your exact problem without example code. Create a plunker or codepen that we can analyze.

Related

AngularJS ng-required better implement from controller?

I'm thinking of a good way to implement ng-required.
Let's say I have a bunch of inputs with ng-required in my app.
<input type="text" id="one" />
<input type="date" id="two" />
<input type="radio" id="three" />
<input type="checkbox" id="four" />
I would like to do something in a controller, where I could pass an array of required fields. I'm thinking that if I made an array of elements such as:
var myEl = angular.element( document.querySelector( '#some-id' ) );
and some how set the required property that way.
I write a directive which would decide from an array if the field is required, if it does not exist in the array, it's not required if it does, it's required.
Ultimately, I would like to have an array that allows passing of fields in such a way:
var reqArray = ('#id', ('#id1' || 'id2')) etc.
Works the same as conditional logic operators:
#id is required
#id1 || #id2 is required, but not both.
Not sure where to begin, or if it's feasible in Angular.
This would make it easier to modify required fields in large applications without having to modify the actual html.
It can be done, but angular already provides its own ways to validate forms. Some brief details:
The form tag must have a novalidate attribute in order to prevent HTML5 validation. <form name="myForm" novalidate>
With that, now angular can take charge of the validation by adding a "required" attribute to your inputs <input ng-model="myModel" type="text" required>
By this point, angular has taken control of your validation, it can also validate other HTML5 attributes like pattern. <input pattern="[0-9][A-Z]{3}" type="text" title="Single digit followed by three uppercase letters."/>
I suggest you take look at this official guide (also take a look at the directives guide on that same site, I wanted to link it but I don't yet have the rep).
That being said, what you are trying to accomplish is also possible, but rather redundant since you would have to add an attribute to your inputs anyway (the directive, and angular is able to validate already) and also require ngModel in the directive.
I made this plunkr to show you how to do it, but take notice of the extra work needed in order to validate something that angular already does natively, I'd leave this kind of work for expanding on validations, like comparing items or models.
Also, querying a selector directly like in your suggestion is not considered "the angular way". A better way would be to add the directive to your form element and access the element through the 'element' parameter in the directive.
Hope this helps.

angular form name from inside associated controller

I'm very new to angular!! What i'm trying to do is get the form name from inside the associated controller or a reference to the form object from inside the controller.
<form name="someName" ng-controller="formController">
<label>Name:
<input type="text"/>
</label>
<input type="submit"/>
</form>
controller:
obApp.controller('formController',function($scope){
//this does NOT work - undefined - was expecting it to be "someName"
var q = $scope.formName;
//this exists - but can not use it since the
//name of the form can be whatever and i do not know beforehand what that name is
var name = $scope.someName.$name;
});
My problem is that i don't know how in the world to get the name of the actual form. A reference to it would be even better. Looked for about 5 hours and i can't seem to figure it out.
The problem translates to this: "How to get a reference to the form object form the associated controller?".
The thing is that you want to access the DOM element of the controller, you could do it like this:
obApp.controller('formController',function($scope, $element){
var name = $element.attr('name');
});
But that is considered a bad practice, you shouldn't be accessing the DOM element directly inside the controller, consider using a directive instead.

One form element updating multiple pieces of the model?

I am looking at using angular for a project at work but I have a question first. We have a single page application that's pretty intricate. We do have a basic model set up but some fields in the model are redundant. If I couldn't reduce the redundancy, what steps could I take in angular so that one form element changes two variables in the model?
I've put together a bare bones jsfiddle of what I'm hoping to do:
<div ng-app>
<input type="text" ng-model="yourName" placeholder="Enter a name here" /><br/>
<span>Hello {{yourName}}!</span><br/>
<span>Hello {{altName}}!</span>
</div>
How could I change this around so that the input would assign it's value to both yourName as well as altName? I've tried what I thought would be obvious such as comma or pipe delimiting the ng-model attribute to no avail. Is it possible?
You could set a $watch on the yourname-Variable within your controller and then change the altName in its callback. Should look like this:
$scope.$watch('yourName', function(){
$scope.altName = $scope.newName;
});

angularjs make div readonly - where to put business logic

I want to make a section readonly based on a checkbox. Typical example is primary address and the billing address. The user enters primary address and can chose the billing address to be the same the primary address.
See fiddle for example.
HTML (primary address):
<fieldset ng-model="primaryAdd">
<legend>Primary Address:</legend>
House#: <input type="text" ng-model="primaryAdd.houseNum"><br>
Street: <input type="text" ng-model="primaryAdd.street"><br>
State: <input type="text" ng-model="primaryAdd.state"><br>
Zip: <input type="text" ng-model="primaryAdd.zip">
</fieldset>
HTML (Same as address)
<input type="checkbox" name="makeSameAsAddress" ng-model="makeSameAsAddressCheck" ng-click="makeSameAsAddress($event)">Same as above<br/>
HTML (Billing Address)
<fieldset ng-model="billingAddress">
<legend>Billing Address:</legend>
<div>
<div style="font-size:small">if the address is the same as residence, prefill and make it readonly</div>
House#: <input type="text" ng-model="billingAddress.houseNum" ng-readonly="makeSameAsAddressCheck"><br>
Street: <input type="text" ng-model="billingAddress.street" ng-readonly="makeSameAsAddressCheck"><br>
State: <input type="text" ng-model="billingAddress.state" ng-readonly="makeSameAsAddressCheck"><br>
Zip: <input type="text" ng-model="billingAddress.zip" ng-readonly="makeSameAsAddressCheck">
</div>
</fieldset>
The fiddle does work. When I click on "Same as above" checkbox, the billing section gets the address from the primary section and is readonly. The question is why I cannot put the ng-readonly on the fieldset or the div surrounding the inputs. I have to put the ng-readonly on all the inputs. I feel like there must be a better way to do this.
Maybe I need to create a directive called "makeSameAsPrimaryAndReadOnly". I read that business logic should sit in directives and not HTML directly or the controllers. But I don't see the point of doing this. Unless I was reusing the billing address in multiple places.
Secondly, where is a good place to initialize the model and have a schema to share with other controllers etc? I imagine a factory would be best for this? Thank you.
Angular rocks !!
You could create a directive, but it might be overkill for this purpose.
You can't apply the ng-readonly to the div or the fieldset like it were a css style. Honestly, I don't think it's too bad the way it is if you only have 4 inputs.
You can simplify the logic for the checkbox (and eliminate the ng-click event) by using a watch expression:
$scope.$watch('makeSameAsAddressCheck', function (newv, oldv) {
if (newv)
$scope.billingAddress = $scope.primaryAdd;
else if (newv !== oldv)
$scope.billingAddress = angular.copy($scope.primaryAdd);
});
Notice that when you turn the checkbox on, I set both scope variables to reference the same object. Now when you make modifications to the primary address, it's automatically updated in the billing address.
When you turn the checkbox off, I make a copy of the primary address. They can now be edited independently.
http://jsfiddle.net/fcSgz/
Business login in controllers is fine. In fact, that's where it should be.
You can initialize your model in a service, which is created with the "factory" function.

How do I access the child ngModel from a directive?

Like in this question, I want to add .error on a form field's parent .control-group when scope.$invalid is true.
However, hardcoding the form name like in ng-class="{ error: formName.fieldModel.$invalid }" means that I can't reuse this in different forms, plus I'd rather not repeat this declaration everywhere.
I figured that a directive that looks something like this could work:
<div class="control-group" error-on="model1, model2">
<input ng-model="model1">
<input ng-model="model2">
</div>
So when either model1 or model2 is not valid, .control-group gets .error added.
My attempt here. Is it possible to access the models from the directive, given the model names?
If there's a better approach, I'd love to hear it too.
I don't think that writing a custom directive is necessery for this use-case as the ng-form directive was created exactly for situations like those. From the directive's documentation:
It is useful to nest forms, for example if the validity of a sub-group
of controls needs to be determined.
Taking your code as an example one would write:
<div class="control-group" ng-class="{ error: myControlGroup1.$invalid }>
<ng-form name="myControlGroup1">
<input ng-model="model1">
<input ng-model="model2">
</ng-form>
</div>
By using this technique you don't need to repeat expressions used in ng-model and can reuse this fragment inside any form.
You can also change the markup in the accepted answer to do without the nesting, since ng-form is also a class directive:
<div class="control-group ng-form" name="controlGroup11" ng-class="{ error: controlGroup1.$invalid }>
<input ng-model="model1">
<input ng-model="model2">
</div>
Final solution Fiddle
Inside your link function, you can get access to the formController. It has all of the controls. So the following will give your directive access to .$valid:
el.controller('form')[attrs.errorOn].$valid
However, I don't know how to watch that for changes. I tried watching attrs.errorOn (i.e., watch the ng-model property), but the watch doesn't trigger unless a valid value is input (because of the way Angular forms work... unless that value is valid, it is not assigned to the scope property set by ng-model.)
Fiddle.
Maybe someone can take this further...

Resources