I keep running into a problem where I need to pass through properties on $scope through an ng-click in order to have access to the property within the ng-click method. One of the issues is that $scope.rejectionMessage never appears to update.
Portion of HTML:
<label class="item item-input">
<input
type="text"
ng-model="rejectionMessage"
ng-change="logWTF(rejectionMessage)"
placeholder="Enter rejection message here."
>
</label>
<button
type="button"
class="button button-block button-outline button-assertive"
ng-click="rejectChild(rejectionMessage)"
>
REJECT
</button>
Portion of Controller:
$scope.logWTF = function(rejectionMessage){
console.log($scope.rejectionMessage) // messageA
console.log(rejectionMessage) // messageB
}
messageA ($scope.rejectionMessage) is always an empty string, but messageB is always what is currently typed in the input.
The html has only 1 controller but it still appears that I have 2 separate $scope objects. I have debugged this and looked at all of the relevant $parentScopes and have not found the rejectionMessage property hiding anywhere unexpected.
A simple solution is to just pass the property through the function on the button ng-click method but I have other functions downstream that depend on $scope.rejectionMessage. I could pass it all the way through, but takes away a lot of value of Angular. Plus I'm relatively new to Angular and feel this will be a great learning experience for me.
EDIT: I just learned that all forms with name='something' use a separate controller and thus have their own $scope.
This will happen if the input is on a child scope of the controller's scope. It's called prototypal inheritance: reading $scope.foo reads upwards in the prototypal chain but writing to $scope.foo will set it on that exact scope. So the input will set it on the child scope, the same as the button's, but not the same as the controller's.
The rule of thumb here is to set an object on the controller's scope, e.g. $scope.model = {};, and change the ng-model to model.rejectionMessage. Then you're sure you're referring to the rejectionMessage of the controller's scope. Some people say "Every ngModel should have a dot", because of this.
Related
The Controller as syntax confuses my a little bit. I understand both ways of delivering data from the controller to the view, once through Controller as syntax and the other through injecting $scope and declaring everything on top of it.
But when I am write code:
<div ng-controller="AddVMController as vm">
<input ng-model="vm.number1" type="number" />
<input ng-model="vm.number2" type="number" />
<button class="btn btn-default" ng-click="vm.add()">Add</button>
<h3>{{vm.result}}</h3>
</div>
What are the relations between $scope and vm? One is contained in the other?
So you have a controller object bound to your HTML view. There is also $scope instance which is a descendant of the $rootScope. $scope and controller are different things but they both are always there.
Here is a little confusing thing, even though if you use controllerAs - there is anyway $scope involved behind the scene. In fact, vm is only available in the view because there is a reference $scope.vm = controllerInstance. But with controllerAs syntax you deal with sort of prefix for all your view variables which helps a lot if you have nested scopes.
Here is my Plunk
I need to understand how AngularJS handles scope variable and method part of given scope.
Below is my controller code
var app = angular.module('plunker', []);
app.controller('MainCtrl', ["$scope", function($scope) {
$scope.name = "";
$scope.getNameLength = function(){
return $scope.name.length;
}
}]);
Here is my html body (just keeping my div for simplicity)
<body ng-controller="MainCtrl">
<div>
Enter Your Name :<input type="text" ng-model="name">
<br>
{{ "Your entered name whoes length is = " + getNameLength() }}
</div>
</body>
As and when i enter something in the text box, the getNameLength() is called and the DOM is updated to reflect the name's length.
As long as the method being referenced in a directive the method is called whenever there is a change in the name.
Here is my doubt:
Why angular calling all the method in the scope (which are being referenced in directive) whenever there is a change in view model? is it possible to disable this behavior? Are there any performance implication in this?
If you are concerned that Angular is calling your method too many times and you want to limit the execution, you can always use the ngModelOptions directive and pass in debounce. You can see the documentation on the AngularJS page. For example:
<input type="text" ng-model="name" ng-model-options="{debounce: 500}">
Will only update the model once the model has stopped updating for 500 milliseconds. You could also use something like ng-model-options="{updateOn: 'blur'}" to only update the model after the field has lost focus.
As far as performance is concerned, if it is something simple like calculating a string's length, you shouldn't have too much to worry about. If it is something more complex, you could run into issues.
Why angular calling all the method in the scope (which are being
referenced in directive) whenever there is a change in view model?
I do not see a custom directive in your example, but Angular directives will either inherit scope properties from their parent, use the parent scope, or have an isolate scope.
If you do not have an isolate scope, it will look for the property in the parent's scope unless you override it.
Because you have an Angular expression (the {{ and }} surrounds it), Angular makes a watcher for whatever is in the expression. When it detects a watched variable or object has changed, it will update all things dependant on it.
is it possible to disable this behavior?
Yes, indeed, as mentioned by 'YOU' in the comment to your question, you can use a 'one time binding'.
Example:
{{normalBinding}}
{{::oneTimeBinding}}
Are there any performance implication in this?
Yes, the more bindings you have, the more watchers, the more the digest cycles will take, the longer it will take for your application to reflect changes. This is a concern for big applications.
More information about the scope, and watchers, can be found here.
I have a simple form that looks like this:
<form name="myForm">
<input name="field" ng-model="item.field"></input>
<button ng-click="save()">Save</button>
</form>
In theory, I should be able to change things like validity of fields in the controller based on the form and field, as such:
$scope.save = function(item) {
item.$save();
$scope.myForm.field.$setValidity("some-error",false);
};
The problem is that directives at various levels get in the way and may set up multiple child scopes. So while item.field is properly rendered and linked, my ability to do anything from the controller is very limited, e.g. if I wrap my partial in a fancy "loading" directive that hides it and shows "loading" until done.
<loading>
<form name="myForm">
<input name="field" ng-model="item.field"></input>
<button ng-click="save()">Save</button>
</form>
</loading>
Now my controller that wanted to directly set validity no longer has access to $scope.myForm. Truth is, with possible directives, it never can safely rely on access to $scope.myForm; only a directive can!
#Beyers recommends passing the form as part of the save() call, as <button ng-click="save(myForm)">Save</button>, which definitely works, but gets cumbersome.
Is there a real Angular way to do this cleanly? Should I be resetting to $pristine inside my controller directly, or is there some other thing to do?
UPDATE:
I am beginning to wonder if this is the right approach at all. Should my controller be doing this like $setValidity on a form? Shouldn't it take input from the view and modify business objects (item) on that basis, as well as interact with services? How does angular know to reset ng-dirty or ng-invalid for its own validations?
The problem is that directives at various levels get in the way and may set up multiple child scopes.
You're hitting the standard if you don't have a dot in your model, you're doing it wrong issue. This isn't just true for ng-model, but also for form name.
A solution is to make sure you have an object in your controller for forms, such as forms:
$scope.forms = {};
In the template use forms.<formName>:
<form name="forms.myForm">
...
</form>
And then in the controller code to access the form, use $scope.forms.<formName>
$scope.forms.myForm.field.$setValidity("some-error" ,false);
And your updated question:
Should my controller be doing this like $setValidity on a form
I would address this on a case-by-case basis in order to KISS. Validity is often the result of interacting with services, and can be related to business objects/rules. A little bit of code in the controller interacting with validation I would say is ok, but if your controller is getting a bit long with a quite a few responsibilities, then it might then be a good idea to try to move some of the logic into custom directives on the elements that require ngModel. These can then accept parameters/functions via attributes, set by the controller, if required.
I just grabbed the form field by Id last time I did this and it worked fine.
Using the ID, you "Angularize" it and then you can access it's model etc...
Like this:
//you will need to add an id to your field.
var myInput = angular.element(document.querySelector('#myInput'));
var ngModelCtrl = myInput.controller('ngModel');
ngModelCtrl.$setValidity("some-error",false);
The angular docs say that you should only do this when you have to, they prefer you to use the ngModel.$validators pipline. Unfortunately I'm not sure how to use that myself so can't help you there.
There is clearly something fundamental im not yet understanding.
Im trying to make user of the Modal module in Angular Ui.Bootstrap but im finding that my clicks are not activating the open() function -- So boiling it down to a very simple testcase, as below, im not seeing any calls when the ng-click points to a function (alert or console.log), but does work when the ng-click points to something which is just an expression
Why is the alert not called in the first example?
<div data-ng-app>
<button data-ng-click="alert('Message 1');">
ngClick -- not working, why not?
</button>
<button onclick="alert('Message 2');">
Plain onclick
</button>
<button data-ng-click="count = (count + 1)">
But this works, why ???
</button>
count: {{count}}
</div>
http://jsfiddle.net/H2wft/1/
ng-click is meant for use with either a function in the current scope (so for example $scope.alert = window.alert would solve the problem of not being able to alert there) or an angular expression. it looks like angular does not allow you to use global scope methods in there (it might be looking them up in the current $scope, from which they are missing).
ng-click expects an angular expression. Internally, angular is using the $parse service to evaluate the expression in ng-click="expression".
An angular expression is not the same as regular javascript code. $parse uses string parsing to interpret the expression and it restricts your access to variables, functions, and objects to just those which are properties of the $scope object, or properties of any $parent scope objects which happen to be available further up the prototypical inheritance chain.
So in theory you could gain access to globals like this:
$scope.window = window;
$scope.alert = alert;
... and then in your template do ng-click="window.alert('hello!')"
...or... ng-click="alert('hello!')"
Or you could do this just once:
$rootScope.window = window;
$rootScope.alert = alert;
Then any scope that prototypically inherits from $rootScope will also have access to window and alert.
... but there are good reasons never to do either of the above, except possibly for debugging purposes. That's not to say that decorating the $rootScope is always a bad idea, there is at least one special case where decorating angular's scope can accomplish something that would be very difficult to do otherwise.
You cannot execute that kind of code in ng directives. They look for content in your local scope.
If you do this in your controller:
$scope.alert = function(){
alert('alerted!');
};
and then in your template
ng-click="alert()"
It will execute the javascript alert properly.
For that to work, you have to define alert on your $scope:
$scope.alert = function(text) {
alert(text);
};
Relevant piece from documentation:
AngularJS restricts access to the Window object from within
expressions since it's a known way to execute arbitrary Javascript
code.
https://docs.angularjs.org/error/$parse/isecwindow
https://docs.angularjs.org/guide/expression
<div class="example2" ng-controller="ExampleController">
Name: <input ng-model="name" type="text"/>
<button ng-click="greet()">Greet</button>
<button ng-click="window.alert('Should not see me')">Won't greet</button>
</div>
The docs specify that you can expost the form to the scope with name. so lets say I have this form:
<form name="myForm" ng-submit="submit()">
<input type="text" ng-model="somemodel"/>
<button ng-submit="submit()"></button>
</form>
but I'm trying to access the form through the controller with $scope.myForm and fail:
$scope.submit = function(){
console.log($scope.myForm) // or form[0] or scope.myForm[0] etc..
if(!$scope.myForm.$dirty){
//do this and that
}
//do something
}
they all fails as undefined. how is this done? ultimately I would like to also call $setPristine() from the controller. but I just can't find out where the form is hiding. is he even available to the controller or just in the view scope?
using angular 1.2.5
EDIT: this doesn't work also when the form name is with the dot notation: myForms.myform
Another edit: after forking the suggested plunker I found out that the form doesn't exists on the $scope before submitting, but does exist after.
So I guess the refined question should be: is this the expected behavior? Is there a workaround this (in the ctrl and not in a directive)?
If this is expected and no workaround so I'll move the custom validation checks to specific directives - since in the directives (if they require:"^form") the form is available before submit.
Thanks!
Answer this issue seems to be when the form is added to the scope. from the plunkers it's obvious that not in the beginning, but attached later, after the Parent Ctrl got loaded. can we control the loading order? I don't know. seems the place for this kind of form scope manipulation should be a directive and not the ctrl
One reason you might not be able to see the form on your Controller $scope is if your <form> is inside a directive that introduces a new child scope. e.g. if the form is inside an ng-if or ng-switch.
In other words, in the example below, myForm ends up getting published to the child scope introduced by the ng-if and you won't be able to see it on the MyController $scope.
<div ng-controller="MyController">
<div ng-if="true">
<form name="myForm"></form>
</div>
</div>
One way to deal with this is to use dot-notation to publish the form into an object wrapper instead of putting it directly on the scope.
<div ng-controller="MyController">
<div ng-if="true">
<form name="myStuff.myForm"></form>
</div>
</div>
And in MyController, make sure an object is there so it will publish to it instead of the child scope.:
$scope.myStuff = {};
Take a look at this egghead.io video that does a good job explaining this: https://www.youtube.com/watch?v=DTx23w4z6Kc
If you change the reference from $scope.form to $scope.myForm, then that is defined, as in this plunker. Unless you have a parent form called myForms that you haven't mentioned, you won't be able to access it as $scope.myForms.myform.
Edit: the refined question asks whether it's expected behaviour that on the form's parent controller that the form is undefined when the controller is first run. The answer is yes, it's not added until later.
The question suggests that the form is only defined when the form is submitted. This isn't quite accurate. The form is added on the scope before this: there is nothing special about the submit function. You should be able to quite happily call form functions in response to other button presses. In the parent controller you can have
$scope.makeTheFormPristine = function() {
$scope.myForm.$setPristine();
}
As can be seen in the following Plunkr:
http://plnkr.co/edit/dDVrez9UP1GYRIDrUxJs?p=preview
Edit: from the comments, if you want a variable in the parent scope, say notification, to depend on a property of the form, you can setup a manual watcher:
$scope.$watch('myForm.$pristine', function(newValue, oldValue) {
if (newValue) {
$scope.notification = 'Pristine';
} else {
$scope.notification = 'Not pristine'
}
});
You can see this at http://plnkr.co/edit/2NMNVAfw8adlpFRGuSoC?p=preview . However, I would be a bit careful about putting too much "view" logic into the controller. It might be better placed in the template, or maybe event a custom directive. This is probably beyond the scope of this question, and maybe better on https://codereview.stackexchange.com/.
Also: I don't think you need the 2 ng-submit attributes, both on the button and the form, but I suspect that isn't related to this issue.
Oops. Just re-read your Question and realized that you are using the wrong scope path. As already indicated by Matt, the form will be pusblished to $scope.myForm and not $scope.form.myForm.