Krisalay Krisalay - 3 months ago 15
AngularJS Question

cannot read property push of 'undefined'

app.controller('ctrl', function($scope){
var socket = io.connect();

this.messages = [];

this.sendMessage = function(){
socket.emit('new message', this.input_message);
this.input_message = '';
}
socket.on('new message', function(data){
this.messages.push(data);
});


});

I am getting the data emitted by the socket, but when i try to push that data into the "messages" array, it shows the error "cannot read the property push of undefined". What wrong am I doing?

Answer

This does not work because you are calling this in the socket scope (this.messages.push(data)), which is not the same as the controller scope. That is why this.messages is undefined, because it is declared in the controller function scope, but not in the socket callback function scope. In javascript, each function has it's own scope (except arrow functions).

Suggestion

app.controller('ctrl', function($scope){
   var ctrlContext = this;
   var socket = io.connect();

   //using the this keyword here means that
   //it's in the controller function scope.
   this.messages = [];

   this.sendMessage = function(){
      socket.emit('new message', this.input_message);
      this.input_message = '';
   }

   socket.on('new message', function(data){
      //inside the callback function we have a different
      //scope, which means that this.messages will be undefined.
      //but by referencing the controller function scope, you
      //are now able to use the messages property set the
      //outer (controller function) scope.
      ctrlContext.messages.push(data);

      //as you stated, you will also have to call $scope.$apply
      //or else the digest cycle will not be triggered.
   });
});

However, if you would like to use the this keyword inside you socket callback function you could do that by using the function.prototype.bind method, which allows you set the context of the executing function.

Example

var person = {
   name: 'John Doe';
}

//Create function and bind to context person    
var changeName = function() {
   this.name = 'Peter Smith';
}.bind(person);

console.log(person); // {name: 'John Doe'}

changeName(person);

console.log(person); // {name: 'Peter Smith'}

For you solution it would be something like:

app.controller('ctrl', function($scope){
   var socket = io.connect();

   this.messages = [];

   var callback = function(data){
      this.messages.push(data);
   }.bind(this);

   socket.on('new message', callback);
});
Comments