I am having troubles linking the inputs of my form into a defined factory that I have injected into the controller that renders the form. Here is my factory:
App.factory('DeviceSelection',function() {
var states=[{selection:{}},{selection:{}},{selection:{}},{selection:{}}];
return states;
});
And here is an input of my form:
<div class="controls">
<label class="radio">
<input type="radio" name="user[role]" id="user_role_managing_editor" value="Managing editor" ng-model='states[0].selection.hours'>
Yes
</label>
<label class="radio">
<input type="radio" name="user[role]" id="user_role_area_editor" value="Area editor", ng-model='states[0].selection.hours'>
No
</label>
</div>
So, when I try to click on that Radio box, I see the following in the JS Console:
TypeError: Cannot read property 'selection' of undefined
Does that mean that I need to initialize the model before the view is rendered. If so, where?
I am trying to achieve a multi-step form, linking all the inputs in the model, until last step is reached when I am able to send the results to an API. As asked here:
Store status between forms in AngularJS?
You say that you have injected in your controller. It would be nice to see that injection, but let me blind guess something that might be happening:
I am assuming you have (something like) this:
YourApp.controller('YourController', ['$scope', 'YourFactory', function ($scope,$yourFactory) {
...
But, have you set that injection into the $scope? Otherwise the view won't have access :)
So, if you don't have it, do this:
$scope.states=$yourFactory;
I really believe this is what happened. The controller needs to tell the view where to find that state array through the $scope
Related
JSFiddle here: http://jsfiddle.net/c6tzj6Lf/4/
I am dynamically creating forms and buttons and want to disable the buttons if the required form inputs are not completed.
HTML:
<div ng-app="choicesApp">
<ng-form name="choicesForm" ng-controller="ChoicesCtrl">
<div ng-bind-html="trustCustom()"></div>
<button ng-repeat="button in buttons" ng-disabled="choicesForm.$invalid">
{{button.text}}
</button>
</ng-form>
</div>
JavaScript:
angular.module('choicesApp', ['ngSanitize'])
.controller('ChoicesCtrl', ['$scope', '$sce', function($scope, $sce) {
$scope.custom = "Required Input: <input required type='text'>";
$scope.trustCustom = function() {
return $sce.trustAsHtml($scope.custom);
};
$scope.buttons = [
{text:'Submit 1'},
{text:'Submit 2'}];
}]);
choicesForm.$invalid is false and does not change when entering text into the input field.
Solution:
I ended up using the angular-bind-html-compile directive from here: https://github.com/incuna/angular-bind-html-compile
Here is the relevant bit of working code:
<ng-form name="choicesForm">
<div ng-if="choices" bind-html-compile="choices"></div>
<button ng-click="submitForm()" ng-disabled="choicesForm.$invalid">
Submit
</button>
</ng-form>
And choices might be a snippit of HTML like this:
<div><strong>What is your sex?</strong></div>
<div>
<input type="radio" name="gender" ng-model="gender" value="female" required>
<label for="female"> Female</label><br>
<input type="radio" name="gender" ng-model="gender" value="male" required>
<label for="male"> Male</label>
</div>
The main problem is that ngBindHtml doesn't compile the html - it inserts the html as it is. You can even inspect the dynamic input and see that it doesn't have the ngModel's CSS classes (ng-pristine, ng-untouched, etc) which is a major red flag.
In your case, the form simply doesn't know that you've added another input or anything has changed for that matter. Its state ($pristine, $valid, etc) isn't determined by its HTML but by the registered NgModelControllers. These controllers are added automatically when an ngModel is linked.
For example this <input required type='text'> won't affect the form's validity, even if it's required, since it doesn't have ngModel assigned to it.
But this <div ng-model="myDiv" required></div> will affect it since it's required and has ngModel assigned to it.
The ngDisabled directive on your buttons works as expected since it depends on the form's $invalid property.
See this fiddle which showcases how ngModel registers its controller. Note that the html containing the dynamic input gets compiled after 750ms just to show how NgModelControllers can be added after FormController has been instantiated.
There are a few solutions in your case:
use a custom directive to bind and compile html - like this one
use ngInclude which does compile the html
use $compile to compile the newly added HTML but this is a bit tricky as you won't know exactly when to perform this action
This is an answer yet imcomplete because i cannot do the code at the moment.
I think your html will be included, not compiled. So the inputs are not bind to angular and are not part of the angular form object.
The only way i see is to use a directive that will compile the passed html and add it to your form. This may be quite tricky though, if you want to go on this way i suggest to edit your question to ask for the said directive.
However i'm not really familiar with $compile so i don't know if it'll work to just add $compile around $sce.trustAsHtml()
You can write a method as ng-disabled does not work with booleans, it works with 'checked' string instead:
So on your controller place a method :
$scope.buttonDisabled = function(invalid){
return invalid ? "checked" : "";
};
And on your view use it on angular expression :
<button ng-repeat="button in buttons" ng-disabled="buttonDisabled(choicesForm.$invalid)">
Here is a working fiddle
Working DEMO
This is the solution you are looking for. You need a custom directive. In my example I have used a directive named compile-template and incorporated it in div element.
<div ng-bind-html="trustCustom()" compile-template></div>
Directive Code:
.directive('compileTemplate', function($compile, $parse){
return {
link: function(scope, element, attr){
var parsed = $parse(attr.ngBindHtml);
function getStringValue() { return (parsed(scope) || '').toString(); }
//Recompile if the template changes
scope.$watch(getStringValue, function() {
$compile(element, null, -9999)(scope); //The -9999 makes it skip directives so that we do not recompile ourselves
});
}
}
});
I found the directive in this fiddle.
I believe what is really happening though due to jsfiddle I'm unable to dissect the actual scopes being created here.
<div ng-app="choicesApp">
<ng-form name="choicesForm" ng-controller="ChoicesCtrl">
<div ng-bind-html="trustCustom()"></div>
<button ng-repeat="button in buttons" ng-disabled="choicesForm.$invalid">
{{button.text}}
</button>
</ng-form>
</div>
The first div is your top level scope, your form is the first child scope. Adding the div using a function creates the dynamically added input field as a child of the first child, a grandchild of the top level scope. Therefore your form is not aware of the elements you're adding dynamically causing only the static field to be required for valid form entry.
A better solution would be to use ng-inclue for additional form fields or if your form isn't to large then simply put them on the page or template you're using.
I am working on a angular form where i need to pass the $index of an ng-repeat back to the controller to make sure i am displaying the correct message. Here is the markup:
<div ng-repeat="user in users">
<form name="form_{{$index}}">
<input name="UserName" ng-model="user.name" ng-required="true">
<div id="error-message" ng-show="form_{{$index}}.$error.required">
{{form_{{$index}}.$error.message}}
</div>
</form>
</div>
In the controller the data may look like this if the index were to be 2:
form[2].$error.required = true;
form[2].$error.message = Form field is required;
The problem is i can't put {{$index}} inside the {{form_{{$index}}.$error.message}} because it creates an angular error:
Error: [$parse:syntax] Syntax Error: Token '{' is an unexpected token
For the end results i would like: form_{{$index}}.$error.message to be parsed into form_2.$error.message which in turn will be parsed into Form field is required
Can anyone help me with this issue?
Here is a pen on this issue: CodePen Angular Form Repeat Issue
You have chosen an inconvenient way to name your form and later attempt to access it. There is no reason to have a dynamic form name (which, sets the scope property of the same name) inside ng-repeat, since you will already have the benefit of a child scope per iteration.
The following would achieve the result that you are after, without using a dynamic form name:
<div ng-repeat="user in users">
<form name="userForm">
...
<span ng-show="userForm.$error.required">
error
</span>
</form>
</div>
But, if you insist, you could use this to refer to scope, which allows you to refer to its property like so:
<span>{{this['form_' + $index].$error.required}}</span>
Change the input to be
<input name="UserName" type="text" custom-validation ng-model="user.name" ng-required="true">
than the directive gets executed and if you change the form name to something static, than your message gets displayed.
Here is a working example http://codepen.io/anon/pen/MayOBJ?editors=101.
Whenever any non-readonly input in my form changes and the form is valid, I want to do certain action. Let's say, my form looks like
<form name="form" novalidate>
<input ng-model='input.a' required/>
<input ng-model='input.b' required/>
<input value='{{output.p | number: 2}}' readonly/>
<input value='{{output.q | number: 2}}' readonly/>
</form>
Now upon any change of input, I want to do something, whenever input.a and input.b are valid. I tried $watch(input), but it didn't work. Watching all its members does, but it feels stupid. Adding ng-change to all fields feels better, but still pretty stupid (non-DRY). What's the proper way?
The other question is how to find out if the input is valid. If I had a button, I could do simply
<button ng-click="doIt()" ng-disabled="form.$invalid">
but how can I access form.$invalid in the controller (it's not contained in $scope)?
You should be able to access form.$invalid by doing
$scope.form.$invalid
See here: AngularJs can't access form object in controller ($scope)
To watch for changes in the form, you should be able to do:
$scope.$watchCollection('input')
#dave has already answered your first question, but for the second I have a solution that I consider more elegant:
In your controller you have declare an object, for example:
$scope.forms = {};
Then you form name must be nested inside that object:
<form name="forms.someForm">
...
Finally in your controller you can do things like this:
if($scope.forms.someForm.$invalid) {
...
}
I have a problem where I'm attempting to post the value of a checkbox in my model to the server and as the checkbox has not been interacted with on the form, angular seems to have not assigned it a value, when I ask for the value of the checkbox it comes back as undefined.
Here is my markup:
<div class="form-group">
<input id="templateDisable" type="checkbox" ng-model="template.disabled" />
<label for="templateDisable">Disabled</label>
</div>
And here's a reduced version of my save action on my controller:
$scope.save = function (form) {
if (form.$valid) {
var formData = new FormData();
// this is the problem line of code
formData.append("disabled", $scope.template.disabled);
// ... some other stuff
}
};
Actually, ticking then unticking the checkbox before I hit the save action results in the template.disabled property being false, which is what I would have expected without any manual intervention.
I've seen other related questions, e.g. AngularJS: Initial checkbox value not in model but surely stuff like a simple checkbox should be baked in? I shouldn't have to be writing directives to manage checkboxes surely?
This is per design. If you want a default value on your model than you should initialise it inside the controller (recommended), or make use of ng-init.
app.controller('AppController',
[
'$scope',
function($scope) {
$scope.template = {
disabled = false
};
}
]
);
<div class="form-group">
<input type="checkbox" ng-model="template.disabled" ng-init="template.disabled=false" />
<label>Disabled</label>
</div>
The following will always set the state back to "unchecked" when the page is loaded (or refreshed). In other words it will overwrite the user's actual selection whenever the page is refreshed.
<input type="checkbox" ng-model="template.disabled"
ng-init="template.disabled=false" />
If, however, you want the checkbox state set to a default state initially and you also want it to remember user interactions, then the following is what you want.
<input type="checkbox" ng-model="template.disabled"
ng-init="template.disabled = template.disabled || false" />
I am trying to understand data binding in Angularjs.
What I want to do is establish binding between pages that is if I change the input on first.html, the data should automatically change in second.html.
For example,
This is first.html:
<div ng-controller="MyCtrl">
<input type="text" ng-model="value"/><br>
{{value}}
<input type="submit" value="Second page"/>
</div>
and say second.html has only this piece of code {{value}}.
and in the .js file we have $routeProvider which takes the template url as 'second.html' & the controller is 'MyCtrl'.
So the controller is:
MyApp.controller(function($scope){
$scope.value="somevalue";
})
By doing the above way the {{value}} on the second.html is getting the value "somevalue". Which is comming from the controller.
But if I change the input value dynamically that is on the first.html, the value on the second.html is not getting that value.
My question is how do I bind the value on second.html with first.html automatically.
To understand the question clearly, Suppose there is an input field for entering text and a submit button on first.html, then I want to get the Input value of the text field of the first.html on the second.html page on Submit.
Use a service and store your model there. Gloopy already has a good example of this here: https://stackoverflow.com/a/12009408/215945
Be sure to use an object property instead of a primitive type.
If you'd rather use $rootScope, then as above, define an object, rather than a primitive:
$rootScope.obj = { prop1: "somevalue" }`
then bind to that object property in your views:
<input type="text" ng-model="obj.prop1">
{{obj.prop1}}
If you attach your data to $rootScope if will survive transitions across controllers and be part of all $scopes (prototype inheritance magic)
//**attach in controller1:**
function MyCtrl1($rootScope) {
$rootScope.recs= { rec1 : "think philosophically" };
}
//**do nothing in controller for view2:**
function MyCtrl2($scope) {
//nothing
}
//**Markup for view2: automaticall makes use of data in $routeScope**
<p>Try doing this: {{recs.rec1 }}</p>
//**markup for view1 to respond to OPs question in comments**:
<input ng-model="recs.rec1" />
Update: Creating a custom service is a more scalable and structurally sound way to handle this, but the $rootScope method is the quick and dirty way.
Update2: added view1 markup to respond to OP question, edited example to take advantage of correct advice to use object rather than primitive.
Found the Solution to what I was looking for, The solution is in the Angular docs, here is the link http://docs.angularjs.org/cookbook/deeplinking.
Some part of the example on that link answers my question.
You should user $broadcast, $emit or scope communication. Try to avoid overloading the rootScope. It is as a bad practice as saving data into the application sessions.