72

I'm finding Angular's use of models confusing. Angular seems to take the approach that a model can be anything you like - I.E. Angular does not include an explicit model class and you can use vanilla JavaScript objects as models.

In almost every Angular example I've seen, the model is effectively an object, either created by hand, or returned from an API call via a Resource. Because almost every Angular example I've looked at is simple, usually the model data stored on $scope in a controller and any state related to the model, for example selection, is also stored on the $scope in the controller. This works fine for simple apps/examples, but this seems like an oversimplification when apps become more complex. Model state stored in a controller is at risk of becoming contextual and being lost if the context changes, for example; A Controller storing selectedGallery and selectedPhoto can only store global selectedImage, not a selectedPhoto per gallery. In such a situation, using a controller per gallery might negate this problem, but would seem wasteful and probably inappropriate and unnecessary from a UI perspective.

Angular's definition of models seems closer to what I would consider a VO/DTO that is a dumb object passed between server and client. My instinct is to wrap such an object in what I would consider a Model - a class that maintains state relating to the DTO/VO (such as selection), offers mutators as needed to manipulate the DTO/VO, and notifies the rest of the application of changes to the underlying data. Obviously this last part is nicely taken care of by Angular's bindings, but I still see a strong use-case for the first two responsibilities.

However I haven't really seen this pattern used in the examples I've looked at, but neither have I seen what I would consider a scalable alternative. Angular seems to implicitly discourage using Services as models by enforcing Singletons (I know there are ways to get around this, but they don't seem widely used or approved of).

So how should I be keeping state on Model data?

[Edit] The second answer in this question is interesting and close to what I'm currently using.

Community
  • 1
  • 1
Undistraction
  • 42,754
  • 56
  • 195
  • 331
  • What don't you like about one service per model type? A `galleryService` could store an array of galleries. – Mark Rajcok May 17 '13 at 16:15
  • @MarkRajcok I have no problem whatsoever with Singleton Services. In lots of situations they are all you need and in the situation you describe that would work out fine. But what if each gallery has an array of Photographs, each of which need to maintain state? – Undistraction May 17 '13 at 16:59
  • I would probably still model everything in a service (or services), and expose to the controllers only what they need. – Mark Rajcok May 17 '13 at 17:54
  • But how would you deal with the situation I've outlined - where there is a structure like Galleries > Gallery > Photo? In a shallower structure - Galleries > Gallery, a singleton GalleriesService works fine, manipulating its Galleries, but when model structure becomes more complex, this doesn't cut it. What if you need state and accessors on Photo? Surely it then makes sense that each Gallery exists as a separate Service/Model, able to operate on its photos? – Undistraction May 17 '13 at 23:16
  • 3
    I suppose I may be over-simplifying and designing on the-the-fly here... I would have three model objects: 1) photo object, 2) gallery object (one property of which is an array of photo objects), 3) galleryCollection object (one property of which is an array of gallery objects). (The galleryCollection might not be a separate object -- it might just be part of the galleryService itself.) Methods and properties can exist on all three. In my mind, each photo and gallery is a separate object, they are just grouped/managed/accessed by a service. The models can be defined outside the service. – Mark Rajcok May 18 '13 at 01:53
  • 1
    I agree with @MarkRajcok (as is often the case). The cleanest, simplest method is to use services as he described. This greatly simplifies testing and makes each service more extensible and reusable. I think it's important to view services not as returning a model *object* but as returning a model *API*. That API is what you use in a controller to access one or a collection of model objects. So a `gallery` service can have the familiar methods (get, update, delete, etc.) while managing state internally and returning objects with individual record methods, like `$resource`. – Josh David Miller May 18 '13 at 02:03
  • @MarkRajcok That makes a lot of sense and is pretty much my approach at the moment. I have a (singleton) Galleries Service/Model to which I pass the data object returned by GalleriesResource::query(). This Service then builds a Gallery Model for each gallery node in the data object, passing in the node, and each Gallery Model creates a Photograph Model for each Photograph, again passing in the node. Now each node of the data object is managed whilst the data structure is intact (and can be modified and retuned to the server if/when needed). – Undistraction May 18 '13 at 04:43
  • @JoshDavidMiller So in this scenario, the Service wraps a Resource? – Undistraction May 18 '13 at 04:48
  • @Pedr I didn't mean to use ngResource, though that is a common approach, but an implementation analogous to it. ngResource acts as a collection and returns objects (or arrays of them) with special methods added on to each, like `$save`. With a single `gallery` service, you could return an object (or an array of them) with whatever individual-level methods you need, like `getPhotos` - which returns an array of photo objects (obviously from a separate, dependency-injected service). When you need to save state, you use an intermediary service that accesses the `gallery` or `photo` services. – Josh David Miller May 18 '13 at 06:09
  • @JoshDavidMiller Thanks. I'll have to have another look at the implementation of ngResource. Be great if you could make a bare-bones Gist or even some pseudo-code as an answer ;) – Undistraction May 18 '13 at 06:44
  • @JoshDavidMiller Thought it would be good to have this as a separate question/answer, so just asked it here: http://stackoverflow.com/questions/16626075/non-singleton-services-in-angular. Please leave an answer with your example. – Undistraction May 18 '13 at 15:47
  • 5
    For anyone else wondering: VO = [Value Object](http://en.wikipedia.org/wiki/Value_object), DTO = [Data Transfer Object](http://en.wikipedia.org/wiki/Data_transfer_object). – Tamlyn Sep 14 '13 at 16:44

3 Answers3

29

State (and models) are stored in $scope

$scope is Angular's data storage object. It's analogous to a database. $scope itself is not the model, but you can store models in $scope.

Each $scope has a parent $scope, all the way up to $rootScope forming a tree structure that loosely mirrors your DOM. When you call a directive which requires a new $scope, such as ng-controller, a new $scope object will be created and added to the tree.

$scope objects are connected using prototypical inheritance. This means that if you add a model at a higher level in the tree, it will be available to all the lower levels. This is a phenomenally powerful feature which makes the $scope hierarchy almost transparent to the template author.

Controllers initialise $scope

The purpose of the controller is to initialise $scope. The same controller can initialize many $scope objects in different parts of the page. The controller is instantiated, sets up the $scope object and then exits. You can use the same controller to initialize many $scopes in different parts of the page.

In the case of your image gallery, you would have an imageGallery controller which you would then apply to every portion of the DOM which you want to be a gallery using the ng-controller directive. That portion of the page would get it's own $scope, which you would use to store the selectedPhoto attribute.

Prototypical scopes

$scope inherits from its parent using plain old prototypical inheritance all the way up to $rootScope, so you can store your objects anywhere on the hierarchy that makes sense. You get a tree of $scope objects that roughly relates to your current DOM. If your DOM changes, new $scope objects are created for you as required.

$scope is just a plain JavaScript object. It's no more wasteful to create multiple $scope objects than it would be to create an array with multiple currentImage objects. It's a sensible way to organise your code.

In this way Angular does away with the old "where do I store my data" problem that we often find in JavaScript. It's the source of one of the really big productivity gains that we get from Angular.

Got global data (eg. a userId)? store it on $rootScope. Got local data (eg. a currentImage in a gallery where there are multiple gallery instances)? Store it on the $scope object that belongs to that gallery.

$scope is automatically available to you in the correct portion of the template.

Angular models are thin

Coming from a Rails background where we emphasise fat models and skinny controllers, I found Angular's 'barely there' models surprising. In fact, putting a lot of business logic in your model often leads to problems down the line, as we sometimes see with the User model in Rails which, if you're not careful, will grow until it becomes unmaintainable.

An angular model is simply a JavaScript object or primitive.

Any object can be a model. Models are typically defined using JSON in the controller, or AJAXed in from a server. A model might be a JSON object, or might be just a string, array, or even a number.

Of course, there's nothing to stop you adding additional functions to your model and storing them in the JSON object if you want to, but this would be porting in a paradigm that doesn't really fit with Angular.

Angular objects are typically repositories of data, not functions.

The model on the front end is not the real model

Of course the model that you hold on the client is not the real model. Your actual model, your single source of truth lives on the server. We synchronise it using an API, but if there's a conflict between the two the model in your database is obviously the ultimate victor.

This gives you privacy for things like discount codes, etc. The model you find in your front end is a synchronised version of the public properties of the real model, which is remote.

Business logic can live in services.

Say you want to write a method to do something to your model, synchronise it, or validate it for example. In other frameworks you might be tempted to extend your model with a method to do this. In Angular you would be more likely to write a service.

Services are singleton objects. Like any other JavaScript object you can put functions or data in them. Angular comes with a bunch of built in services, such as $http. You can build your own, and use dependency injection to automatically provide them to your controllers.

A service might contain methods to talk to a RESTful API for example, or to validate your data, or any other work you might need to do.

Services are not models

Of course you shouldn't use services as models. Use them as objects which can do stuff. Sometimes they do stuff to your model. It's a different way of thinking, but a workable one.

superluminary
  • 47,086
  • 25
  • 151
  • 148
  • 3
    Thanks. All good points. However you don't address the crux of the question. Are you suggesting that all state should be stored in the 'real' models on the server? Because that effectively negates one of the principle benefits of a rich client - speed. I would argue that in many cases a client-side model is every bit as 'real' as the server-side model, though it obviously depends on architecture. In my question I specifically address state that is relevant only to the client - selection state - not a more permanent state that needs to be persisted to the server. – Undistraction May 02 '14 at 06:14
  • 2
    Yes, you are of course right, the state should be held on the client, and front end tasks should take place in the client. I just felt it was useful to talk around the issue. – superluminary May 02 '14 at 09:21
  • 1
    Definitely good to talk around it. I think this is still an unsolved problem. State is where the monsters lurk and no-one likes to deal with it. – Undistraction May 02 '14 at 10:35
  • 2
    State is of course solved wonderfully for us by Angular using $scope. – superluminary Nov 12 '14 at 10:31
  • Nice post, this is the paradigm I am trying to follow in my app. – mjj1409 Oct 09 '15 at 17:48
  • Sidenote: You are not bound to use only the tools Angular gives to manage state. You can integrate any state management library into Angular, e.g. http://redux.js.org/ – Ore4444 Nov 07 '16 at 10:00
7

First of all, let's not forget that Angular is a web based framework and if you "keep your state" solely in an object, it will not survive user hitting refresh on their browser. Therefore, figuring out how to keep state of Model data in a web based application means figuring out how you are going to persist it so that your code will function in a browser environment.

Angular makes it really easy for you to persist your state using:

  1. A call to a RESTful $resource
  2. An URL representing an instance of your model

In your simple example, the storing of user actions such as selectedGallery and selectedPhoto can be represented using URL with something like:

// List of galleries
.../gallery

// List of photos in a gallery
.../gallery/23

// A specific photo
.../gallery/23/photo/2

The URL is critical because it allow your user to navigate the browser history using back and forward buttons. If you wish to share this state with other part of your application, web application provide wealth of methods for you accomplish that using cookie/localStorage, hidden frame/fields or even storing it in your server.

Once you defined your strategy on how to persist different state of your application, it should be easier to decide if you wish to access these persisted info using a singleton object as provided by .service or an instance via .factory.

Antoine Jaussoin
  • 5,002
  • 4
  • 28
  • 39
marcoseu
  • 3,892
  • 2
  • 16
  • 35
  • 3
    This makes a lot of sense, and I fully agree that application state needs to be tracked by path/query string, however surely this is a projection of application state, not a way to store it. The URL bar is an strange combination of model and view, but it ultimately reflects internal application state or prompts changes in this state. The rest application itself still needs ways of tracking this state internally, and doing so on a Service/Model where the application's actors can share access seems like the right solution to me. – Undistraction May 18 '13 at 13:33
  • Also .service and .factory both return Singleton Services (which is very counter-intuitive). – Undistraction May 18 '13 at 13:59
  • Actually, that is not true. `.service` returns Singleton but you can create instance using `.factory`. Let me find an example and I will update this comment with it. – marcoseu May 18 '13 at 14:06
  • Thanks. I agree it is possible, but it certainly isn't promoted as a common use of .factory. – Undistraction May 18 '13 at 14:12
  • I see your point. I do agree with you that Angular's documentation states that "Lastly, it is important to realize that all Angular services are application singletons" but I take that as poor documentation attempt. I personally use `.service` when a Singleton is needed but use `.factory` to return new'able objects when I wish to have an instance object. – marcoseu May 18 '13 at 14:42
  • If you have an example of using factory to generate instances, it would be great to see it. – Undistraction May 18 '13 at 15:08
  • Here: http://plnkr.co/edit/9UMI1zeMRArefqgvC8Ch?p=preview. I tried to make it as simple as possible to get the point across. – marcoseu May 18 '13 at 15:14
  • 4
    @marcoseu, interesting... although you can do that, should you? Now dependency injection works very differently with that factory -- i.e., since you are returning a function (object) rather than an object (i.e, {}), you have to use `new` to create an instance. This means that you can't share that instance with other entities using the normal dependency injection syntax. I think this would be confusing for other Angular developers that might use your code. – Mark Rajcok May 18 '13 at 16:37
  • 1
    @MarkRajcok I intentionally return a new'able function so that the `new` keyword will indicate that this service is not to be shared with other entities. For example, I have a wrapper around ui.bootstrap.alert that I instantiate passing $scope in my controller. When I need to show alert later on, I simply call `alert.success("Ok")`. That said, I do agree that it is not clear when caller should use new, but it is really up to the user of the service to understand how the service should be used. – marcoseu May 18 '13 at 17:13
  • 1
    @MarkRajcok I do realize that the use of `new` is confusing but it served as a reminder for me that the service I am calling is not to be shared. However, if concern is purely on the syntax side, you can return a object as well, ie: calling new in the `.factory`. I updated the plunker to illustrate how this can be done. – marcoseu May 18 '13 at 17:25
1

Angular does not have an opinion on how you store what you call "model objects". The Angular controller $scope exists solely as a "view model" for the purposes of managing your UI. I suggest separating these two concepts in your code.

If you want the nicety of Angular scope change notification ($watch), you can use a scope object to store your model data if you wish (var myScope = $rootScope.$new()). Just don't use the same scope object to which your UI is bound.

I recommend writing custom services to this end. So the data flow goes like this:

AJAX --> Custom Service --> Model Scope Object --> Controller --> UI Scope Object --> DOM

Or this:

AJAX --> Custom Services --> Plain old JavaScript Objects --> Controller --> UI Scope Object --> DOM

djsmith
  • 3,007
  • 3
  • 24
  • 21
  • 3
    I have already separated these concepts which is exactly why I've asked this question. The standard Angular way of dealing with model state seems to be to store in in this View Model which is exactly what makes me uncomfortable. Creating a new scope doesn't feel like a good solution to me. It feels like a misuse of scope. – Undistraction May 17 '13 at 23:08