Ng-model does not update controller value
Asked Answered
K

14

283

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 ;]

Koren answered 27/9, 2012 at 9:35 Comment(7)
are you sure you are using this controller in this part of the document? can you post a minimal failing example?Zeller
Yes, 100% sure the controller is ok, that issue seems to be familiar to me... Surprisingly it works when I change the property name from searchText to search.text, any idea why??Koren
This is a great question. I ran across the same issue and following the "Update" block solved it for me as well. I'm very curious as well why this solution works - or more importantly why can't I just a $scope.var and have it updated by angular.Paraboloid
@Arthur: It's kinda not obvious but ng-model only creates a sort of speak local variable in your view, there fore if you want to keep it this way you would need to pass it into the check() function, like: check(searchText) and your controller will recognise it then. Hope it helpsKoren
Thanks, but why does your hash solution work (i.e., hash var vs. regular var)?Paraboloid
For the record, it's spelled voila, not vuala, wolla, etc.Natter
I think the answer you are looking for is at https://mcmap.net/q/36192/-what-are-the-nuances-of-scope-prototypal-prototypical-inheritance-in-angularjsBohaty
G
74

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);
}
Graptolite answered 29/9, 2012 at 8:14 Comment(7)
This only works one-way... what if you want to change the value of searchText?Kahl
This also doesn't answer the "why?" question.Kahl
what if i don't want to use any button? i need to submit on enter for instanceAnalysis
Saniko => To submit on enter, you must use form tag and ng-submit on it (docs.angularjs.org/api/ng.directive:ngSubmit)Graptolite
@Kahl - It has to do with the way scopes are inherited via ng-model and the fact that the data is a String (a primitive) and thus not updated by reference. See Will Stern's answer.Coronet
@kba I'm having the Same Problem. Angular's ng-model behaviour is if it is changed it will be changed into Controller also. but why this happens, can anyone explain, what is the issue?Darius
@HP's411 it's because a string is a primitive. When Angular assign the value it change the pointer to the value, so the controller looks at the old value because it have the old pointer to the value.Graptolite
C
649

"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

Carin answered 31/3, 2014 at 17:54 Comment(12)
This worked for me, but can you elaborate on "this quirk" in javascript?Inigo
@Inigo With prototypal inheritance, whenever you write to the child property, the reference/connection to the parent property ceases to exist. The way Angular Scopes models, if you don't have a dot, then a new property within your child scope is created. This video explains it in more detail: youtube.com/watch?v=ZhfUv0spHCY&feature=youtu.be&t=30mCarin
Gracias Boss. This is true! However, it doesnt seem to be a consistent quirk as I encounter the problem only when using with Ionic frameworkAniline
Could you explain why this happens?Territorialism
@RezaRahmati When you try to access a property on a child scope, it first checks the child...if not found, it checks the parent for that property and so on. If the child adds the property, it now no longer looks to the parent property, it will always modify the child property of the same name leaving the parent property untouched.Carin
Here it works without a dot. It mostly works without a dot and sometimes only it doesn't. Haven't noticed a pattern, have you?Cholecyst
@Cholecyst It works fine without a dot until you have a child scope that tries to talk to it (like an item within an ng-repeat for example). Say your model name was "phone" Your child scope creates "phone", then you get a scope conflict because child-scope has a "phone" variable and thus can't access "phone" on the parent scope. Whereas if the child scope creates user.phone, it will be added to the parent's user object so both scopes are pointing to the same objectCarin
Thank you, but for me it works without .$modelValue. Maybe newer version.Mitsue
Very helpful. I wasn't sure I would locate an answer to a relatively vague question but this was dead on.Proudman
Angular really should at least warn you if you attempt to bind directly to a scope property for this very reason. It seems this is confusing a lot of people (myself included).Hannahhannan
It's depressing that this rule is not shown in many places on the web. e.g. w3schools.com/angular/angular_model.asp which is incorrect.Length
That was a nasty one, tnx much!, I had input ng-model="email" and ng-model="userPassword". First one worked the second one no. After adding dots everything work but I would specify this as an Angular bug.Unmeriting
G
74

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);
}
Graptolite answered 29/9, 2012 at 8:14 Comment(7)
This only works one-way... what if you want to change the value of searchText?Kahl
This also doesn't answer the "why?" question.Kahl
what if i don't want to use any button? i need to submit on enter for instanceAnalysis
Saniko => To submit on enter, you must use form tag and ng-submit on it (docs.angularjs.org/api/ng.directive:ngSubmit)Graptolite
@Kahl - It has to do with the way scopes are inherited via ng-model and the fact that the data is a String (a primitive) and thus not updated by reference. See Will Stern's answer.Coronet
@kba I'm having the Same Problem. Angular's ng-model behaviour is if it is changed it will be changed into Controller also. but why this happens, can anyone explain, what is the issue?Darius
@HP's411 it's because a string is a primitive. When Angular assign the value it change the pointer to the value, so the controller looks at the old value because it have the old pointer to the value.Graptolite
X
61

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.

Xerxes answered 20/11, 2013 at 15:23 Comment(0)
H
46

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.

Haldane answered 21/9, 2014 at 11:32 Comment(4)
Looks like the functions attached to $scope creates a new scope(i.e. in function check(), this refers to a scope that's a child scope of $scope). Why is it so? Can you give some explanations?Numskull
why should i use 'this' instead of '$scope' in this case? because it worked using $scope in my previous project but this time same thing does not work using $scope. But 'this' has worked. Why is that?Sericin
this works, but if you say this.searchText = "something else" or even if $scope.searchText = "something else", it doesn't update the view. can you explain this problem?Originality
it should. I tried it using the code above, it updated the view.Haldane
B
14

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!

Brushwood answered 21/1, 2016 at 17:7 Comment(0)
G
11

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.

Gethsemane answered 3/4, 2013 at 2:4 Comment(0)
D
7

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!
    };
}]);
Drida answered 18/8, 2016 at 13:22 Comment(1)
thanks for the answer, however y is this working? it should ideally be working on the same scope element right and not the parent?Eductive
A
6

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?

Aeriell answered 27/9, 2012 at 11:12 Comment(2)
Hmmmm.. thanks for looking into this, I would presume that it should work... why isn't it working for me? And what's more important: Why does it work with objects and doesn't with plain string variables?? Maybe cause I refer my controllers in an in-appropriate way in the routeProvider?? Was trying to avoid globals and have put my controllers as modulename.ctrlName into controllers.js file. Could that cause a headache?Koren
I am not really sure why it doesnt work for you. If you could isolate this issue in a fiddle, I guess someone would be able to give you a better answer :)Aeriell
B
5

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

Braided answered 21/2, 2016 at 15:24 Comment(2)
Little off topic here, but 'data' is a plural term for 'datum'Laterality
@Laterality and "datum" in hungarian is "date", so I sure wouldn't use that either :PHummel
M
2

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.

Magda answered 13/3, 2014 at 7:12 Comment(0)
S
2

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);
        }
    }
Spruik answered 4/9, 2015 at 9:28 Comment(0)
D
2

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..

Drennen answered 15/8, 2017 at 13:31 Comment(0)
C
2

I was facing same problem... The resolution that worked for me is to use this keyword..........

alert(this.ModelName);

Convent answered 30/12, 2017 at 8:57 Comment(0)
D
-1

Provide a name property to your form control

Diffractive answered 23/3, 2021 at 11:49 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.