This lesson is for PRO members.

Unlock this lesson NOW!
Already subscribed? sign in

Accessing Data in HTML

8:05 Angular 1.x lesson by

In this lesson, John will take you from bad practices to good practices, as he explores how to bring data into your HTML templates with AngularJS. You'll go from attaching data directly to the $rootScope in your Angular module run block (bad!) all the way through using promises to asynchronously assign data to your controller and access it with "controller-as" syntax (much better!).

Get the Code Now
click to level up

egghead.io comment guidelines

Avatar
egghead.io

In this lesson, John will take you from bad practices to good practices, as he explores how to bring data into your HTML templates with AngularJS. You'll go from attaching data directly to the $rootScope in your Angular module run block (bad!) all the way through using promises to asynchronously assign data to your controller and access it with "controller-as" syntax (much better!).

Avatar
Andrey

If you expose Service name in controller (this.ServiceName) will it be preserved after minimizing/uglifying JS? If not, how to get around this?

In reply to egghead.io
Avatar
Joel

If you expose Service name in controller (this.ServiceName) will it be preserved after minimizing/uglifying JS? If not, how to get around this?

It should be, but I'd definitely test it out.

In reply to Andrey
Avatar
Samuel

I find the code in the last example with the method for adding todos being declared in the service, but not working unless its copied over to the controller object.

Except for that I want to applaud you on a great walk-through of the 'evolution' of this app.

In reply to egghead.io
Avatar
Nick Bolton

It would be great if you could give an example of using a service like this to cache and share the data between two Controllers and have them both update / refresh data when necessary. It's one big conundrum everyone runs into.

This approach is great for a single controller, but when using it between multiples it's a lot harder due to this.todos | $scope.todos not being the single source where the data resides anymore because if each controller stores a result on their own scopes independently it is no longer shared.

In reply to egghead.io
Avatar
Joel

Ya, this video definitely covers the "naive" approach. In a real application your going to want to model the data in some form or fashion. I've got a general rule that $http is banned from production controllers. That should always be abstracted and encapsulated. Here's a basic TodosModel service:

angular.module('common.models.todos-model', [])
  .service('todosModel', function ($http, $q) {
    var todosModel = this,
      _selectedTodo;

    todosModel.todos = [];

    todosModel.getSelectedTodo = function() {
      return _selectedTodo;
    }

    todosModel.setSelectedTodo = function(value) {
      _selectedTodo = value;
    }

    todosModel.fetchTodos = function () {
      var deferred = $q.defer();

      function parseTodosResult(result) {
        todosModel.todos.length = 0;
        _.each(result.data, function (Todo) {
          todosModel.todos.push(Todo);
        });
        todosModel.setSelectedTodo(todosModel.todos[0]);
      }

      if(_.isEmpty(todosModel.Todos)) {
        $http.get('/api/todos').then(function (result) {
          parseTodosResult(result);
          deferred.resolve(todosModel.todos);
        }, function(error) {
          deferred.reject(error);
        })
      } else {
        deferred.resolve(todosModel.todos);
      }

      return deferred.promise
    }
  })

;

It has some simple "caching", and uses $q to manage the promise. With this, it can be called from anywhere and the state of the Todos is going to be consistent, and most importantly in a single place. Most of the time I'd just map this model in my controller with this.todos = todosModel (or the $scope variant). The model would also supply a CRUD API and any other manipulation/persistence related to a todo.

I'm going to add this to my queue for a video. It's an incredibly important topic.

In reply to Nick Bolton
Avatar
Nick Bolton

Hi Joel,

Yes this is definitely more along the lines I was talking about. A video on egghead.io relating to this subject is a definite must in my opinion. Service -> Single Controller is a great beginner stance, but we need to take it a step further with a best practice similar to your approach and a video to promote this if possible please.

In reply to Joel
Avatar
Diego

Awesome Joel, I really would like to see this video(as I asked if you already have one in the help section).

Btw, I don't think that using ctrl.Service.todos is even a acceptable approach, since it exposes to the view something that theoretically the controller should know about - and even worse, it breaks the Law of Demeter.

In reply to Joel
Avatar
Vishal

Hi

What is the better way to implement deleteTodo,

like I call ToDoService.deleteTodo(todoToBeDeleted).then(
// splice scope copy of todos
)

or

