angular js blog tutorial
DESCRIPTION
Tutorial to create a blog using AngularJS. The slides were originally used for a study meetup at our office.TRANSCRIPT
![Page 2: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/2.jpg)
LEAVESSTATIC WEBSITE BUILD TOOL
Depends only on NodeJS.
Uses:
Yeoman
Grunt
Bower
Jade (or EJS)
Stylus (or less or plain CSS)
Coffee (or plain JS)Install with:
$ npm install -g leaves$ leaves setup
Checkout for more info.the docs
![Page 3: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/3.jpg)
PROJECT CREATION$ leaves new angular-blog$ cd angular-blog$ leaves install jquery angular angular-resource bootstrap angular-ui-router markdown
Rename assets/js/app.coffee to assets/js/app.js,
erase assets/css/main.styl content and edit views/layout.jade.
// views/layout.jadehtml head ....
link(rel="stylesheet" href="components/bootstrap/dist/css/bootstrap.min.css") link(rel="stylesheet" href="components/bootstrap/dist/css/bootstrap-theme.min.css")
script(src="components/jquery/dist/jquery.min.js") script(src="components/angular/angular.min.js") script(src="components/angular-resource/angular-resource.min.js") script(src="components/angular-ui-router/release/angular-ui-router.min.js") script(src="components/bootstrap/dist/js/bootstrap.min.js")
script(src="js/app.js") body block content
![Page 4: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/4.jpg)
START HACKINGWhen you are done with basic setup, run
$ leaves
and start hacking.
FOR SCEPTICAL PEOPLEIf you do not want to use leaves, check about basic Angular setup.my blog post
![Page 5: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/5.jpg)
TRY ANGULARJSInitialize application
// views/layout.jadehtml head ... body(ng-app="") block content
Try two-way data binding:
Output variable value with: {{variable}}.
Change variable value with: ng-model="variable"// views/index.jadeextends ./layout.jade
block content input(ng-model="variable") span {{variable}}
![Page 6: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/6.jpg)
TRY ANGULARJSInitialize variable:
// views/index.jadeextends ./layout.jade
block content div(ng-init="variable = 'plain text'") span {{variable}}
You can use any element, not just div and span.
![Page 7: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/7.jpg)
TRY ANGULARJSUse controller to initialize variable.
// views/index.jadediv(ng-controller="TestCtrl") span {{variable}}
Define controller in JS file.
$scope is injected by Angular on instanciation.
// assets/js/app.jsfunction TestCtrl($scope) { $scope.variable = "my variable text";}
Angular uses to instanciate controllers, services, etc.DI
![Page 8: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/8.jpg)
TRY ANGULARJSReact to events:
// views/index.jadediv(ng-controller="TestCtrl") span {{variable}} button(ng-click="action()")
// assets/js/app.jsfunction TestCtrl($scope) { $scope.variable = "my variable text"; $scope.action = function () { $scope.variable = "I just clicked!"; };}
![Page 9: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/9.jpg)
CREATE BLOGCreate blog with following functionalities:
List posts
List posts by category
Show post
Create new postWe will use prepared API to work with.
Authentication/authorization will be for next time.
Sample is available at .angular-blog-sample.herokuapp.com
Full source code is available at .
github.com/claudetech-meetups/angular-blog-sample
![Page 10: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/10.jpg)
AVAILABLE APIThe available API calls are
GET /posts
GET /posts/:id
POST /posts
GET /categories
POST /categoriesAPI is available at: http://angular-tutorial-api.herokuapp.com/
![Page 11: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/11.jpg)
CREATE BASIC LAYOUTAdd header to views/layout.jade
html head .... body(ng-app="") .container nav.navbar.navbar-default .container-fluid .navbar-header a.navbar-brand(href="#") Blog .row .col-xs-8 block content .col-xs-4 .block.categories h3 Categories ul.list-unstyled li: a.small(href="#") Plenty of categories .block.admin h3 Admin ul.list-unstyled li: a.small(href="#") Add post
![Page 12: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/12.jpg)
CREATE BASIC LAYOUTCreate empty views/posts/index.jade and edit views/index.jade.
// views/index.jadeextends ./layout.jade
block content include ./posts/index
![Page 13: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/13.jpg)
BUILD POST LISTCreate controller in JS file:
function PostIndexCtrl($scope) { $scope.post = { id: 1, title: "Post title", content: "Post content", createdAt: new Date() };}
Wrap post list in a controller and use dummy data
.posts(ng-controller="PostIndexCtrl") .post.row .row .col-xs-12 h2 {{post.title}} small.date {{post.createdAt}} .row.content .col-xs-12 {{post.content}} .row .col-xs-12 a.small(href="#") See more
and create the controller.
![Page 14: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/14.jpg)
SOME ISSUES{{variable}} appears on page load
Date format is strange
No linebreak in content
Content may be very long
![Page 15: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/15.jpg)
SOME SOLUTIONSUse ng-bind instead of {{}}Use date filter
We will see how to render markdown in content later on
Use limitTo filter
.posts(ng-controller="PostIndexCtrl") .post.row .row .col-xs-12 h2(ng-bind="post.title") small.date(ng-bind="post.createAt|date:'y/M/d'") .row.content .col-xs-12(ng-bind="post.content|limitTo:100") .row .col-xs-12 a.small(href="#") See more
![Page 16: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/16.jpg)
MAKE IT A LISTAdd dummy data in the controller.
function PostIndexCtrl($scope) { $scope.posts = [{ id: 1, title: "Post title", content: "Post content", createdAt: new Date() }, { id: 2, title: "Post title 2", content: "Post content 2", createdAt: new Date() }];}
Use ng-repeat.posts(ng-controller="PostIndexCtrl") .post.row(ng-repeat="post in posts") ...
![Page 17: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/17.jpg)
SHOW SINGLE POSTCreate views/posts_show.jade, we will extend layout.jade for now.
extends ./layout.jade
block content .post.row(ng-controller="PostShowCtrl") .row .col-xs-12 h2 {{post.title}} small.date {{post.createdAt|date:'y/M/d'}} .row.content .col-xs-12 {{post.content}}
create PostShowCtrl function.
// assets/js/app.js....function PostShowCtrl($scope) { $scope.post = { id: 1, title: "Post title", content: "Post content", createdAt: new Date() };}
You can try to access your page: localhost:9000/posts_show.html
![Page 18: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/18.jpg)
SOME ISSUESBetter modularize controllers
We don't want another page
We want content in markdown
![Page 19: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/19.jpg)
MODULARIZATIONLet's start by splitting files.
// assets/js/controllers/posts/index.jsfunction PostIndexCtrl($scope) { .....}
// assets/js/controllers/posts/show.jsfunction PostShowCtrl($scope) { .....}
// views/layout.jadehtml head .... script(src="js/app.js") script(src="js/controllers/posts/index.js") script(src="js/controllers/posts/show.js") body(ng-app="") ...
![Page 20: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/20.jpg)
ANGULAR MODULESAngular has native modules. Declare BlogApp module with no depencies.
// assets/js/app.jsangular.module('BlogApp', []);
Declare controllers as part of the module.
// assets/js/controllers/posts/index.jsangular.module('BlogApp').controller('PostIndexCtrl', [ '$scope', function ($scope) { ... }]);
Same goes for PostShowCtrl.
![Page 21: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/21.jpg)
ROUTINGRoute in order to be able to have
/#/ -> all posts
/#/posts/:id -> post with id = :id/#/posts/new -> new post
First, add depency to the module.ui.router// assets/js/app.jsangular.module('BlogApp', [ 'ui.router']);
![Page 22: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/22.jpg)
SETUP VIEWFirst, add the view where to display the content of the route.
// views/index.jadeextends ./layout.jade
block content div(ui-view)
![Page 23: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/23.jpg)
SETUP ROUTERConfigure to redirect to / when no route found, and configure the routes.
// assets/js/app.js...
angular.module('BlogApp').config([ '$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) { $urlRouterProvider.otherwise('/');
$stateProvider .state('index', { url: '/', templateUrl: 'posts/index.html' }) .state('show', { url: '/posts/:id', templateUrl: 'posts/show.html' }); }]);
Then, remove the extends and block content fromviews/posts_show.jade: we don't need the whole layout. For consistency,move views/post_show.jade to views/posts/show.jade.
You can now access your route: localhost:9000/#/posts/1
![Page 24: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/24.jpg)
SETUP ROUTERControllers should be defined in routes.
// assets/js/app.js .state('index', { url: '/', templateUrl: 'posts/index.html', controller: 'PostIndexCtrl' }) .state('show', { url: '/posts/:id', templateUrl: 'posts/show.html', controller: 'PostShowCtrl' });
and removed from views/posts/index.jade andviews/posts/show.jade.
// views/posts/show.jade.post.row // no ng-controller anymore ...
![Page 25: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/25.jpg)
ADD LINKS uses ui-sref to make link instead of normal href or ng-href
attributes.ui-router
// views/posts/index.jade... a.small(ui-sref="show({ id: post.id })") See more
![Page 26: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/26.jpg)
USE APIAdd ngResource module as a dependency.
// assets/js/app.jsangular.module('BlogApp', [ 'ui.router', 'ngResource']);...
Try it
// assets/js/controllers/posts/index.jsangular.module('BlogApp').controller('PostIndexCtrl', [ '$scope', '$resource', function ($scope, $resource) { // no need for dummy data anymore var Post = $resource('http://angular-tutorial-api.herokuapp.com/posts/:id'); Post.query(function (posts) { console.log(posts[0]); $scope.posts = posts; }); }]);
![Page 27: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/27.jpg)
RESOURCE FACTORYWe want to be able to use Post resource anywhere.
Let's make it a factory.
// assets/js/resources/post.jsangular.module('BlogApp').factory('Post', [ '$resource', function ($resource) { return $resource('http://angular-tutorial-api.herokuapp.com/posts/:id'); }]);
and include it in views/layout.jadehtml head ... script(src="js/resources/post.js")
![Page 28: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/28.jpg)
USE RESOURCE FACTORY// assets/js/controllers/index.jsangular.module('BlogApp').controller('PostIndexCtrl', [ '$scope', 'Post', function ($scope, Post) { Post.query(function (posts) { $scope.posts = posts; }); }]);
![Page 29: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/29.jpg)
FETCH SINGLE POSTUse $stateParams to get id, and get post from server.
// assets/js/controllers/posts/show.jsangular.module('BlogApp').controller('PostShowCtrl', [ '$scope', '$stateParams', 'Post', function ($scope, $stateParams, Post) { Post.get({id: $stateParams.id}, function (post) { $scope.post = post; }); }]);
![Page 30: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/30.jpg)
CREATE CATEGORY FACTORY// assets/js/resources/category.jsangular.module('BlogApp').factory('Category', [ '$resource', function ($resource) { return $resource('http://angular-tutorial-api.herokuapp.com/categories/:id'); }]);
and add it to layout.jade
![Page 31: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/31.jpg)
NEW POST CONTROLLERCreate new JS file, and a fresh post to bind.
// assets/js/controllers/posts/new.jsangular.module('BlogApp').controller('PostNewCtrl', [ '$scope', 'Post', function ($scope, Post) { $scope.post = new Post(); }]);
and include it in views/layout.jadescript(src="js/controllers/posts/new.js")
![Page 32: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/32.jpg)
CREATE NEW POST TEMPLATEBind the model created in the controller.
// views/posts/new.jadeh2 Create new post
form .form-group label(for="title") Title input#title.form-control(type="text" placeholder="Title" ng-model="post.title") .form-group label(for="content") Content textarea#content.form-control(rows="10" ng-model="post.content") .form-group select.form-control(ng-model="post.category_id") input.btn.btn-default(type="submit" value="Create")
![Page 33: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/33.jpg)
ADD A NEW ROUTECareful, order matters!
$stateProvider .state('index', { ..... }) .state('new', { url: '/posts/new', templateUrl: 'posts/new.html', controller: 'PostNewCtrl' }) .state('show', { ..... });
And set link
// views/layout.jade... .block.admin h3 Admin ul.list-unstyled li: a.small(ui-sref="new") Add post
![Page 34: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/34.jpg)
RESOLVE CATEGORIESBetter have categories before loading template. Use custom made promise andresolve API result.
// assets/js/app.js... .state('new', { url: '/posts/new', templateUrl: 'posts/new.html', controller: 'PostNewCtrl', resolve: { categories: ['$q', 'Category', function ($q, Category) { var deferred = $q.defer(); Category.query(function (categories) { deferred.resolve(categories); }); return deferred.promise; }] } }) .state('show', { ....
![Page 35: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/35.jpg)
RESOLVE CATEGORIESUse the categories resolved in the controller.
// assets/js/controllers/posts/new.js
angular.module('BlogApp').controller('PostNewCtrl', [ '$scope', 'Post', 'categories', function ($scope, Post, categories) { $scope.post = new Post(); $scope.categories = categories; }]);
![Page 36: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/36.jpg)
USE NG-OPTIONSDynamically show categories in select using ng-options.
form ... select.form-control( ng-model="post.category_id" ng-options="c.id as c.name for c in categories" )
![Page 37: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/37.jpg)
CREATE POSTReact to submit event
// views/posts/new.jade
form(ng-submit="createPost()")
Add handler for submit event.
// js/controllers/posts/new.jsangular.module('BlogApp').controller('PostNewCtrl', [ '$scope', 'Post', '$state', 'categories', function ($scope, Post, $state, categories) { ... $scope.createPost = function () { $scope.post.$save(function (post) { $state.go('index', {id: post.id}); }); }; ...
![Page 38: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/38.jpg)
FORMAT MARKDOWNWe are going to create a filter to format markdown.
// assets/js/filters/markdown.js
angular.module('BlogApp').filter('markdown', [ '$sce', function ($sce) { return function (input) { if (!input) { return ''; } return $sce.trustAsHtml(markdown.toHTML(input)); }; }]);
input is the value to convert to markdown. We return empty string if
undefined.
The markdown is formatted in HTML, we need to trust the input to tell Angular
it is safe.
![Page 39: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/39.jpg)
DISPLAY FORMATTEDMARKDOWN
We use ng-bind-html to display HTML and not escaped values.
// views/posts/show.jade.post.row .... .row.content .col-xs-12(ng-bind-html="post.content|markdown")
![Page 40: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/40.jpg)
CREATE CATEGORIESCONTROLLER
As for posts, query to get all the categories.
// assets/js/controllers/categories/index.jsangular.module('BlogApp').controller('CategoryIndexCtrl', [ '$scope', 'Category', function ($scope, Category) { Category.query(function (categories) { $scope.categories = categories; }); }]);
![Page 41: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/41.jpg)
SHOW CATEGORIESCreate partial
// views/categories/index.jade
.block.categories(ng-controller="CategoryIndexCtrl") h3 Categories ul.list-unstyled li(ng-repeat="category in categories") a.small(ui-sref="index({category: category.id})" ng-bind="category.name")
Change layout to use partial.
// views/layout.jade.... .col-xs-4 include ./categories/index .block.admin h3 Admin ul.list-unstyled li: a.small(ui-sref="new") Add post
![Page 42: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/42.jpg)
UPDATE ROUTESUpdate index routes to take category query parameter.
// assets/js/app.js...
$stateProvider .state('index', { url: '/?category', templateUrl: 'posts/index.html', controller: 'PostIndexCtrl' })
This is needed to pass category to ui-sref and to read it from$stateParams.
![Page 43: Angular JS blog tutorial](https://reader034.vdocuments.us/reader034/viewer/2022042602/557b631ed8b42a12578b45dd/html5/thumbnails/43.jpg)
FILTER QUERYCheck if there is a category defined and adapt query.
// assets/js/controllers/posts/index.js
angular.module('BlogApp').controller('PostIndexCtrl', [ '$scope', '$stateParams', 'Post', function ($scope, $stateParams, Post) { var search = $stateParams.category ? {category_id: $stateParams.category} : {}; Post.query(search, function (posts) { $scope.posts = posts; }); }]);