basic SPA (single page app) boilerplate for fast prototyping in angular

Creating this inside a Vagrant image (meaning there are extra steps to install the needed basic packages):

Creating the Vagrant workspace:

vagrant init ubuntu/trusty64;
vagrant up; vagrant ssh;
vi Vangrant # and then port forward 8080 to 8080 on the host

Creating the boilerplate:

mkdir myapp; cd myapp;
sudo apt-get update;
sudo apt-get install git;
sudo apt-get install npm;
sudo ln -s /usr/bin/nodejs /usr/bin/node;
npm init;
sudo npm install -g bower;
bower init;
touch main.js
touch index.html
git init
vi .gitignore
# include the following inside your .gitignore file:
mode_modules
bower_components
# back to the command line:
bower install --save angular
bower install --save ui-router

HTML / JS basic bootstrap

main.js

angular.module(“MyAppName”, [‘ui-router’])

.config(function ($stateProvider, $urlRouterProvider) {

});

index.html

<head>
<title>
My App Title
</title>
</head>
<body ng-app=MyAppName>
<h1>My App</h1>
<div ui-view></div>
<script src=bower_components/angular/angular.js></script>
<script src=bower_components/angular-ui-router/release/angular-ui-router.js></script>
<script src=main.js></script>
</body>

Quickly testing the page without setting up apache:

sudo npm install -g http-server

http-server

ES6: the basics

let declarations

Allow you to define a variable that only exist inside a block delineated by { and }

Example:

var a = 0;
{
  let a = 1;
  console.log(a); // prints 1
}
console.log(a); // prints 0

let declarations are not initialized until they are used, that is why it is a good idea to put them at the top of the block, to avoid weird behavior. If you are not ready to attach a value, you can just say: let a;

const declarations

By using const, once the assignment has been made, it can’t be undone.

Example:

{
    const a = 7;
    console.log(a);   // 7
    a = 5;              // TypeError!
}

If you assign Objects or Arrays to constants, you can still modify the values, you just can’t modify the assignment type:

{
    const a = [1,2,3];
    a.push(4);
    console.log(a);       // [1,2,3,4]
    a = 42;                 // TypeError!
}

Angularjs: template to setup just the UI part

Setting this up with Vagrant (therefore the extra steps / packages downloaded):

$ mkdir src; vagrant init ubuntu/trusty64

$ vi Vagrantfile // modify the following lines:

from:  # config.vm.network “forwarded_port”, guest: 80, host: 8080

to:

  config.vm.network :”forwarded_port”, guest: 3000, host: 3000

from: # config.vm.synced_folder “../data”, “/vagrant_data”

to:

 config.vm.synced_folder “./src”, “/home/vagrant/[your project name]”

$ vagrant ssh

Once you are inside the vagrant instance:

$ sudo apt-get install git

git clone –depth=1 https://github.com/angular/angular-seed.git [your_project_name]

Installing NPM:

sudo apt-get update

$ sudo apt-get install nodejs

$ sudo apt-get install npm

$ sudo ln -s /usr/bin/nodejs /usr/bin/node

Initialize and start the project:

npm install

$ python -m SimpleHTTPServer 3000 // to see your files in a simple server

The rest of the details (like testing, and updating angular and the other packages) are in:

https://github.com/angular/angular-seed

Running your code in production:

You just need all the static files inside your app/ directory in a server, that’s it!

angular.js: CRUD operations via $http

These are the basic operations you can do with $http:

  • $http.get(): Accepts aURL and optional config object. Performs an HTTP GETrequest.
  • $http.head(): Accepts a URL and optional config object. Performs an HTTP HEADrequest.
  • $http.post(): Accepts a URL, data object, and optional config object. Performs an HTTP POST request.
  • $http.put(): Accepts a URL, data object, and optional config object. Performs an HTTP PUT request.
  • $http.delete(): Accepts a URL and optional config object. Performs an HTTPDELETE request.
  • $http.jsonp(): Accepts a URL and optional config object. The callback name should be the string JSON_CALLBACK.
  • $http.patch(): Accepts a URL, data object, and optional config object. Performs an HTTP PUT request.

Example of calling the get() method:

$http({
    method: 'GET',
    url: 'http://localhost:8000'
});

$http.get('http://localhost:8000');

You can easily chain promises to handle success or error returns from the server:

