Skip to content

Instantly share code, notes, and snippets.

@primathon
Last active August 29, 2015 14:08
Show Gist options
  • Save primathon/4ccb35ad6ddece4af1db to your computer and use it in GitHub Desktop.
Save primathon/4ccb35ad6ddece4af1db to your computer and use it in GitHub Desktop.
AngularJS API Frontend "hello world" Example
<!--
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