Upload
wingify-engineering
View
735
Download
11
Embed Size (px)
DESCRIPTION
This presentation is about effectively managing Resources (Models) and their relationships in your single page application (based on AngularJS)
Citation preview
Angular.js and ResourcesEffectively Managing Resources (Models) in Your Angular.js Based Single
Page Applicationby Himanshu Kapoor, Front-end Engineer, Wingify
Web: , Twitter: , Email: fleon.org @himkp [email protected]
This presentation:
Download / Fork on GitHub:
http://lab.fleon.org/angularjs-and-resources/https://github.com/fleon/angularjs-and-resources
The interwebs today...Single Page Apps™
(Today's Hot Topic)
+
Front-end Frameworks(Our Pick: Angular.js)
+
Moar Stuff(Package Management, AMD, Project Organisation, etc.)
Why Single Page Apps™?Why should you make Single Page Apps?
They're coolEverybody else is doing itThe ™ symbol on it looks cool
Why Single Page Apps™?The real reasons...
Faster experience: no page refresh, on-demand data fetchingBetter runtimes: V8, spidermonkeyHeightened expectations: new products, mobile
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 ControllersMVC 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
The view
The model
function MyCtrl($scope) { $scope.myModel = 'hello world';}
<h1 ng-controller="MyCtrl"> {{myModel}}</h1>
// myModel is a POJO model
The result:
That was easy, but...
A real model, usually...is a rather big and complex objectlies on the server
Ok, lets request the server!$http shall answer all our queries
The code...The controller
The view
The model
function MyCtrl($scope, $http) { $http.get('/user').success(function (user) { $scope.user = user; });}
<h1 ng-controller="MyCtrl"> Hello there, {{user.name}}</h1>
// HTTP GET{ "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:
DRYConsistentScalableTestable
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 adapterAn abstraction of HTTP methodsAbility to add custom actionsPromise-based APIResources are lazily loaded
Time for some code...The model
The controller
The view
app.factory('UserResource', function () { return $resource('/user/:userId', { userId: '@id' });});
function MyCtrl($scope, UserResource) { $scope.user = UserResource.get({ id: 1 });}
<h1 ng-controller="MyCtrl"> Hello there, {{user.name}}</h1>
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 biggerSeveral views, controllers and resourcesEditable content
Incoming problems that say...
Which includeView inconsistenciesDuplicated model functionalityThe code isn't DRY anymore
Editable content
What is it?Edit a model using a formThe model gets updated in that viewBut not other views across the appResult: inconsistency
Inconsistencies?Multiple views render the same modelEach with different valuesExample: Blog, edit author name, save
Why are inconstencies so bad?Contradicting/misleading informationWorse than having no information at all
Here's an example:In addition to the code we already have:
The model
The controller
The view
app.factory('UserResource', function () { return $resource('/user/:userId', { userId: '@id' });});
function MyCtrl($scope, UserResource) { $scope.user = UserResource.get({ id: 1 });}
<h1 ng-controller="MyCtrl"> Hello there, {{user.name}}</h1>
Let us add another view that does something else, and something more...
The view
The controller
<hr><h2>Edit your name</h2><form ng-controller="MyEditCtrl" ng-if="user.name"> New name: <input type="text" ng-model="newName"> <button ng-click="updateName()">Save</button></form>
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 solutionMaintain references of that model throughout the appWhen it changes, propagate that change to all instances
Real world inconsistencies:Editing a resource that is related to multiple parent resourcesExample: author ~ post, author ~ commentMaintaining references here isn’t so trivial
The solution: RelationshipsRelationships to handle sub-resourcesMaintaining a single reference for each unique resource / sub-resource
Relationships
Parent and childrenA property on a resource belongs to another resourceExample:
post.author is an AuthorResource,
author.posts is a collection of PostResourcesFour kinds of relationships: one-to-one, one-to-many, many-to-one, many-to-many
Subsequent problemMaintaining 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=1UserResource with id=2PostResource with id=1
Q. How are such references maintained?
A. By transforming each backend response.
Looks like a job for...
TransformerA serviceInput: A backend response objectOutput: 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 abovevar 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 bepossible?
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
Scalablity by abstractionSolving the same problem for different resources across the appIndexing each resource instance by a given propertyTransforming relationships between parents and children recursively
How?
Abstract out the core logic from configurable inputIn this particular case: the configuration is a schema
The End ResultAn abstracted base that every resource stands on that is:
ScalableTestableConfigurable
Prevention of mixing resource management logic with the business logic
The core logic stays at a single place
Putting it all togetherRelationshipsResource TransformationIndexing / Maintaining ReferencesA configurable schema
The result: ResourceManager
Resource ManagerAn abstraction of resource-related problems faced while developing VWOA lot of them described in this presentationWe will be open-sourcing it soon
General LearningsAbstract out duplicate logicAbstract out configurations from the logicThink recursivelyResearch along each stepTake inspiration from other libraries (In this particular case, it was Ember-Data)
Thank YouQuestions / Comments / Suggestions?Reach Out
Web: fleon.orgGitHub: @fleonTwitter: @himkpEmail: [email protected]
View this presentation: Download / Fork on GitHub:
http://lab.fleon.org/angularjs-and-resources/http://github.com/fleon/angularjs-and-
resources/