Ng-model does not update controller value - javascript

Probably silly question, but I have my html form with simple input and button:
<input type="text" ng-model="searchText" />
<button ng-click="check()">Check!</button>
{{ searchText }}
Then in the controller (template and controller are called from routeProvider):
$scope.check = function () {
console.log($scope.searchText);
}
Why do I see the view updated correctly but undefined in the console when clicking the button?
Thanks!
Update:
Seems like I have actually solved that issue (before had to come up with some workarounds) with:
Only had to change my property name from searchText to search.text, then define empty $scope.search = {}; object in the controller and voila... Have no idea why it's working though ;]

"If you use ng-model, you have to have a dot in there."
Make your model point to an object.property and you'll be good to go.
Controller
$scope.formData = {};
$scope.check = function () {
console.log($scope.formData.searchText.$modelValue); //works
}
Template
<input ng-model="formData.searchText"/>
<button ng-click="check()">Check!</button>
This happens when child scopes are in play - like child routes or ng-repeats.
The child-scope creates its own value and a name conflict is born as illustrated here:
See this video clip for more: https://www.youtube.com/watch?v=SBwoFkRjZvE&t=3m15s

Controller as version (recommended)
Here the template
<div ng-app="example" ng-controller="myController as $ctrl">
<input type="text" ng-model="$ctrl.searchText" />
<button ng-click="$ctrl.check()">Check!</button>
{{ $ctrl.searchText }}
</div>
The JS
angular.module('example', [])
.controller('myController', function() {
var vm = this;
vm.check = function () {
console.log(vm.searchText);
};
});
An example: http://codepen.io/Damax/pen/rjawoO
The best will be to use component with Angular 2.x or Angular 1.5 or upper
########
Old way (NOT recommended)
This is NOT recommended because a string is a primitive, highly recommended to use an object instead
Try this in your markup
<input type="text" ng-model="searchText" />
<button ng-click="check(searchText)">Check!</button>
{{ searchText }}
and this in your controller
$scope.check = function (searchText) {
console.log(searchText);
}

In Mastering Web Application Development with AngularJS book p.19, it is written that
Avoid direct bindings to scope's properties. Two-way data binding to
object's properties (exposed on a scope) is a preferred approach. As a
rule of thumb, you should have a dot in an expression provided to the
ng-model directive (for example, ng-model="thing.name").
Scopes are just JavaScript objects, and they mimic dom hierarchy. According to JavaScript Prototype Inheritance, scopes properties are separated through scopes. To avoid this, dot notation should use to bind ng-models.

