Last active
August 29, 2015 14:08
-
-
Save primathon/4ccb35ad6ddece4af1db to your computer and use it in GitHub Desktop.
AngularJS API Frontend "hello world" Example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- | |
Some words about what's going on here: http://colinmccann.com/angularjs-api-frontend | |
GitHub Gist: https://gist.github.com/primathon/4ccb35ad6ddece4af1db | |
Live Plunkr Demo: http://embed.plnkr.co/fUIHDw/preview | |
--> | |
<!doctype html> | |
<html lang="en" ng-app="app"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>AngularJS API Frontend "hello world" Example</title> | |
<!-- of course we're using Bootstrap --> | |
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"/> | |
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css"/> | |
<!-- | |
AngularJS 1.2.20 + modules: | |
- ui.router - https://github.com/angular-ui/ui-router | |
- ui.bootstrap - https://github.com/angular-ui/bootstrap | |
- Restangular - https://github.com/mgonto/restangular - requires underscore.js | |
- angular-loading-bar - https://github.com/chieffancypants/angular-loading-bar | |
- angular-growl - https://github.com/marcorinck/angular-growl | |
- ngSanitize - https://docs.angularjs.org/api/ngSanitize - (optional) for showing HTML in angular-growl | |
- ngAnimate - https://docs.angularjs.org/api/ngAnimate - (optional) for animating opacity change in angular-growl | |
--> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore-min.js"></script> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular.js"></script> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.11/angular-ui-router.min.js"></script> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.10.0/ui-bootstrap.min.js"></script> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/restangular/1.4.0/restangular.min.js"></script> | |
<script src="//cdn.rawgit.com/chieffancypants/angular-loading-bar/master/src/loading-bar.js"></script> | |
<script src="//cdn.rawgit.com/marcorinck/angular-growl/master/build/angular-growl.min.js"></script> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular-sanitize.min.js"></script> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular-animate.min.js"></script> | |
<!-- Stylesheets for angular-loading-bar and angular-growl --> | |
<link href="//cdn.rawgit.com/chieffancypants/angular-loading-bar/master/src/loading-bar.css" rel="stylesheet" type="text/css"> | |
<link href="//cdn.rawgit.com/marcorinck/angular-growl/master/build/angular-growl.min.css" rel="stylesheet" type="text/css"> | |
<base href="/"> | |
<script type="text/javascript"> | |
/** | |
* Efforts have been made to follow best practices and guidelines as laid out here: https://github.com/johnpapa/angularjs-styleguide | |
* That said, I've included everything in one file for the sake of readability. There are still quite a few tweaks that could be | |
* made to this in order to conform to the style guide more accurately. First and foremost, this is a functional demonstration. | |
*/ | |
'use strict'; | |
/** | |
* Create our Angular instance and load our modules | |
*/ | |
var app = angular.module("app", [ | |
'ngSanitize', 'ngAnimate', | |
'ui.router', 'ui.bootstrap', | |
'angular-loading-bar', 'angular-growl', | |
'restangular' | |
]); | |
(function () { | |
'use strict'; | |
/** | |
* Define application routes (well... route) | |
*/ | |
app.config(function ($stateProvider, $urlRouterProvider) { | |
$stateProvider | |
.state('posts', { | |
url: "/posts", | |
controller: "PostsController" | |
}); | |
$urlRouterProvider.otherwise("/posts"); | |
}); | |
/** | |
* Configure Restangular to use a dummy API | |
*/ | |
app.config(function (RestangularProvider) { | |
RestangularProvider.setBaseUrl("http://jsonplaceholder.typicode.com"); | |
}); | |
/** | |
* Create PostsController | |
* | |
* @require PostsService | |
*/ | |
app.controller('PostsController', ['PostsService', function (PostsService) { | |
/** | |
* This utilizes "controller-as" syntax (which you can see referenced in the HTML). For more info: | |
* - http://toddmotto.com/digging-into-angulars-controller-as-syntax/ | |
* - http://www.johnpapa.net/angularjss-controller-as-and-the-vm-variable/ | |
*/ | |
var vm = this; | |
vm.posts = getPosts(); | |
vm.deletePost = deletePost; | |
vm.showPost = showPost; | |
vm.storePost = storePost; | |
vm.updatePost = updatePost; | |
/** | |
* It would be rare that you're ever dealing with creating a new post and updating existing posts on the same page, | |
* but in this instance, it keeps things much more readable to hold the data in two separate objects. | |
* | |
* Also, I'm hard-coding a user_id in here; this would normally be taken care of elsewhere in your application. | |
*/ | |
vm.new_post = { title: null, body: null, user_id: 12345 }; | |
vm.update_post = { title: null, body: null, id: null }; | |
// GET /resource | return a collection of resources | index | HTTP/1.1 200 OK | idempotent | |
function getPosts() { | |
return PostsService.getPosts(); | |
} | |
// DELETE /resource/{id} | delete the resource | destroy | HTTP/1.1 204 No Content | |
function deletePost(postId) { | |
PostsService.deletePost(postId); | |
} | |
// GET /resource/{id} | return a single resource instance | show | HTTP/1.1 200 OK | idempotent | |
function showPost(postId) { | |
PostsService.showPost(postId); | |
} | |
// POST /resource | create a new resource in a collection | store | HTTP/1.1 201 Created | |
function storePost() { | |
PostsService.storePost(this.new_post); | |
} | |
// PUT /resource/{id} | update a resource | update | HTTP/1.1 204 No Content | idempotent | |
function updatePost() { | |
PostsService.updatePost(this.update_post); | |
} | |
}]); | |
/** | |
* Configure PostsService | |
* | |
* @require Restangular | |
* @require growl (actually, quite optional, but it lets you see what's going on) | |
*/ | |
app.service('PostsService', ['Restangular', 'growl', function (Restangular, growl) { | |
/** | |
* I've made every attempt to conform to the standards set forth by {json:api}, "A standard for building APIs in JSON" | |
* - http://jsonapi.org/format/ | |
* | |
* It is definitely in your best interest to read as much as possible and get very familiar with the concepts they introduce. | |
* | |
* Also note that most of the methods here include a big chunk of code for the angular-growl notifications. In production, | |
* you would likely not be using these, but they're extremely helpful for determining what's going on while building your API. | |
*/ | |
/** | |
* Return a collection of resources | |
* | |
* @request HTTP/1.1 GET /resource | |
* @return HTTP/1.1 200 OK | |
*/ | |
this.getPosts = function () { | |
return Restangular.all('posts').getList().$object; | |
}; | |
/** | |
* Return a single resource instance | |
* | |
* @request HTTP/1.1 GET /resource/{id} | |
* @return HTTP/1.1 200 OK | |
*/ | |
this.showPost = function (postId) { | |
return Restangular.one('posts', postId).get().then(function (post) { | |
// Begin growl response popup --------------------------------------- | |
post = post.plain(); | |
var resp = "GET /posts/" + postId + "<br><br>"; | |
resp += "HTTP/1.1 200 OK<br><br>"; | |
Object.keys(post).forEach(function (k, i) { | |
resp += '<strong>' + k + '</strong>: "' + post[k] + '",<br>'; | |
}); | |
growl.addInfoMessage(resp, {ttl: 5000, enableHtml: true}); | |
// End growl popup -------------------------------------------------- | |
}); | |
}; | |
/** | |
* Delete the resource | |
* | |
* @request HTTP/1.1 DELETE /resource/{id} | |
* @return HTTP/1.1 204 No Content | |
*/ | |
this.deletePost = function (postId) { | |
return Restangular.one('posts', postId).remove().then(function () { | |
/** | |
* NOTE: it is dependent on the specific details of your implementation how you want to remove the deleted entities from your | |
* data model. For this example, no posts are removed, as they're not actually being deleted from the demo API. | |
*/ | |
// Begin growl response popup --------------------------------------- | |
var resp = "DELETE /posts/" + postId + "<br><br>"; | |
resp += "HTTP/1.1 204 No Content<br><br>"; | |
resp += "Post ID #" + postId + " successfully deleted"; | |
growl.addErrorMessage(resp, {ttl: 5000, enableHtml: true}); | |
// End growl popup -------------------------------------------------- | |
}); | |
}; | |
/** | |
* Create a new resource in a collection | |
* | |
* @request HTTP/1.1 POST /resource | |
* @return HTTP/1.1 201 Created | |
*/ | |
this.storePost = function (newPost) { | |
if ((newPost.title == null) || (newPost.body == null)) return false; | |
return Restangular.all("posts").post(newPost).then(function (post) { | |
/** | |
* NOTE: Creating a new resource entity SHOULD return HTTP 201 Created, but the demo API returns 200 OK. | |
*/ | |
// Begin growl response popup --------------------------------------- | |
post = post.plain(); | |
var resp = "POST /posts<br><br>"; | |
resp += "HTTP/1.1 201 Created<br><br>"; | |
Object.keys(post).forEach(function (k, i) { | |
resp += '<strong>' + k + '</strong>: "' + post[k] + '",<br>'; | |
}); | |
growl.addSuccessMessage(resp, {ttl: 5000, enableHtml: true}); | |
// End growl popup -------------------------------------------------- | |
}); | |
}; | |
/** | |
* Update an existing resource | |
* | |
* @request HTTP/1.1 PUT /resource/{id} | |
* @return HTTP/1.1 204 No Content | |
*/ | |
this.updatePost = function (updatePost) { | |
if ((updatePost.id == null) || (updatePost.title == null) || (updatePost.body == null)) return false; | |
var postId = updatePost.id; | |
/** | |
* NOTE: Updating an existing resource SHOULD return HTTP 204 No Content, but the demo API returns 200 OK with the updated entity. | |
* | |
* NOTE 2: We're using customPUT() instead of put() to eliminate the redundant GET request that occurs when you select an element | |
* and then start performing operations on it. Using customPUT() allows us to just send the object directly to the intended endpoint. | |
*/ | |
return Restangular.one('posts', postId).customPUT(updatePost).then(function (post) { | |
// Begin growl response popup --------------------------------------- | |
post = post.plain(); | |
var resp = "PUT /posts/" + postId + "<br><br>"; | |
resp += "HTTP/1.1 204 No Content<br><br>"; | |
Object.keys(post).forEach(function (k, i) { | |
resp += '<strong>' + k + '</strong>: "' + post[k] + '",<br>'; | |
}); | |
growl.addInfoMessage(resp, {ttl: 5000, enableHtml: true}); | |
// End growl popup -------------------------------------------------- | |
}); | |
}; | |
}]); | |
})(); | |
</script> | |
<style> | |
/* ui.bootstrap styling */ | |
.nav, .pagination, .carousel, .panel-title a, a:not([href]) { cursor: pointer; } | |
/* angular-growl messages offset */ | |
.growl { top: 20px; right: 20px; width: 350px; z-index: 9999; opacity: 0.85; } | |
</style> | |
</head> | |
<body> | |
<div growl></div> | |
<div class="container"> | |
<h1>AngularJS API Frontend "hello world" Example</h1> | |
<!-- See above for more on the "controller as" syntax --> | |
<div ng-controller="PostsController as vm"> | |
<hr> | |
<h2>Create a new post</h2> | |
<form class="form-inline" role="form"> | |
<div class="form-group"> | |
<input ng-model="vm.new_post.title" type="text" class="form-control" placeholder="Post Title" required> | |
</div> | |
<div class="form-group"> | |
<input ng-model="vm.new_post.body" type="text" class="form-control" placeholder="Post Body" required> | |
</div> | |
<input type="submit" ng-click="vm.storePost()" class="btn btn-success" value="Create Post"> | |
</form> | |
<hr> | |
<h2>Update existing post</h2> | |
<form class="form-inline" role="form"> | |
<div class="form-group"> | |
<input ng-model="vm.update_post.id" type="text" class="form-control" placeholder="ID" style="width: 45px;" required> | |
</div> | |
<div class="form-group"> | |
<input ng-model="vm.update_post.title" type="text" class="form-control" placeholder="Post Title" required> | |
</div> | |
<div class="form-group"> | |
<input ng-model="vm.update_post.body" type="text" class="form-control" placeholder="Post Body" required> | |
</div> | |
<input type="submit" ng-click="vm.updatePost()" class="btn btn-success" value="Update Post"> | |
</form> | |
<hr> | |
<h2>Viewing all posts</h2> | |
<ul class="list-group"> | |
<li class="list-group-item" ng-repeat="post in vm.posts"> | |
<a class="btn btn-danger btn-xs pull-right" ng-click="vm.deletePost(post.id)" style="margin-left: 5px;">delete</a> | |
<a class="btn btn-primary btn-xs pull-right" ng-click="vm.showPost(post.id)">load info</a> | |
<p style="margin:0;" ng-click="vm.showPost(post.id)"><a style="font-weight: bold;">{{ post.title }}</a></p> | |
</li> | |
</ul> | |
</div> | |
</div> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment