Fork me on GitHub

Angular.js and Resources


Effectively Managing Resources (Models) in Your Angular.js Based Single Page Application

by Himanshu Kapoor, Front-end Engineer, Wingify
Web: fleon.org, Twitter: @himkp, Email: [email protected]


This presentation: http://lab.fleon.org/angularjs-and-resources/
Download / Fork on GitHub: https://github.com/fleon/angularjs-and-resources

The Web World Today...


Single Page Apps™

+

Front-end Frameworks

+

More Stuff

Why Single Page Apps?


Why should you make Single Page Apps?

  • Faster experience
  • Better runtimes
  • Heightened expectations

Well ok, lets make a Single Page App!

Thus begins our SPA Journey...


with Angular.js + Angular UI Router + Require.js

And then, there were...

Models, Views and Controllers


MVC 101: Angular.js Edition

Views: rendered in the browser

Controllers: makes your view dynamic, has the logic

Models: plain old POJOs

POJOs as Models?


Yes, Plain Old Javascript Objects!

Hmm, sounds cool!

OK, here's what we got...


  • The controller
    
    function MyCtrl($scope) {
      $scope.myModel = 'hello world';
    }
    								
  • The view
    
    

    {{myModel}}

  • The model
    
    // myModel is a POJO model
    								

The result:


That was easy, but...

A real model, usually...


  • is a rather big and complex object
  • lies on the server

Ok, lets request the server!


$http shall answer all our queries

The code...


  • The controller
    
    function MyCtrl($scope, $http) {
      $http.get('/user').success(function (user) {
        $scope.user = user;
      });
    }
    								
  • The view
    
    

    Hello there, {{user.name}}

  • The model
    
    { // HTTP GET Response
      "id": 1234,
      "name": "John Doe",
      "email": "[email protected]"
    }
    								

The result:


Pretty sweet, right?

But hold on...


Back in the real world, things aren't so simple.

The problems:


  • What about multiple views?
  • What about other kinds of actions (POST, PATCH, PUT, DELETE)?
  • What about muliple types of models (users, posts, comments)?
  • How do you handle multiple instances of the same model?

And while answering the questions,