subscribe to an event (say TodoDeleted in ToDoService and then update splice local copy there ??

-Vishal

Avatar
John

In your controller, if you assign a property on the controller to a property on a service (and you're using the controllerAs approach), Angular will automatically begin watching for changes. This is because using controllerAs puts the controller on the $scope and $scope keeps track of whatever it references.

So you shouldn't have to worry about events. There are rare scenarios where you don't want your model/view to stay in sync whenever a property changes and only update on certain events.

In reply to Vishal
Avatar
John

Check out the "Disadvantages" section on the Law of Demeter: http://en.wikipedia.org/wiki/Law_of_Demeter#Disadvantages

It is a tradeoff, but in JavaScript/Angular, I believe less code and less wrappers leads to a better solution. This is definitely a point of contention with valid arguments on both sides, especially if you come from other programming languages.

In reply to Diego
Avatar
Helio

There is another way you didn't talk about: Object.defineProperty.

Basically, in your controller you do something like this:

Object.defineProperty('todos', {
get: function () { return TodosService.todos }
});

Now everytime your TodosService is getting new todos, the controller will be updated as well.

Or you can inject the TodosService in the $scope.

John: There are a million ways to display data from your angular app into your HTML, so I figured I'd cover some of the bad practices and work my way up to the good practices. Because even the bad practices have a little bit of good in them and they're good to know, just to be able to fix the problems later and keep your app nice and clean.

First we'll start with a way of doing it that nobody probably does, we'll just inject the root scope into a run call and say as an array of todos, which I set up previously. Refresh here and say one and two and three. You can see this is working just fine, because my HTML says lop through that array, which is on the root scope.

Then, whenever I click, just push a new item into that array that's on the root scope. But this is actually a terrible practice. You never want to assign anything to the root scope because it can just get overwritten later. If you do it here, it's impossible to share it elsewhere. Let's go ahead and do it differently and abstract it a little bit through a service.

To set it up as a service, let's go ahead and grab all of this, copy and paste over here. We can set up our new app service, which will be a todo service. Our todo service is just going to take these todos and it'll paste them over here. Then, next to the root scope, we'll inject a todo service and say our todo service is equal to todo service. That way we've exposed the todo service to the HTML.

In our index let's swap over to abstract and then in each of these we can say todoservice.todos and todoservice.todos. Then you can see that this will now work again just fine.

This is actually a little bit better, because now we have a todo service which we can share throughout our application. We're just injecting it into the run, which is a really weird place to put it, because it makes it depend on the very startup of the application after it's all configured and everything.

Then this also looks fine as far as exposing to the HTML. It's OK that it says todoservice.todos and that you're accessing the todos directly off the todo service. This looks a little bit funky, because you're putting a little bit too much logic into the HTML. We can go ahead and say this add todo and then create a function where you can pass in a new todo where we just say add todo, and the new todo is going to be the new todo off of this NG model.

Then, in our service we'll just say this .todos.push, which is going to look, from the HTML, exactly like that. Actually, we'll just take that. Then, the new todo is going to be what gets passed in there. You can see if we refresh again we are working still.

This is a little bit better because now we can change the logic of adding a todo however we want. We're not restricted to that custom line of logic here inside of the click. But we can still improve this by setting up a controller and injecting the service into the controller to give us a bit more separation from the initial setup of the application.

To set up our controller, let's go ahead and grab all of this again. We'll paste it into this controller script. Instead of using run, we'll just say app controller and we'll call this todo controller. We won't inject the root scope anymore, so we want to have that dependency there.

I'm actually not even going to use the root scope here because one of the new best practices in Angular 1.2 and above is to use the controller as syntax. I'm going to say this todo service is todo service and then I can expose, and the NG controller can say todocontrol as todocontrol.

Then I want to make sure that I replace this with the controller script. Now I can say todocontrol and todocontrol. I know this change has a lot of people upset, they don't like having the service off of the controller and exposing that to the HTML. If that's really against your preference, we'll show you a way of getting around that later.

But other people completely agree that this is just fine. This is one of those best practices, where it's really up to your personal opinion on whether you think a service should be exposed through the controller to the HTML or not. Anyway, I'll just refresh again. You can see this is a controller working and everything is still working just fine.

The main reason it really doesn't matter whether or not you like this as a best practice or not, where you expose the service directly from the controller, is because typically in services you're working with data from a server. You're working with promises. Here, I'm just mocking a promise call where after a three-second timeout, I resolve by sending back the array of todo items.

Then it's in the controller that we say let's get these todos. Then when it's done just use that result and expose it as a todo array on the controller. In services you use promises all the time, so if I switch over to this, I'm going to remove the todo service, remove the todo service. Then in my app control we can say promises. Once I refresh here after one, two, three seconds, it will still work using promises.

This is what your setup ends up looking like at the end. You have your controller. If you're using the AS controller syntax, it will just allow you to expose the things directly off of the controller. This is one of the best practices of 1.2 and above. Then, if you want to alias this todoservice add todo, you can do it that way. Or you could wrap it with more logic of something to preprocess before you make that call.

If you wanted to expose the todo service and directly add a todo on the controller, that's fine. That's your call on that one. But again, typically when getting data, you're going to be getting data using promises.

Hopefully that helps clarify some of what all the different approaches to exposing data are on the HTML, because there are so many options like root scope, run the scope, AS controller, or exposing the service on the controller, exposing the service through root scope, et cetera, et cetera, et cetera.

Basically what it boils down to is that because in services you end up using promises so much, you're just using your controller to invoke those services. You get a result back and then expose that data on the controller to the HTML.

The only argument at the end there is whether you want to expose that data using the AS controller syntax, which I recommend. Or whether you want to expose it on the scope. Then, whether you want to expose the services directly through a todocontroller, todoservice, add todo, or whether you want to alias those calls or wrap the calls in something else.

Honestly, what I've written here is the approach that I would personally take.

HEY, QUICK QUESTION!
Joel's Head
Why are we asking?