285

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

7
  • are you sure you are using this controller in this part of the document? can you post a minimal failing example? Commented Sep 27, 2012 at 9:38
  • 1
    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?? Commented Sep 27, 2012 at 9:51
  • 4
    @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 helps Commented Jun 11, 2013 at 9:43
  • 3
    For the record, it's spelled voila, not vuala, wolla, etc. Commented Sep 4, 2014 at 15:24
  • 3
    I think the answer you are looking for is at stackoverflow.com/a/14049482/1217913 Commented Sep 25, 2015 at 8:03

14 Answers 14

652

"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

Sign up to request clarification or add additional context in comments.

12 Comments

@Catfish 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=30m
Gracias Boss. This is true! However, it doesnt seem to be a consistent quirk as I encounter the problem only when using with Ionic framework
@user3677331 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 object
Thank you, but for me it works without .$modelValue. Maybe newer version.
Very helpful. I wasn't sure I would locate an answer to a relatively vague question but this was dead on.
|
75

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);
}

7 Comments

This only works one-way... what if you want to change the value of searchText?
This also doesn't answer the "why?" question.
what if i don't want to use any button? i need to submit on enter for instance
Saniko => To submit on enter, you must use form tag and ng-submit on it (docs.angularjs.org/api/ng.directive:ngSubmit)
@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.
|
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.

Comments

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.

4 Comments

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?
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?
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?
it should. I tried it using the code above, it updated the view.
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!

Comments

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.

Comments

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!
    };
}]);

1 Comment

thanks for the answer, however y is this working? it should ideally be working on the same scope element right and not the parent?
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?

2 Comments

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?
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 :)
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

2 Comments

Little off topic here, but 'data' is a plural term for 'datum'
@thethakuri and "datum" in hungarian is "date", so I sure wouldn't use that either :P
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.

Comments

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);
        }
    }

Comments

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

Comments

2

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

alert(this.ModelName);

Comments

-1

Provide a name property to your form control

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.