How do you make sure your code is:

    • DRY
    • Consistent
    • Scalable
    • Testable

    And here are the answers...


    • Q: What about multiple views?
      A: Abstract out the model in a service.
    • Q: What about other kinds of actions?
      A: Add support for those methods in the service.
    • Q: What about muliple types of models?
      A: Add support for instantiating different model types in the service.

    This looks like a job for...

    $resource

    $resource to the rescue!


    • A configurable REST adapter
    • An abstraction of HTTP methods
    • Ability to add custom actions
    • Promise-based API
    • Resources are lazily loaded

    Time for some code...

    • The model
      
      app.factory('UserResource', function () {
        return $resource('/user/:userId', {
          userId: '@id'
        });
      });
      								
    • The controller
      
      function MyCtrl($scope, UserResource) {
        $scope.user = UserResource.get({
          id: 1
        });
      }
      								
    • The view
      
      

      Hello there, {{user.name}}

    The result:


    Looks no different from the previous output,
    but our code is a lot more extendible with the above logic.

    The journey continues...


    • Application grows bigger
    • Several views, controllers and resources
    • Editable content

    Incoming problems that say...


    Which include


    • View inconsistencies
    • Duplicated model functionality
    • The code isn't DRY anymore

    Editable content

    What is it?


    • Edit a model using a form
    • The model gets updated in that view
    • But not other views across the app
    • Result: inconsistency

    Inconsistencies?


    • Multiple views render the same model
    • Each with different values
    • Example: Blog, edit author name, save

    Why are inconstencies so bad?


    • Contradicting/misleading information
    • Worse than having no information at all

    Another Example + Code:

    In addition to the code we already have:

    • The model
      
      app.factory('UserResource', function () {
        return $resource('/user/:userId', {
          userId: '@id'
        });
      });
      								
    • The controller
      
      function MyCtrl($scope, UserResource) {
        $scope.user = UserResource.get({
          id: 1
        });
      }
      								
    • The view
      
      

      Hello there, {{user.name}}

    Let us add another view that does something else, and something more...

    • The view
      
      

      Edit your name

      New name:
    • The controller
      
      function MyEditCtrl($scope, UserResource) {
        $scope.user = UserResource.get({
          id: 1
        });
        $scope.updateName = function () {
          $scope.user.name = $scope.newName;
          $scope.user.$save();
        };
      }
      								

    The result:


    Separation of concerns is good, but not if it leads to such an inconsistency.

    The solution


    • Maintain references of that model throughout the app
    • When it changes, propagate that change to all instances

    Real World Example


    In the real world, things aren't so simple...

    Complicated inconsistencies:


    • Editing a resource that is related to multiple parent resources
    • Example: author ~ post, author ~ comment
    • Maintaining references here isn’t so trivial

    The solution: Relationships


    • Relationships to handle sub-resources
    • Maintaining a single reference for each unique resource / sub-resource

    Relationships

    Parent and children


    • A property on a resource belongs to another resource
    • Example:
      post.author is an AuthorResource,
      author.posts is a collection of PostResources
    • Four kinds of relationships: one-to-one, one-to-many, many-to-one, many-to-many

    Example


    Diagram (Kinds of relationships)


    Subsequent problem


    Maintaining references

    References?

    What are references?


    Maintaining references: Ensuring that each unique resource has only one instance throughout the app.
    For instance, there should be only one instance of:

    • UserResource with id=1
    • UserResource with id=2
    • PostResource with id=1


    Q. How are such references maintained?
    A. By transforming each backend response.

    Looks like a job for...


    Transformer


    • A service
    • Input: A backend response object
    • Output: A transformed mesh of resources

    Example input:


    
    // GET /posts
    [{
      "id": 1,
      "createdBy": { "id": 1, "name": "John Doe" }
      "title": "My First Post",
      "excerpt": "Lorem Ipsum"
    }, {
      "id": 2,
      "createdBy": { "id": 1, "name": "John Doe" }
      "title": "My Second Post",
      "excerpt": "Lorem Ipsum"
    }, {
      "id": 3,
      "createdBy": { "id": 1, "name": "Jony Ive" }
      "title": "My Third Post",
      "excerpt": "Lorem Ipsum"
    }]
    						

    The output:


    
    // Output obtained by transforming the response above
    var output = /* ... */;
    
    expect(output).toEqual(any(Array));
    expect(output.length).toBe(3);
    						
    
    expect(output[0]).toEqual(any(PostResource))
    expect(output[1]).toEqual(any(PostResource))
    expect(output[2]).toEqual(any(PostResource))
    						
    
    expect(output[0].createdBy).toBe(output[1].createdBy);
    expect(output[0].createdBy).toBe(output[2].createdBy);
    						

    How would such a transformation be possible?


    • By identifying unique resources
      By getting one or more properties that can uniquely identify a resource
      For example: post.id, author.id
    • By maintaining an index
      A key value pair where:
      Key: the unique identification above
      Value: the actual resource
    • Knowing how to identify unique resources
      By maintaining a schema

    Scalablity by abstraction


    • Solving the same problem for different resources across the app
    • Indexing each resource instance by a given property
    • Transforming relationships between parents and children recursively


    How?

    • Abstract out the core logic from configurable input

    The End Result


    An abstracted base that every resource stands on that is:

    • Scalable
    • Testable
    • Configurable


    Prevention of mixing resource management logic with the business logic


    The core logic stays at a single place

    Well you'd say...


    Putting it all together


    • Relationships
    • Resource Transformation
    • Indexing / Maintaining References
    • A configurable schema


    The result: ResourceManager

    Resource Manager


    Resource Manager


    • An abstraction of resource-related problems faced while developing VWO
    • A lot of them described in this presentation
    • We will be open-sourcing it soon

    General Learnings / Takeaways


    • Abstract out duplicate logic
    • Abstract out configurations from the logic
    • Think recursively
    • Research along each step
    • Take inspiration from other libraries
      (In this particular case, it was Ember-Data)

    Thank You