The ability to reply to discussions is limited to PRO members. Want to join in the discussion? Click here to subscribe now.

Accessing Data in HTML

Accessing Data in HTML

8:05
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!).
Watch this lesson now
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.

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