Form naming and model naming recommendation - angularjs

If I have a form like this
<form name="form" ng-submit="form.save()">
<input type="text" name="name" ng-model="form.name">
<button type="submit" ng-disabled="form.name.$pristine">Submit</button>
</form>
Here I have a form named "form", an input-field named "name" and a model named "form.name". Will the formname+fieldname ("form.name") be in conflict with the model name ("form.name") somehow? Is this bad naming practice? Or maybe this is perfectly OK and will give me no troubles?

To be honest that's a terrible Idea, naming your form the same your model. When you declare a form in Angular, a form object by the name of the form will be created on the Scope of the view's controller. Having a model object named same as the form will result in in mixing up of the 2 objects, the form object created by angular has many fields and functions of it's own, why would you want to litter your model with that? If you try running the code you posted and placing a $watch on form, you will notice that the model value won't even be bond properly:
$scope.$watch("form", function (v) {
console.log(v);
}, true);
results in:
$addControl: function (control) {
$dirty: true
$error: Object
$invalid: false
$name: "form"
$pristine: false
$removeControl: function (control) {
$setDirty: function () {
$setPristine: function () {
$setValidity: function (validationToken, isValid, control) {
$valid: true
name: "[object Object]john"
Solution: Just give your model and form different names :)

Related

ng-model versus getElementById

I am reading the book "Pro AngularJS" by Adam Freeman, Apress.
He creates an app that is a to-do list and you can add your own things on the list. In the part "Responding to User Interaction", page 34
Here is the model
var model = {
user: "Adam",
items: [{ action: "Buy Flowers", done: false },
{ action: "Get Shoes", done: false },
{ action: "Collect Tickets", done: true },
{ action: "Call Joe", done: false }]
};
the app
var todoApp = angular.module("todoApp", []);
the controller
todoApp.controller("ToDoCtrl", function ($scope) {
$scope.todo = model;
$scope.addNewItem = function (actionText) {
$scope.todo.items.push({ action: actionText, done: false });
}
});
the input field
<input class="form-control" ng-model="actionText" />
and the button that adds new things in the model
<button ng-click="addNewItem(actionText)">Add</button>
And then he comments this line <input class="form-control" ng-model="actionText" /> (pp 36)
I have specified the name of a property for the directive to update
that is not part of the model. The ng-model directive will dynamically
create the property for me within the scope of the controller,
effectively creating dynamic model properties that are used to handle
user input.
What does he mean by
creating dynamic model properties that are used to
handle user input.
?
Do we add a new property to the model named actionText ? . We add a ""pseudo""-property so the controller can use it to grab an input value?
If this is the case, then why use ng-model="actionText" and not just use plain old getElementById? After all we only need the value of the field.
Can something like ng-click getElementById("myInput").value do the trick and also avoid adding unwanted stuff to the model?
Thanks
One of the features of angularjs is to abstract as much as possible the manual javascript manipulation of the DOM.
Imagine having 10 fields on a form and having to do getElementById…..It’s much simpler in angularjs. You just need to have ng-model in each input type and on your controller methods you just use your model. You don't even have to pass them as arguments; the method on the controller will have access to the model.
I think that using ng-model will ultimately generate a cleaner code than using getElementById to access each field value.

LocalForage + Angular - how to automatically save entire form object on change

I have a scope variable named productForm and I would like to save it whenever a change occurs to it's underlying models (i.e. productForm.designer)
I would like the key to be the id of the product the form is associated with, rather than on a per input basis. I tried doing something like this, with no luck:
intakeApp.controller("MageProductCtrl", ["$scope", "$http", "$localForage", function($scope, $http, $localForage) {
$localForage.bind($scope, 'someProductId');
...
}]);
My view looks something like this
<form name="productForm" ng-submit="submit(productForm.$valid)" novalidate>
<input type="text" class="primary-text" name="designer" ng-model="product.designer" value="{{product.designer}}" required>
...
</form>
When I add local-forage binding to my input, it saves the specific input with the key of product.designer BUT, I would rather if the whole form would be saved as such:
key: someProductId, value: {product.designer: "foo", product.name: "bar"}
Any ideas what I'm doing wrong?
I was facing the same issue and toke a look of the binding method https://github.com/ocombe/angular-localForage/blob/master/dist/angular-localForage.js#L379
You can see that it does only bind when the object is already in the database or when you provide a default value. I solved the issue like this:
$localForage.bind($scope, { key: 'user', defaultValue: {}})

handle submit event in directive as well

I’m quite new to Angular but I love it already!
I need to write a couple of reusable components for a wizard.
I would like to handle the submit event from the form in the directive as well, is this possible?
<form ng-submit="submit()" name="exampleForm">
<foo data="someData"></foo> <!-- I need to handle the submit event in directive as well-->
<input type="submit" id="submit" value="Submit"/>
</form>
If the user presses enter or clicks the button on the form the directive has to make a call to the backend and double check the data.
If the check is successful the form will be valid.
I built a simple example here
Thanks in advance!
Stefan
Yes it's possible.
The first issue is targeting the correct $scope. Right now your code targets the submit() function on the controller. The <form> can't see the $scope of the directive due to how the html elements are nested.
Since you want to submit() from the directive, then the directive template should also include the <form> element and the submit input button.
Invoking foo will look like this:
<foo data="someData" name="exampleForm"></foo>
If you would rather keep foo the way it is (and that's a totally legit way to go about it) then you'll need a new directive with the submit() function. So you'd have 2 directives working together (Very angular! Many wow!).
What you will want here is a custom directive for form validation. Custom form validators are added to the $validators object on the ngModelController.
For a simple example for an "integer" directive (found in the Angular docs here under "Custom Validation"), the directive would be defined like this:
var INTEGER_REGEXP = /^\-?\d+$/;
app.directive('integer', function() { return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
ctrl.$validators.integer = function(modelValue, viewValue) {
if (ctrl.$isEmpty(modelValue)) {
// consider empty models to be valid
return true;
}
if (INTEGER_REGEXP.test(viewValue)) {
// it is valid
return true;
}
// it is invalid
return false;
};
} }; });
And it would be used in the form like this:
<form name="form" class="css-form" novalidate>
<input type="number" ng-model="size" name="size" min="0" max="10" integer />{{size}}<br />
</form>
There is also an option for asynchronous validation, adding the validator to the $asyncValidatorsobject. More info is found at the link mentioned above. The working example using both is found here
Here's a working example:
http://plnkr.co/edit/sckCOq3a50PBjat2uixk?p=preview
I've created another 2-way binding on the directive's scope (called "submit"), so you can specify the name of the submit function as it will be seen from ExampleController's scope (I used "directiveSubmit" here).
scope: {
data: '=',
submit: '='
},
.
<foo data="someData" submit="directiveSubmit"></foo>
The directive's controller then creates that function and assigns it to its own scope, and that also assigns it to ExampleController's scope via the magic of 2-way binding.
Next you can run that submit function from ExampleController's scope and it will actually be referring to the newly created function in the directive.
<form ng-submit="directiveSubmit(exampleForm)" name="exampleForm" novalidate>
Notice that I'm also passing the exampleForm into that function. Without that the directive will have a hard time getting access to it.
Finally, I've also added novalidate to the form because if you don't do that some browsers (i.e. Chrome) will handle form validation in a way that may (or may not) be undesirable. In Chrome try removing that and submitting, then you'll see what I mean (I also made the "name" field required).

Forms-angular, how a custom directive field can affect the $pristine state of the full record?

Question related to forms-angular project.
Preamble
The default formInput directive of forms-angular can be override by a custom directive by specifying the form.directive property in an extended mongoose model,
var CategorySchema = new Schema({
name: String,
});
var PlansSchema = new Schema({
categories: {
type: [CategorySchema],
form: {
directive: 'plan-categories'
}
}
});
The custom plan-categories directive has a template where fields of [CategorySchema] can be edited.
What is working
Let's start with a first simple template:
<div>
<div ng-repeat="category in record.categories">
<input ng-model="category.name" />
</div>
</div>
Forms-angular can successfully detect changes in these custom plan-categories directive input fields bound to data (injected scope.record). In particular when changing the user changes the value of the above input fields, the "Save" button of the page is enabled, allowing the Save operation.
The activation of the Save button thanks to the following comparison in parent formInput's BaseCtrl scope false === $scope[$scope.topLevelFormName].$pristine (see base.js).
Not working
Now, the Save button doesn't get enabled, when changing the category.name variable with an expression or a function called by ng-click, as below:
<div>
<div ng-repeat="category in record.categories">
<input ng-model="category.name" />
<button ng-click="category.name = 'Hello'">Edit</button>
</div>
</div>
On button click, the category.name variable seems to be correctly changed, since the value in the input is changed accordingly. Unfortunately, the Save button stays disabled.
Note: I also unsuccessfully tried to pass to ng-click a method (from the scope injected in the link method of the custom directive) and setting the category.name variable in a $timeout call.
I guess the ng-model directive of the input field calls parent's (multi-ancestor?) $setDirty() method.
Question
how do I magically get $setDirty() called by forms-angular in order to enable the "Save" button
If it is not possible:
how do I access BaseCtrl scope and call $setDirty() when changing the record.categories elements?
Offhand I cannot think of a magical solution, but the decidedly non-magical way is to depend on $data.baseScope (see https://github.com/forms-angular/forms-angular/blob/master/js/controllers/base.js#L12) which saves going through lots of $parents.

AngularJs can't access form object in controller ($scope)

I am using bootstrap-ui more specifically modal windows. And I have a form in a modal, what I want is to instantiate form validation object. So basically I am doing this:
<form name="form">
<div class="form-group">
<label for="answer_rows">Answer rows:</label>
<textarea name="answer_rows" ng-model="question.answer_rows"></textarea>
</div>
</form>
<pre>
{{form | json}}
</pre
I can see form object in the html file without no problem, however if I want to access the form validation object from controller. It just outputs me empty object. Here is controller example:
.controller('EditQuestionCtrl', function ($scope, $modalInstance) {
$scope.question = {};
$scope.form = {};
$scope.update = function () {
console.log($scope.form); //empty object
console.log($scope.question); // can see form input
};
});
What might be the reasons that I can't access $scope.form from controller ?
Just for those who are not using $scope, but rather this, in their controller, you'll have to add the controller alias preceding the name of the form. For example:
<div ng-controller="ClientsController as clients">
<form name="clients.something">
</form>
</div>
and then on the controller:
app.controller('ClientsController', function() {
// setting $setPristine()
this.something.$setPristine();
};
Hope it also contributes to the overall set of answers.
The normal way if ng-controller is a parent of the form element:
please remove this line:
$scope.form = {};
If angular sets the form to your controllers $scope you overwrite it with an empty object.
As the OP stated that is not the case here. He is using $modal.open, so the controller is not the parent of the form. I don't know a nice solution. But this problem can be hacked:
<form name="form" ng-init="setFormScope(this)">
...
and in your controller:
$scope.setFormScope= function(scope){
this.formScope = scope;
}
and later in your update function:
$scope.update = function () {
console.log(this.formScope.form);
};
Look at the source code of the 'modal' of angular ui bootstrap, you will see the directive has
transclude: true
This means the modal window will create a new child scope whose parent here is the controller $scope, as the sibling of the directive scope. Then the 'form' can only be access by the newly created child scope.
One solution is define a var in the controller scope like
$scope.forms = {};
Then for the form name, we use something like forms.formName1. This way we could still access it from our controller by just call $scope.forms.formName1.
This works because the inheritance mechanism in JS is prototype chain. When child scope tries to create the forms.formName1, it first tries to find the forms object in its own scope which definitely does not have it since it is created on the fly. Then it will try to find it from the parent(up to the prototype chain) and here since we have it defined in the controller scope, it uses this 'forms' object we created to define the variable formName1. As a result we could still use it in our controller to do our stuff like:
if($scope.forms.formName1.$valid){
//if form is valid
}
More about transclusion, look at the below Misco's video from 45 min. (this is probably the most accurate explanation of what transcluded scopes are that I've ever found !!!)
www.youtube.com/watch?v=WqmeI5fZcho
No need for the ng-init trickery, because the issue is that $scope.form is not set when the controller code is run. Remove the form = {} initialization and get access to the form using a watch:
$scope.$watch('form', function(form) {
...
});
I use the documented approach.
https://docs.angularjs.org/guide/forms
so, user the form name, on "save" click for example just pass the formName as a parameter and hey presto form available in save method (where formScopeObject is greated based upon the ng-models specifications you set in your form OR if you are editing this would be the object storing the item being edited i.e. a user account)
<form name="formExample" novalidate>
<!-- some form stuff here -->
Name
<input type="text" name="aField" ng-model="aField" required="" />
<br /><br />
<input type="button" ng-click="Save(formExample,formScopeObject)" />
</form>
To expand on the answer by user1338062: A solution I have used multiple times to initialize something in my controller but had to wait until it was actually available to use:
var myVarWatch = $scope.$watch("myVar", function(){
if(myVar){
//do whatever init you need to
myVarWatch(); //make sure you call this to remove the watch
}
});
For those using Angular 1.5, my solution was $watching the form on the $postlink stage:
$postLink() {
this.$scope.$watch(() => this.$scope.form.$valid, () => {
});
}

Resources