Accessing Data in HTML

John Lindquist
InstructorJohn Lindquist
Share this video with your friends

Social Share Links

Send Tweet
Published 10 years ago
Updated 5 years ago

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

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.

Vishal
Vishal
~ 9 years ago

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

John Lindquist
John Lindquistinstructor
~ 9 years ago

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.

Helio
Helio
~ 9 years ago

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.

Markdown supported.
Become a member to join the discussionEnroll Today