Angular JS blog tutorial
-
Upload
claude-tech -
Category
Technology
-
view
656 -
download
0
description
Transcript of Angular JS blog tutorial
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
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
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
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}}
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.
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
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!"; };}
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
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/
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
CREATE BASIC LAYOUTCreate empty views/posts/index.jade and edit views/index.jade.
// views/index.jadeextends ./layout.jade
block content include ./posts/index
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.
SOME ISSUES{{variable}} appears on page load
Date format is strange
No linebreak in content
Content may be very long
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
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") ...
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
SOME ISSUESBetter modularize controllers
We don't want another page
We want content in markdown
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="") ...
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.
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']);
SETUP VIEWFirst, add the view where to display the content of the route.
// views/index.jadeextends ./layout.jade
block content div(ui-view)
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
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 ...
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
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; }); }]);
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")
USE RESOURCE FACTORY// assets/js/controllers/index.jsangular.module('BlogApp').controller('PostIndexCtrl', [ '$scope', 'Post', function ($scope, Post) { Post.query(function (posts) { $scope.posts = posts; }); }]);
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; }); }]);
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
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")
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")
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
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', { ....
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; }]);
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" )
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}); }); }; ...
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.
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")
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; }); }]);
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
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.
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; }); }]);