Using this instead of $scope works.
function AppCtrl($scope){
$scope.searchText = "";
$scope.check = function () {
console.log("You typed '" + this.searchText + "'"); // used 'this' instead of $scope
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app>
<div ng-controller="AppCtrl">
<input ng-model="searchText"/>
<button ng-click="check()">Write console log</button>
</div>
</div>
Edit: At the time writing this answer, I had much more complicated situation than this. After the comments, I tried to reproduce it to understand why it works, but no luck. I think somehow (don't really know why) a new child scope is generated and this refers to that scope. But if $scope is used, it actually refers to the parent $scope because of javascript's lexical scope feature.
Would be great if someone having this problem tests this way and inform us.

I had the same problem and it was due to me not declaring the blank object first at the top of my controller:
$scope.model = {}
<input ng-model="model.firstProperty">
Hope this will works for you!

I came across the same issue when dealing with a non-trivial view (there are nested scopes). And finally discovered this is a known tricky thing when developing AngularJS application due to the nature of prototype-based inheritance of java-script. AngularJS nested scopes are created through this mechanism. And value created from ng-model is placed in children scope, not saying parent scope (maybe the one injected into controller) won't see the value, the value will also shadow any property with same name defined in parent scope if not use dot to enforce a prototype reference access. For more details, checkout the online video specific to illustrate this issue, http://egghead.io/video/angularjs-the-dot/ and comments following up it.

Have a look at this fiddle http://jsfiddle.net/ganarajpr/MSjqL/
I have ( I assume! ) done exactly what you were doing and it seems to be working. Can you check what is not working here for you?

Since no one mentioned this the problem can be resolved by adding $parent to the bound property
<div ng-controller="LoginController">
<input type="text" name="login" class="form-control" ng-model="$parent.ssn" ng-pattern="/\d{6,8}-\d{4}|\d{10,12}/" ng-required="true" />
<button class="button-big" type="submit" ng-click="BankLogin()" ng-disabled="!bankidForm.login.$valid">Logga in</button>
</div>
And the controller
app.controller("LoginController", ['$scope', function ($scope) {
$scope.ssn = '';
$scope.BankLogin = function () {
console.log($scope.ssn); // works!
};
}]);

For me the problem was solved by stocking my datas into an object (here "datas").
NgApp.controller('MyController', function($scope) {
$scope.my_title = ""; // This don't work in ng-click function called
$scope.datas = {
'my_title' : "",
};
$scope.doAction = function() {
console.log($scope.my_title); // bad value
console.log($scope.datas.my_title); // Good Value binded by'ng-model'
}
});
I Hop it will help

I just had this very issue using a root_controller bound to the body-element. Then I was using ng-view with the angular router. The problem is that angular ALWAYS creates a new scope when it inserts the html into ng-view element. As a consequence, my "check" function was defined on the parent scope of the scope that was modified by my ng-model element.
To solve the problem, just use a dedicated controller within route-loaded html content.

You can do that to enable search in ng-keypress enter for input text and in ng-click for the icon:
<input type="text" ng-model="searchText" ng-keypress="keyEnter(this,$event)" />
<button ng-click="check(searchText)">Check!</button>
in the controller
$scope.search = function (searchText) {
console.log(searchText);
}
$scope.keyEnter = function (serachText,$event) {
var keyCode = $event.which || $event.keyCode;
if (keyCode === 13) {//KeyCode for Enter key
console.log(searchText);
}
}

I had the same problem.
The proper way would be setting the 'searchText' to be a property inside an object.
But what if I want to leave it as it is, a string? well, I tried every single method mentioned here, nothing worked.
But then I noticed that the problem is only in the initiation, so I've just set the value attribute and it worked.
<input type="text" ng-model="searchText" value={{searchText}} />
This way the value is just set to '$scope.searchText' value and it's being updated when the input value changes.
I know it's a workaround, but it worked for me..

I was facing same problem...
The resolution that worked for me is to use this keyword..........
alert(this.ModelName);

Provide a name property to your form control

Related

Unable to call $scope function in ng-repeat

I have this function showPopupSelectTopic(subject) to call in my ng-repeat html code. But it does not work at all.
<div style="width:100%;" ng-controller="manageStudyCtrl">
<div class="div-subject" ng-repeat="subject in dataSubject" ng-click="showPopupSelectTopic(subject)">
<div class="round-button-subject-2">
<div class="subject-name-2 subject-eng" style="color:{{subject.subject_code.colour_code}}">
{{subject.subject_code.short_name}}
<div>{{subject.avg_overall_coverage | number : 0}}%</div>
</div>
<circular-progress
value = "subject.avg_overall_coverage"
max="100"
orientation="1"
radius="36"
stroke="8"
base-color="#b9b9b9"
progress-color="{{subject.subject_code.colour_code}}"
iterations="100"
animation="easeInOutCubic"
></circular-progress>
</div>
</div>
</div>
I want to call my showPopupSelectTopic(subject)in my controller so that I can make popup and manipulate the data.
I have done outside from ng-repeatand its working perfectly. However if I used in ng-repeat then it would not execute as expected. How to solve this issue?
My controller:
angular.module('manageStudy', [])
.controller('manageStudyCtrl', function($scope,){
$scope.showPopupSelectTopic = function(subject) {
alert(subject.chapter_id);
};
});
That's not possible due to every ng-repeat creating its own child scope. That being said, ever functio invocation will lead the child to copy some variables into its own scope. You'd have to use their parentscope or refere to the origin $scope of your controller.
ng-click="$parent.showPopupSelectTopic(subject)"
This should solve the problem. However, it's kinda dirty. A better solution would be to return your parents scope and use it in every child scope just like that. So declare a function inside of your controller (e.g. $scope.getScope) and let it simply return its $scope. Afterwards you'll be able to access it properly.
$scope.getScope = function() {
return $scope;
}
ng-click = "getScope().showPopupSelectTopic(subject)"
ng-repeat works only with an iterate able object, like array or collection. Before you open the div where you intent to repeat, the iterate able object must be in ready in the scope. Try using ngInit instead of ngClick to initialize the array before attempting to ngRepeat

Change parent controller model through directive $watch using controllerAs syntax

I'm new to controllerAs syntax of angular and just trying to understand how it works with directive. I've created one directive for password validation. I want to make some flag true based on conditions and those will be used in parent template for displaying error messages. I'm not getting how can I achieve this!
JSFiddle
VIEW
<div ng-app="myapp">
<fieldset ng-controller="PersonCtrl as person">
<input name="emailID" type="text" ng-model="person.first" >
<input name="pass" type="password" ng-model="person.pass" password-validator>
<p ng-show="person.showMsg">Password validation message here.</p>
</fieldset>
</div>
Directive
myapp.directive('passwordValidator',function() {
return {
controller : PasswordCtrl,
controllerAs : 'dvm',
bindToController : true,
require : ['ngModel','passwordValidator'],
link : function(scope,ele,attrs,ctrls) {
var person = ctrls[1];
var ngModelCtrl = ctrls[0];
scope.$watch(function() {
return ngModelCtrl.$modelValue;
},function(newVal) {
if(newVal!='') {
person.showMsg = true;
} else {
person.showMsg = false;
}
console.log(person.showMsg);
});
}
}
function PasswordCtrl() {
}
});
Specially I want to understand why and how below watch is working fine!
// Why this below is also working, can anyone explain what's going behind!!
scope.$watch('person.pass',function(newVal) {
console.log("Watch fires");
});
This is just for learning purpose so please explain how controllerAs and bindToController works!
I know this was not part of your question, I will get to it, but using directive 'ng-controller' is an anti-pattern. If if are interested why I can explain in a separate post but in short it makes code much harder to follow.
Now, to get to the heart of your question.
From reading the Angular documentation for bindToController it would appear that if you are not also creating an isolated scope, i.e. scope: true or scope: {} it does not do anything.
Personally I have never used it before and does not seem particularly useful.
Using ng-controller is in essence adding a property to the current scope with that controller object.
So:
<fieldset ng-controller="PersonCtrl as person">
Is effectively saying, (in a contrived way):
$scope.person = new PersonCtrl();
Your directive passwordValidator which is using the controllerAs syntax within it is basically doing:
$scope.dvm= new PasswordCtrl();
In this case you effectively have a scope object that looks like:
$scope = {
person = new PersonCtrl(),
dvm: new PasswordCtrl()
}
Your person controller and dvm controller are sibling objects.
Within your passwordValidator directive you are requiring in its controller, which is the dvm object. Using that dvm object are you setting person.showMsg which is the same as doing:
$scope.dvm.person.showMsg = <value>
The dvm object does not have a way to access the person object, as they are siblings, on the $scope. So you need to use the $scope itself to access the person object. You would need to do:
$scope.person.showMsg = <value>
Although this assumes that person exists on the scope, which is a dangerous assumption.
your example is kinda messy but ill try to answer your questions.
// Why this below is also working, can anyone explain what's going behind!!
scope.$watch('person.pass',function(newVal) {
console.log("Watch fires");
});
this works because your directive is using SAME scope and variables on it as its parent controller.
this.checkDirCtrl = function() {
console.log($scope.dvm);
}
this is undefined because you are using controllerAs on SAME scope so new variable called dvm is not created and all your dvm controller variables are initialized on parent scope.
That means that you dont need to 'inject' person controller into directive because you already have controller instance on directive scope. So just set your variable 'showMsg' like this and it will work like a magic!
if(newVal!='') {
scope.person.showMsg = true;
} else {
scope.person.showMsg = false;
}
console.log(scope.person.showMsg);
I made a fiddle for you: JSFiddle

Adapting non-standard properties in angularjs

I have an existing model compiled from java to javascript using GWT 2.7. Each property in this model has a getValue() and a setValue() method. The method names are not mangled.
I want to use these properties in {{}} expressions, specifically for binding using ngModel. I'm using the "getterSetter" option and a function in the $scope that wraps getValue()/setValue() in an angularJS getterSetter function.
The problem is that I don't want to duplicate this function in each scope. Is there any way of accessing a global function in an angularJS expression?
From the docs:
Angular expressions do not have access to global variables like window, document or location. This restriction is intentional. It prevents accidental access to the global state – a common source of subtle bugs.
Can you just attach the function to the model prototype? That way you just can access it wherever you have an instance of the model.
Alternatively, you could create a wrapper class (basically a view-model) that has the interface you need, and delegates to the real model instance.
After quite some searching and experimenting I found that I can use the directive's controller to add the function to the scope.
.directive('wrapped', function() {
return {
controller: function($scope) {
$scope.wrapper = function(prop) {
// turns signal into getterSetter
return function(newVal) {
if (angular.isDefined(newVal)) {
prop.setValue(newVal);
}
return prop.getValue();
}
}
},
scope : {
model : '&'
},
templateUrl : 'template.html',
}
})
A function in the scope can then be used in expressions in the template.
<p class="field {{model().status().isValid() ? 'valid' : 'invalid'}}">
<span class="label">{{label}}:</span>
<input type="text" ng-model="wrap(model())" ng-model-options="{getterSetter: true}" ></input>
</p>
I would love to hear from more experienced AngularJS programmers if there is a more idiomatic way to write this.

In AngularJS, how does $scope get passed to scope?

I'm a bit confused with the use of $scope in controllers and of scope in directives. Please verify if my understanding is correct (and also provide some alternative ways how to do this).
Let's say I have an html:
<div ng-controller="app1_Ctrl">
.
.
.
<input type="text" ng-model="value"/>
<input type="checkbox" />
<button ng-click="submit()"></button>
</div>
And my main.js
(function() {
angular.module('mainApp', ['app1']);
})();
And my app1 looks like this (based on official AngularJS documentation here)
(function() {
var app = angular.module('app1', []);
app.controller('app1_Ctrl', ["$scope", function($scope) {
.
.
.
}]);
app.directive('app1_Dir1', [function() {
function link(scope, element, attr) {
scope.$watch(attr.someAttrOfCheckBox, function() {
// some logic here
});
function submit() {
// some logic here
}
}
return link;
}]);
})();
How does $scope.value passed in scope in directive so that I can do some manipulations there? Will ng-click fire the function submit() in the directive link? Is it correct to use scope.$watch to listen for an action (ticked or unticked of course) in checkbox element?
Many thanks to those who can explain.
By default, directive scope is controller $scope; but it means the directive is directly dependent on your controller and you need a different controller for each instance of the directive you want to use. It is usually considered a best practice to isolate your directive scope and specifically define the variables you wish to pass it from your controller.
For this, you will need to add a scope statement to your directive :
scope {
label :'#',
context : '=',
function : '&'
}
and update your view :
<my-directive label="labelFromController" context="ctxtFromController" function="myFunction()" ></my-directive>
The symbols denote the kind of thing you wish to pass through : # is for one-way binding (as a string in your directive), = is for two-way binding of an object (which enables the directive to update something in your controller), and & is for passing a function.
There are a lot of additional options and subtleties that are best explained by the Angular doc https://docs.angularjs.org/guide/directive. There are also some nice tutorials out there (e.g. http://www.sitepoint.com/practical-guide-angularjs-directives/)
Your submit() function is not attached to anything, so you won't be able to call if from your viewer. You need to define it as scope.submit = function() ... in your link function if you wish to access it.
You can use $watch for this kind of thing, but there are usually other more elegant ways to achieve this by leveraging the fact that angular already "watches" the variables it is aware of and monitors any changes he can (this can be an issue when some external service changes data for exemple, because angular cannot listen to events it is not made aware of). Here, you can probably simply associate the ng-model directive to your input checkbox to store its true/fale (checked/unchecked) value, and the ng-change or ng-click directives to act on it. The optimal solution will mostly depend on the exact nature of your business logic.
Some additional thoughts :
The HTML insides of your directive should be packaged in an inline template field, or in a separate HTML file referenced by the templateUrl field in your directive.
In your HTML code above, your directive is not referenced anywhere. It should be an element, attribute or class (and your directive definition should reflect the way it can be called, with the restrict field). Maybe you have omitted the line containing the directive HTML, but as it stands, your directive doesn't do anything.
To my knowledge, you don't need to return link. Think of it as the "body" of your directive, where you define the variables and functions you will call in the HTML.
Your directive doesn't actually need HTML code and the above thoughts might be irrelevant if you are going in a different direction, but encapsulating some kind of view behaviour that you want to reuse is probably the most common use of directives.

AngularJs "controller as" syntax - clarification?

I read about the new syntax from angularJS regarding controller as xxx
The syntax InvoiceController as invoice tells Angular to instantiate
the controller and save it in the variable invoice in the current
scope.
Visualization :
Ok , so I wont have the parameter $scope in my controller and the code will be much cleaner in the controller.
But
I will have to specify another alias in the view
So Until now I could do :
<input type="number" ng-model="qty" />
....controller('InvoiceController', function($scope) {
// do something with $scope.qty <--notice
And now I can do :
<input type="number" ng-model="invoic.qty" /> <-- notice
....controller('InvoiceController', function() {
// do something with this.qty <--notice
Question
What is the goal of doing it ? removing from one place and add to another place ?
I will be glad to see what am I missing.
There are several things about it.
Some people don't like the $scope syntax (don't ask me why). They say that they could just use this. That was one of the goals.
Making it clear where a property comes from is really useful too.
You can nest controllers and when reading the html it is pretty clear where every property comes.
You can also avoid some of the dot rule problems.
For example, having two controllers, both with the same name 'name', You can do this:
<body ng-controller="ParentCtrl">
<input ng-model="name" /> {{name}}
<div ng-controller="ChildCtrl">
<input ng-model="name" /> {{name}} - {{$parent.name}}
</div>
</body>
You can modify both parent and child, no problem about that. But you need to use $parent to see the parent's name, because you shadowed it in your child controller. In massive html code $parent could be problematic, you don't know where that name comes from.
With controller as you can do:
<body ng-controller="ParentCtrl as parent">
<input ng-model="parent.name" /> {{parent.name}}
<div ng-controller="ChildCtrl as child">
<input ng-model="child.name" /> {{child.name}} - {{parent.name}}
</div>
</body>
Same example, but it is much much clearer to read.
$scope plunker
controller as plunker
The main advantage with controller as syntax I see is that you can work with controllers as classes, not just some $scope-decorating functions, and take advantage of inheritence. I often run into a situation when there's a functionality which is very similar to a number of controllers, and the most obvious thing to do is to create a BaseController class and inherit from it.
Even though there's is $scope inheritence, which partially solves this problem, some folks prefer to write code in a more OOP manner, which in my opinion, makes the code easier to reason about and test.
Here's a fiddle to demonstrate: http://jsfiddle.net/HB7LU/5796/
I believe one particular advantage is clear when you have nested scopes. It will now be completely clear exactly what scope a property reference comes from.
Source
Difference between Creating a controller using the $scope object and Using the “controller as” syntax and vm
Creating a controller using the $scope object
Usually we create a controller using the $scope object as shown in the listing below:
myApp.controller("AddController", function ($scope) {
$scope.number1;
$scope.number2;
$scope.result;
$scope.add = function () {
$scope.result = $scope.number1 + $scope.number2;
}
});
Above we are creating the AddController with three variables and one behaviour, using the $scope object controller and view, which talk to each other. The $scope object is used to pass data and behaviour to the view. It glues the view and controller together.
Essentially the $scope object performs the following tasks:
Pass data from the controller to the view
Pass behaviour from the controller to the view
Glues the controller and view together
The $scope object gets modified when a view changes and a view gets modified when the properties of the $scope object change
We attach properties to a $scope object to pass data and behaviour to the view. Before using the $scope object in the controller, we need to pass it in the controller function as dependencies.
Using the “controller as” syntax and vm
We can rewrite the above controller using the controller as syntax and the vm variable as shown in the listing below:
myApp.controller("AddVMController", function () {
var vm = this;
vm.number1 = undefined;
vm.number2=undefined;
vm.result =undefined;
vm.add = function () {
vm.result = vm.number1 + vm.number2;
}
});
Essentially we are assigning this to a variable vm and then attaching a property and behaviour to that. On the view we can access the AddVmController using controller as syntax. This is shown in the listing below:
<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>
Ofcourse we can use another name than “vm” in the controller as syntax. Under the hood, AngularJS creates the $scope object and attaches the properties and behaviour. However by using the controller as syntax, the code is very clean at the controller and only the alias name is visible on the view.
Here are some steps to use the controller as syntax:
Create a controller without $scope object.
Assign this to a local variable. I preferred variable name as vm, you can choose any name of your choice.
Attach data and behaviour to the vm variable.
On the view, give an alias to the controller using the controller as syntax.
You can give any name to the alias. I prefer to use vm unless I’m not working with nested controllers.
In creating the controller, there are no direct advantages or disadvantages of using the $scope object approach or the controller as syntax. It is purely a matter of choice, however, using the controller as syntax makes the controller’s JavaScript code more readable and prevents any issues related to this context.
Nested controllers in $scope object approach
We have two controllers as shown in the listing below:
myApp.controller("ParentController", function ($scope) {
$scope.name = "DJ";
$scope.age = 32;
});
myApp.controller("ChildController", function ($scope) {
$scope.age = 22;
$scope.country = "India";
});
The property “age” is inside both controllers, and on the view these two controllers can be nested as shown in the listing below:
<div ng-controller="ParentController">
<h2>Name :{{name}} </h2>
<h3>Age:{{age}}</h3>
<div ng-controller="ChildController">
<h2>Parent Name :{{name}} </h2>
<h3>Parent Age:{{$parent.age}}</h3>
<h3>Child Age:{{age}}</h3>
<h3>Country:{{country}}</h3>
</div>
</div>
As you see, to access the age property of the parent controller we are using the $parent.age. Context separation is not very clear here. But using the controller as syntax, we can work with nested controllers in a more elegant way. Let’s say we have controllers as shown in the listing below:
myApp.controller("ParentVMController", function () {
var vm = this;
vm.name = "DJ";
vm.age = 32;
});
myApp.controller("ChildVMController", function () {
var vm = this;
vm.age = 22;
vm.country = "India";
});
On the view these two controllers can be nested as shown in the listing below:
<div ng-controller="ParentVMController as parent">
<h2>Name :{{parent.name}} </h2>
<h3>Age:{{parent.age}}</h3>
<div ng-controller="ChildVMController as child">
<h2>Parent Name :{{parent.name}} </h2>
<h3>Parent Age:{{parent.age}}</h3>
<h3>Child Age:{{child.age}}</h3>
<h3>Country:{{child.country}}</h3>
</div>
</div>
In the controller as syntax, we have more readable code and the parent property can be accessed using the alias name of the parent controller instead of using the $parent syntax.
I will conclude this post by saying that it’s purely your choice whether you want to use the controller as syntax or the $scope object. There is no huge advantage or disadvantage to either, simply that the controller as syntax you have control on the context is a bit easier to work with, given the clear separation in the nested controllers on the view.
I find the main advantage is a more intuitive api since the methods/properties are associated with the controller instance directly and not the scope object. Basically, with the old approach, the controller becomes just a decorate for building up the scope object.
Here are some more info on this: http://www.syntaxsuccess.com/viewarticle/551798f20c5f3f3c0ffcc9ff
From what I've read, $scope will be removed in Angular 2.0, or at least how we view the use of $scope. It might be good to start using controller as as the release of 2.0 nears.
Video link here for more discussion on it.

Categories

Resources