$http.get('http://localhost:8000')
.success(function(data){
    $scope.contacts = data;
})
.error(function(){
    window.alert('There was an error!');
});

Example of a post:

$http.post('http://localhost:8000', {
    name: 'Declan Proud',
    email: 'declan@example.com',
    ...
});

angular.js: directives

Basic structure:

myModule.directive('directiveName', function(){
    return {
        restrict: 'AE',
        replace: true,
        template: '<div class="{{class}}">some html, this could also be a reference to an external template. In which case, you can use templateUrl</div>', link: function(scope, elem, attrs){ // elem is the HTML element the directive was applied to, and attrs are the attributes of the same } } })

The possible restrict values:

  • A: This restricts the directive to be attached using an attribute
  • E: This allows the directive to be used as a custom element
  • C: This allows the directive to be used by adding it as a class to the element
  • M: Allows the directive to be executed via an HTML comment

Other options:

replace: true substitutes the DOM element for the template (instead of just filling out their inner HTML)

On Gulp

– Starting up:

$ npm install –global gulp

$ npm install –save gulp

– Create a gulpfile.js on your root directory, sample content:

var gulp = require(‘gulp’);

gulp.task(‘welcome’, function () {

  console.log(‘gulp is working!’);

});

– run your gulp functions / commands:

$ gulp welcome

note: some linux installations refer to nodejs as “node” internally. If you get the following error message when running your gulp commands:

/usr/bin/env: node: No such file or directory

that means you need to create a soft link to alias node to nodejs:

ln -s /usr/bin/nodejs /usr/bin/node

– install the pluging that will let you concat your js files together in one big file:

$ npm install –save gulp-concat

– install the plugin that will compact and uglify your js (needs to go along with the other one, to make uglification compatible with angular)

npm install –save gulp-uglify

$ npm install –save gulp-ng-annotate

– here is an example that will concat and uglify into an app.js file, and put it into your assets directory, loading file ng/module.js first in the resulting file:

var gulp = require(‘gulp’);

var concat = require(‘gulp-concat’);

var uglify = require(‘gulp-uglify’);

var ngAnnotate = require(‘gulp-ng-annotate’);

gulp.task(‘js’, function () {

  gulp.src([‘ng/module.js’, ‘ng/**/*.js’])

    .pipe(concat(‘app.js’))

    .pipe(ngAnnotate())

    .pipe(uglify())

    .pipe(gulp.dest(‘assets’));

});

 

boostrapping a MEAN app (angular.js, express, node.js, mongodb)

Complete code example here

– in the folder you are creating your app, place a manifest file called package.json, example:

{
“name”: “yourappnamehere”
}

– install express and other dependencies:

$ sudo apt-get update

$ sudo apt-get install npm

sudo apt-get install nodejs

Note: copying and pasting these commands sometimes will give you the following error message:

TypeError: Cannot read property ‘latest’ of undefined

make sure to retype the “–” part manually, and you’ll be all right

$ sudo npm install –save express

$ sudo npm install –save body-parser

– if you want node to automatically restart when there are changes in the files, you can also install the following package:

$ npm install –global nodemon

– and then, when you start your server, you need to start it as:

$ npm install –global nodemon

– install mongodb, create the following file on your home directory: mongo_install.bash, with the following content:

apt-key adv --keyserver keyserver.ubuntu.com --recv 7F0CEB10
echo "deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen" | tee -a /etc/apt/sources.list.d/10gen.list
apt-get -y update
apt-get -y install mongodb-10gen

– run sudo bash ./mongo_install.bash

– the installation starts mongod by default, but that is the daemon you need to start if you don’t see it running

– install mongoose:

$ npm install –save mongoose

– to enter the console mode (and verify the installation), type mongo, if you want to connect to an specific db, you do:

mongo nameofyourdbhere

> db.posts.find()

that will give you all the records saved under the Post model

– create a server.js file, that will host your app (see code in heroku instance for content details)

– run your server:

nodejs server.js

(config.vm.network :forwarded_port, guest: 3000, host: 3000 on Vagrantfile if you are running inside vagrant)

– server.js is kind of your single point of entry for your app. It is always a good idea to keep it lean, and move as much code as possible away from it into other files. Some things that are worth having at this file are:

— the server listening loop

— global configuration and other middleware packages

— logging and error handling

— controllers spawning and mounting

– on static files: it is a good idea not to serve them via nodejs. Try to keep your node instance as an API, and let apache and other cache services to do the static servers job. But if you must, it is always a good idea to spin them into:

/controllers/static.js

and inside that file:

var express = require(‘express’)
var router = express.Router()

router.use(express.static(__dirname + ‘/../assets’))

– so now any file you put on your /assets folder will be served by node

– on services: things like $http are better constructed via a service, and then injected to wherever they are needed. Below is an example of doing just that:

app.service(‘PostsSvc’, function ($http) {

  this.fetch = function () {

    return $http.get(‘/api/posts’)

  }

  this.create = function (post) {

    return $http.post(‘/api/posts’, post)

  }

});

– and then, the controllers that consume it would looks something like this:

    // create the PostsCtrl module

    // dependency inject $scope

    app.controller(‘PostsCtrl’, function ($scope, PostsSvc) {

      // the function runs when the “Add Post” button is clicked

$scope.addPost = function () {

    if ($scope.postBody) {

      PostsSvc.create({

        username: ‘ramiro’,

        body: $scope.postBody

      }).success(function (post) {

        $scope.posts.unshift(post)

        $scope.postBody = null

      })

    }

  };

 Deploying to Heroku

– create a .gitignore file with the following lines:

node_modules
assets

– if you are in a new vagrant instance, install heroku tools first:

$ wget -qO- https://toolbelt.heroku.com/install-ubuntu.sh | sh

$ heroku login

heroku create your-app-name-here

heroku addons:create mongolab

– check what is the address of your mongolabs instance:

$ heroku config

– modify your db.js file according to what you see printed by that command, it would look something like this:

var mongoose = require(‘mongoose’);

var url = process.env.MONGOLAB_URI || ‘mongodb://localhost/social’;

mongoose.connect(url);

module.exports = mongoose;

– you also need to do a similar move for the listen command in the server.js file:

// process.env.PORT for the benefit of Heroku

app.listen(process.env.PORT || 3000, function () {

  console.log(‘Server listening on’, 3000)

});

bootstapping an angular.js app on top of rails

Complete example here

– The following will skip jquery and turbolinks:

rails new [your-app-name-here] –skip-javascript

– But now you need to manually create the following file: app/assets/javascripts/application.js

– And include the following in the file contents:

//= require_tree .

– put all your angular app files inside app/assets/javascripts, and include the following in your app/views/layouts/application.html.erb template:

<%= javascript_include_tag ‘application’ %>

– Your app/views/layouts/application.html.erb will also be the home page of your single page app, so make sure you put all your initial HTML code in there, and get rid of the default  <%= yield %> in there.

– Add the following to your application controller:

def angular

render ‘layouts/application’

end

– and in your config/ routes.rb file, add a route to it:

root to: ‘application#angular’

– install bower, if you haven’t done so already:

npm install -g bower

– initialize it inside your rails project:

bower init

– create a .bowerrc file, with the following, to tell bower where you are storing your js dependencies:

{ “directory“:“vendor/assets/bower_components” }

– install the dependencies you need, and tell bower to save them in your config file:

bower install angular angular-ui-router bootstrap –save

– now that the libraries you need are installed, modify your app/assets/javascripts/application.js file to call them at page load time:

//= require angular

//= require angular-ui-router

//= require_tree .

– to add the bootstrap CSS, add the following line to the comments section on app/assets/stylesheets/application.css

*= require bootstrap/dist/css/bootstrap

*= require_tree .

Important: that goes inside the header /* comments */

At this point, you have all your js / css dependencies managed by bower, and minified and pulled in the right places by the rails app.

– place your template files inside public/templates, and modify your config file as follows:

.config([

'$stateProvider',
'$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {

$stateProvider
.state(‘home’, {
url: ‘/home’,
templateUrl: ‘templates/_home.html’,
controller: ‘MainCtrl’
}).state(‘posts’, {
url: ‘/posts/{id}’,
templateUrl: ‘templates/_posts.html’,
controller: ‘PostsCtrl’
});

$urlRouterProvider.otherwise(‘home’);
}])

– move your JS to folders inside app/assets/javascripts (create mainCtrl.js inside the home folder, and postsCtrl.js inside the posts folder), so the only thing left inside app.js is the configuration and routes.

– generate your models:

rails generate model Post title:string link:string upvotes:integer

rails generate model Comment body:string upvotes:integer post:references

rake db:migrate

– declare your associations inside your models:

class Post < ActiveRecord::Base

  has_many :comments

end

– if you have children models from one of your models, and you want the children to be returned as part of your JSON, put the following method inside your model as well:

def as_json(options = {})

     super(options.merge(include: :comments))

end

 – setup your base routes:

root to: ‘application#angular’

resources :posts, only: [:create, :index, :show] do

   resources :comments, only: [:show, :create] do

      member do put ‘/upvote’ => ‘comments#upvote’

   end

end

member do put ‘/upvote’ => ‘posts#upvote’ end end

Nesting resources will create urls like this: posts/1/comment/3

By specifying member do, you are also simplifying some of the resources out of the deep nesting

 – using “rake routes” at this point will tell you what kind of routes you have defined

– now is time to create your controllers, make sure you skip the templates and assets creation when doing so:

rails generate controller Posts –skip-assets –skip-template-engine

rails generate controller Comments –skip-assets –skip-template-engine

 – add the following line to your application controller, so it responds back in json format:

respond_to :json

– in the newer versions of rails, you will need to add this gem to your Gemfile to get that respond_to functionality:

gem ‘responders’, ‘~> 2.0’

 – tell your controllers which parameters are allowed, and also create the basic controller methods:

def index
respond_with Post.all
end

def create
respond_with Post.create(post_params)
end

def show
respond_with Post.find(params[:id])
end

def upvote
post = Post.find(params[:id])
post.increment!(:upvotes)

respond_with post
end

private
def post_params
params.require(:post).permit(:link, :title)
end
end

– back to your front end: setup your factory to be able to retrieve all of your records by means of hitting your index method (see how $http is injected, and how the getAll function is implemented as a promise)

angular.module(‘flapperNews’)

.factory(‘posts’, [‘$http’, function(){

  var o = {

    posts: []

  };

  o.getAll = function() {

    return $http.get(‘/posts.json’).success(function(data){

      angular.copy(data, o.posts);

    });

  };

  return o;

}]);

– now, if you want your view to refresh with the server data every time the UI calls “home”, you need to set your stateProvider with the “resolve” property:

$stateProvider

    .state(‘home’, {

      url: ‘/home’,

      templateUrl: ‘templates/_home.html’,

      controller: ‘MainCtrl’,

      resolve: {

            postPromise: [‘posts’, function(posts){

               return posts.getAll();

           }]

      }

    })

 – to be able to create post, add the following to your post service:

o.create = function(post) { return $http.post(‘/posts.json’, post).success(function(data){ o.posts.push(data); }); };

– and, in your main controller:

$scope.addPost = function(){ if(!$scope.title || $scope.title === ) { return; } posts.create({ title: $scope.title, link: $scope.link, }); $scope.title = ; $scope.link = ; };

– by default, rails has protection against fake posts, so in order to save the data you are posting, you will need to add the following gem (otherwise you will be getting “422 unprocessable entry” error messages

gem ‘angular_rails_csrf’

– to add user authentication via Device, you need to install the gem first:

gem ‘devise’, ‘~> 3.4.0’

– after you bundle install, initialize it, and create the user’s model:

rails generate devise:install

rails generate devise User

– if you need to, you can add more fields to the default devise model, which only contain email and password by default. We will also make the username unique:

rails generate migration AddUsernameToUser username:string:uniq

– in order to integrate devise with the front end, you can install the following js package helper via bower:

bower install angular-devise –save

– in application.js, require the newly installed package:

//= require angular-devise

– and inject the module in the main app:

angular.module(‘flapperNews’, [‘ui.router’, ‘Devise’]).

# note: when you try your registration / login forms, if there is a devise error, it may manifest in the front end as a 422 error message, you need to handle the errors as they come from the server

– to secure your posts savings (or any other controller actions for that matter) you can now use the following:

class PostsController < ApplicationController before_filter :authenticate_user!, only: [:create, :upvote]

– if you want to associate two of the models you are working with (in this case posts and users), run the following command, that will create the db migration necessary to do the work:

rails g migration AddUserRefToPosts user:references

rails g migration AddUserRefToComments user:references

rake db:migrate

– if you do so, you need to enhance the models to reflect that association:

class Comment < ActiveRecord::Base

  belongs_to :user

– and your update and create methods also need to include the relationship, so rails knows at save time what users are assigned what records:

def create

respond_with Post.create(post_params.merge(user_id: current_user.id))

end