Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript AngularJS Basics (1.x) Services in Angular Using Services To Get Data

Sean Flanagan
Sean Flanagan
33,235 Points

What's wrong with my code?

Hi. My code (without /mock/json) doesn't work. Instead I get a ton of error messages in the console.

I would be grateful if someone could take a look.

Here's my workspace:

https://w.trhou.se/44eetu1xt1

Joshua Eagle
Joshua Eagle
9,778 Points

Hey Sean,

You did not properly close your controller so that you can method chain the service after. Whenever you create a module. you can define a controller, a service, a directive, etc. in order to help you create what you need for that module

In your app.js file:

angular.module("todoListApp", [])

.controller('mainCtrl', function($scope, dataService) {
  $scope.learningNgChange = function() {
    console.log("An input changed!");
  };

  $scope.helloWorld = dataService.helloWorld;

  dataService.getTodos(function(response) {
    console.log(response.data);
    $scope.todos = response.data;
  });  
}) // This is what I added, you are method chaining server, right after controller.
.service("dataService", function($http) {
  this.helloWorld = function() {
    console.log("This is the data service's method!");
  };

  this.getTodos = function(callback) {
    $http.get("mock/todos.json")
    .then(callback)
  }
});

Look for my comment, I highlighted the syntax error.

2 Answers

Joshua Eagle
Joshua Eagle
9,778 Points

Hey Sean,

In your original code, the comment I added (everything on that line is what I added). You were missing }).

To answer your other question, I believe your reference to $scope inside of the callback function of dataService.getTodos in your controller is undefined. The reason for this is when you reference variables from the parent class in a callback, the variable loses scope.

Short Answer:

inside of your controller modify this code:

dataService.getTodos(function(response) {
    console.log(response.data);
    $scope.todos = response.data;
  });

to

dataService.getTodos(function(response) {
    console.log(response.data);
    this.todos = response.data;
  }.bind($scope));

Long Answer: Observe the following code and read my comment:

angular.module("todoListApp", [])

.controller('mainCtrl', function($scope, dataService) {
  $scope.learningNgChange = function() {
    console.log("An input changed!");
  };

  $scope.helloWorld = dataService.helloWorld;

  dataService.getTodos(function(response) {
    console.log(response.data);
    $scope.todos = response.data; // This loses scope. because the callback doesn't know that it is inside of a class. and is actually a new object, with no reference to `this` or `$scope`.
  });
})

Why does this happen?

There are 3 levels of scope in Javascript (ES5): Global, Object, and Functional scope. There are 4 levels of scope in Javascript (ES6): Global, Object, Functional, and Block scope.

When you try to call anything inside of a callback that is actually referencing the object (i.e., the controller), you will get an error because you lose scope of the variables you were working with on the object level.

What does this mean?

It means that each level of scope, (Gobal being highest level, and Block being the lowest level) has access to it's parent levels of scope. Block scope has access its parent function, the function has access to its parent object, and everything really has access to global scope, however, if you go backwards, Functional scope does not have access to block scope, each functional scope cannot access variables defined inside of other functions, and Object scope does not have access to variables defined in functional scope.

So if you created a function:

function getName() {
    var firstname = "Joshua";

    if (firstname) {
        var lastname = "Eagle";
    }

   return firstname + lastname;

}

In ES6 for example, the above code would not work as we would expect, because var lastname is inside of the block level scope of the if statement and firstname is inside of the functional level of scope inside of getName() function. However, if I were to change this up.

function getName() {
    var firstname = "Joshua";
    var lastname = "Eagle";
    var fullname = "";

    if (firstname) {
        fullname = firstname + ' ' + lastname;
    }

   return fullname;

}

You will notice I added a fullname, and moved the variable lastname to the top of the function. Now inside of the if statement, I can access fullname from the functional scope and assign it the concatenated values of firstname and lastname.

This is a lot to take in, this is kind of an intermediate explanation. But this is essentially what is happening with your callback function. The callback function is not bound to this controller class that you have defined. So you would have to bind the scope to your callback as stated in the short answer.

If you want more of an explanation and more visuals. look up the following: "levels of scope in Javascript", "Variable Hoisting", "Bind vs. Call Vs. Apply", "Understanding 'this'".

These four concepts while a little tough to initially wrap your head around, you will eventually get it. Just give it time and patience, don't expect to get it the first time, and you will eventually understand it more as you grow as a developer. I know when I first started, I had no idea what any of these concepts were or how they worked. :)

Sean Flanagan
Sean Flanagan
33,235 Points

Hi Joshua. I previewed the workspace and your code worked. No long stream of error messages. Thank you! :-)

Sean Flanagan
Sean Flanagan
33,235 Points

Hi Joshua. Thanks for your help. I read your post and then watched the video again to fill in the gaps in my understanding. The code in my workspace now matches what you did. It seems that all these parentheses and curly braces got me in a bit of a muddle. Was I missing an opening or closing parenthesis or curly brace or did I have one too many?

Here's my edited app.js:

angular.module("todoListApp", [])

.controller('mainCtrl', function($scope, dataService) {
  $scope.learningNgChange = function() {
    console.log("An input changed!");
  };

  $scope.helloWorld = dataService.helloWorld;

  dataService.getTodos(function(response) {
    console.log(response.data);
    $scope.todos = response.data;
  });
})

.service("dataService", function($http) {
  this.helloWorld = function() {
    console.log("This is the data service's method!!!");
  };

  this.getTodos = function(callback) {
    $http.get("mock/todos.json")
    .then(callback)
  }
});

Also, I did what Huston Hedinger did right at the end of the video and in the console I ran

$scope.todos

but I got

VM1189:1 Uncaught ReferenceError: $scope is not defined
    at <anonymous>:1:1

whereas Huston got the todos listed in an array. Is there any particular reason for this?

Thanks for your help so far.

Joshua Eagle
Joshua Eagle
9,778 Points

Sorry for the delay, I didn't see an email about your reply.

You've probably figured it out by now, checkout out this section of your code below:

dataService.getTodos(function(response) {
    console.log(response.data);
    $scope.todos = response.data;
  });

$scope here is undefined because it is in a callback function. It is technically outside of scope to your class object.

The reason this happens because javascript is asynchronous, meaning, you can call functions and once that function has been resolved, you pass in a callback function to handle that data. What happens in this process is that callback function isn't really associated with your controller class, and therefore does not inherit your controller level parameters unless you attach it to 'this' which we assigned a variable vm (stands for view model) Typical naming for this in controllers.

If you don't know what 'this' is, it essentially is the class object or in this case the controller class. It grants you access to all of its methods and properties.

You might want to do something like this:

.controller('mainCtrl', function($scope, dataService) {
  var vm = this;

  vm.$scope = $scope;

  $scope.learningNgChange = function() {
    console.log("An input changed!");
  };

  $scope.helloWorld = dataService.helloWorld;

  dataService.getTodos(function(response) {
    console.log(response.data);
    vm.$scope.todos = response.data;
  });
})

You are assigning $scope as a property to your controller class. This allows you to maintain the scope of your controller class variables and you can reference them in your callbacks.