Single Page App Architecture: Angular Install
So Angular is the new hottness. Everyone wants to learn it and write incredible single-page applications (heretofore referred to as SPAs), but the documentation is crap and every dev I talk to seems to have wildly different ideas about how to structure an Angular application.
Getting started with Angular should not be this difficult. There should be a preferred way to start up with Angular. Experienced devs can do whatever the math they feel like, but beginners need some best practices drilled into their heads before they go hog-wild and start writing trashy Angular. Because it's easy to write trash Angular if you're overconfident in your own understanding of the framework.
For the purpose of these exercises, I'm going to assume that you're developing in OS X. Everything should work the same in Linux. Sorry Windows.
I know that experienced devs have all sorts of opinions on Bower vs. NPM/Browserify and Grunt vs something else and whether or not Yeoman makes any sense at all. I have had great success with the Yeoman/Grunt/Bower front-end stack. It works. The end.
Install your dependencies
Node - There are a bunch of way to install Node.js. I prefer to first install Homebrew and then install node by firing up my terminal and typing brew install node. Node.js now comes pre-packaged with the Node Package Manager (NPM). We'll be using NPM to install the rest of our dependencies.
Global NPM Installs - Some NPM packages need to be installed globally for use at the system level, while others get installed locally on a per-project basis.
Let's install our global dependencies
npm install -g yo grunt bower angular-generator
Almost everything that you do should be version controlled. It's free and easy, so just do it. GitHub is fantastic. [Sign up for a GitHub account] and create a repo. I'm going to name this repo quiver-invoice. Don't add a .gitignore quite yet, and pick whichever license type you like.
Now you'll need to clone your new repo to your local dev environment. I do all of my work inside of /Users/christopheresplin/Development, so I'll navigate to my Development folder and type git clone [email protected]:deltaepsilon/quiver-invoice.git. This creates a folder at /Users/christopheresplin/Development/quiver-invoice with the contents of my repo. CD into quiver-invoice cd quiver-invoice.
Type ls -al to list out the contents of quiver-invoice. Done.
System Configuration (Optional)
These configurations are optional, but they'll help your productivity in the long run, so just do them now.
Set your username and email
git config --global user.name "Joe Schmoe" git config --global user.email "[email protected]"
Prevent white space merge problems
git config --global apply.whitespace nowarn
git config --global core.autocrlf true
If you are on Linux or Mac:
git config --global core.autocrlf input
Copy and paste for abbreviated commands on the command line.
git config --global alias.st status git config --global alias.ci commit git config --global alias.br branch git config --global alias.co checkout git config --global alias.df diff git config --global alias.lg "log --graph --pretty=format:'%Cred%h%Creset | %cn | -%C(yellow)%d%Creset %s %Cgreen(%cr)%Creset' --abbrev-commit --date=relative"
git config --global color.diff auto git config --global color.status auto git config --global color.branch auto
Auto Complete branch data
Edit your .bashrc, .bash_profile or other source files and append:
source /usr/local/git/contrib/completion/git-completion.bash
Note: /usr/local/git is where git is installed. Look for the git-completion.bash file and append the above to the your file. If you're having trouble finding git, type which git. If you're still not finding it (probably because you used Homebrew) like me, copy/paste the guts of git-completion.bash into a scripts directory in your home directory. I, for example, have a copy of git-completion.bash at /Users/christopheresplin/.scripts/git-completion.bash.
Color Code your Terminal output
Edit your .bashrc or.bash_profile and PRE-pend the following:
source /usr/local/git/contrib/completion/git-completion.bash RED="\[\033[0;31m\]" YELLOW="\[\033[0;33m\]" GREEN="\[\033[0;32m\]" NO_COLOUR="\[\033[0m\]" PS1="$GREEN\u$NO_COLOUR:\w$YELLOW\$(__git_ps1)$NO_COLOUR\$ "
My own .bash_profile has the following lines
source /Users/christopheresplin/.scripts/git-completion.bash RED="\[\033[0;31m\]" YELLOW="\[\033[0;33m\]" GREEN="\[\033[0;32m\]" NO_COLOUR="\[\033[0m\]" PS1="$RED\u$NO_COLOUR:\W$YELLOW\$(__git_ps1)$NO_COLOUR ā: "
Set up your OS X/Linux environment for maximum speed
I really like this tutorial about quickly navigating your filesystem from the command line. I have this jump/mark system installed on my local machine and on my Linux servers so that I don't have to type the full paths to most of my folders. I just mark them and jump to them.
You should also install some command line aliases for common tasks. For instance, I hate typing ls -al to list out a directory, so I have it aliased to ll instead. The quickest way to do this is to create a file named .bash_aliases and to edit your .bashrc to look for it. Here's what my .bashrc and .bash_alias files look like.
# /Users/christopheresplin/.bashrc if [ -f ~/.bash_aliases ]; then . ~/.bash_aliases fi if [ -f ~/.bash_marks ]; then . ~/.bash_marks fi
# /Users/christopheresplin/.bash_aliases alias apache='sudo apachectl' alias ll='ls -al' alias gitclean='git ls-files --deleted | xargs git rm' alias reload='source ~/.bash_profile' alias redisdrop='redis-cli KEYS "*" | xargs redis-cli DEL' alias mongodrop='mongo quake --eval "db.dropDatabase()"' alias dropall='redisdrop mongodrop' alias fo='sudo -E forever' alias redisstart="sudo launchctl start io.redis.redis-server" alias redisstop="sudo launchctl stop io.redis.redis-server" alias show="defaults write com.apple.finder AppleShowAllFiles TRUE && killall Finder" alias hide="defaults write com.apple.finder AppleShowAllFiles FALSE && killall Finder"
Once you have this all figured out, you should be able to CD into quiver-invoice or whatever your project is called and type mark invoice. Then, from anywhere in your filesystem, you can always type jump invoice and return to the quiver-invoice folder. And once you're there, you'll see that your current git branch is displayed in your command line prompt.
christopheresplin:quiver-invoice (master) ā:
You also have aliases for your git commands, so you can type git co instead of git checkout, git br intead of git branch, and git lg instead of git log. And your git log will be beautiful! Try logging your commit history to glory in it's excellence.
Now jump to quiver-invoice and scaffold out an Angular app using yo angular quiver-invoice. Don't include bootstrap, angular-resource or angular-cookies, but do include angular-sanitize. It's super useful. Enjoy the crazy terminal readout as Yeoman installs a ton of files into your directory.
I hate that Bower installs everything by default into app/bower_components, so I always create a file named .bowerrc and change the name to app/lib using vim .bowerrc. Then I rename app/bower_components to app/lib using mv app/bower_components app/lib. You'll need to do a find/replace inside app/index.html for all of the references to "bower_components" that should read "lib". We'll also need to update our .gitignore file to ignore /app/lib instead of /app/bower_components.
{ "directory": "app/lib" }
You may need a quick vim tutorial. Just learn the basics. You'll need to edit files via command line, especially when it's time to deploy your application to a Virtual Private Server (VPS).
The Angular project has become more modular in recent releases, and some of the best Angular code is outside of the core project, so I prefer to start each project with a handful of the most useful modules. Install these via command line from the root directory of your app, in my case, I jump to quiver-invoice.
bower install --save angular-ui-router restangular angular-firebase momentjs angular-google-analytics angular-cache foundation foundation-icons foundation-icon-fonts
Bower will let you know if you need to resolve any angular dependencies. It doesn't usually matter how you resolve them... I just pick the most recent version of Angular.
The --save flag tells bower to save your installations in /bower.json, so that you can run commands like bower install and bower update to quickly install or update all of your dependencies. This is critical for working in teams. Use Sindre Sorhus's Bower Components application to search for additional Bower components that you can install with bower install --save.
Now make sure to add all of the JS modules as script tags in index.html
<!-- build:js scripts/modules.js --> <script src="lib/angular-sanitize/angular-sanitize.js"></script> <script src="lib/angular-cache/dist/angular-cache.js"></script> <script src="lib/angular-google-analytics/src/angular-google-analytics.js"></script> <script src="lib/angular-ui-router/release/angular-ui-router.js"></script> <script src="lib/jquery/jquery.js"></script> <script src="lib/lodash/dist/lodash.js"></script> <script src="lib/modernizr/modernizr.js"></script> <script src="lib/momentjs/moment.js"></script> <script src="lib/restangular/dist/restangular.js"></script> <script src="lib/firebase/firebase.js"></script> <script src="lib/angularfire/angularfire.js"></script> <!-- endbuild -->
Create a new file at /styles/screen.scss and populate it with a few preliminary imports.
// /styles/screen.scss // Make sure the charset is set appropriately @charset "UTF-8"; // Behold, here are all the Foundation components. @import "../lib/foundation/scss/foundation/components/accordion", "../lib/foundation/scss/foundation/components/alert-boxes", "../lib/foundation/scss/foundation/components/block-grid", "../lib/foundation/scss/foundation/components/breadcrumbs", "../lib/foundation/scss/foundation/components/button-groups", "../lib/foundation/scss/foundation/components/buttons", "../lib/foundation/scss/foundation/components/clearing", "../lib/foundation/scss/foundation/components/dropdown", "../lib/foundation/scss/foundation/components/dropdown-buttons", "../lib/foundation/scss/foundation/components/flex-video", "../lib/foundation/scss/foundation/components/forms", "../lib/foundation/scss/foundation/components/grid", "../lib/foundation/scss/foundation/components/inline-lists", "../lib/foundation/scss/foundation/components/joyride", "../lib/foundation/scss/foundation/components/keystrokes", "../lib/foundation/scss/foundation/components/labels", "../lib/foundation/scss/foundation/components/magellan", "../lib/foundation/scss/foundation/components/orbit", "../lib/foundation/scss/foundation/components/pagination", "../lib/foundation/scss/foundation/components/panels", "../lib/foundation/scss/foundation/components/pricing-tables", "../lib/foundation/scss/foundation/components/progress-bars", "../lib/foundation/scss/foundation/components/reveal", "../lib/foundation/scss/foundation/components/side-nav", "../lib/foundation/scss/foundation/components/split-buttons", "../lib/foundation/scss/foundation/components/sub-nav", "../lib/foundation/scss/foundation/components/switch", "../lib/foundation/scss/foundation/components/tables", "../lib/foundation/scss/foundation/components/tabs", "../lib/foundation/scss/foundation/components/thumbs", "../lib/foundation/scss/foundation/components/tooltips", "../lib/foundation/scss/foundation/components/top-bar", "../lib/foundation/scss/foundation/components/type", "../lib/foundation/scss/foundation/components/offcanvas", "../lib/foundation/scss/foundation/components/visibility"; // Behold... Foundation icon fonts!!! @import "../lib/foundation-icon-fonts/foundation-icons";
Set up Grunt for Compass Compilation
Add an appConfig attribute to Grunt's initConfig object. It'll look like this.
... grunt.initConfig({ appConfig: grunt.file.readJSON('./config/appConfig.json'), ...
Now make a file at /config/appConfig.json and fill it like so.
{ "app": { "ngModule" : "quiverInvoiceApp", "dist" : "dist", "src" : "app", "test" : "test", "assets": { "bower" : "lib", "fonts" : "fonts", "images" : "images", "partials" : "partials", "scripts" : "scripts", "styles" : "styles", "templates" : "views" }, "engines": { "compass" : true } } }
appConfig.json provides some handy configuration attributes that we can reference inside our Gruntfile. So let's open up Gruntfile.js and add another attribute to Grunt's initConfig object. I like to add things to the bottom of the config object like so...
grunt.initConfig({ // A whole bunch of configs... compass: { dev: { options: { cssDir : '<%= appConfig.app.src %>/<%= appConfig.app.assets.styles %>', fontsDir : '<%= appConfig.app.src %>/<%= appConfig.app.assets.fonts %>', httpFontsPath : '../<%= appConfig.app.assets.fonts %>', imagesDir : '<%= appConfig.app.src %>/<%= appConfig.app.assets.images %>', httpImagesPath : '../<%= appConfig.app.assets.images %>', importPath : '<%= appConfig.app.src %>/<%= appConfig.app.assets.lib %>', javascriptsDir : '<%= appConfig.app.dev %>/<%= appConfig.app.assets.scripts %>', sassDir : '<%= appConfig.app.src %>/<%= appConfig.app.assets.styles %>', httpGeneratedImagesPath : '../<%= appConfig.app.assets.images %>' } } } }
Now add your new compass job to the list of watches. You'll notice an attribute in Grunt's initConfig object named "watch". Add the compass job to the bottom like so...
grunt.initConfig({ watch: { coffee: { files: ['<%= yeoman.app %>/scripts/{,*/}*.coffee'], tasks: ['coffee:dist'] }, coffeeTest: { files: ['test/spec/{,*/}*.coffee'], tasks: ['coffee:test'] }, styles: { files: ['<%= yeoman.app %>/styles/{,*/}*.css'], tasks: ['copy:styles', 'autoprefixer'] }, livereload: { options: { livereload: '<%= connect.options.livereload %>' }, files: [ '<%= yeoman.app %>/{,*/}*.html', '.tmp/styles/{,*/}*.css', '{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js', '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' ] }, compass: { files: [ '<%= appConfig.app.src %>/<%= appConfig.app.assets.styles %>/{,*/}*.scss' ], tasks: ['compass'] } }, // A whole bunch of configs }
Now add your compass task to the build job. Place it right at the top.
grunt.registerTask('build', [ 'compass', 'clean:dist', 'useminPrepare', 'concurrent:dist', 'autoprefixer', 'concat', 'copy:dist', 'cdnify', 'ngmin', 'cssmin', 'uglify', 'rev', 'usemin' ]);
We're just about ready to launch our fancy new Angular application, but first we need to make some adjustments to app.js and index.html.
// /app/scripts/app.js 'use strict'; angular.module('quiverInvoiceApp', [ 'ngSanitize', 'restangular', 'ui.router', 'angular-google-analytics', 'jmdobry.angular-cache', 'firebase' ]) .config(function ($stateProvider, $urlRouterProvider) { $urlRouterProvider.otherwise('/'); $stateProvider.state('root', { url: '/', views: { body: { templateUrl: 'views/main.html', controller: 'MainCtrl' } } }); });
We just replaced Angular's native $routeProvider with a $stateProvider from angular-ui-router. We also imported Restangular, angular-google-analytics, angular-cache and angular-firebase. These libraries will come in handy later, but let's have some fun.
Jump to your terminal, make sure you're in your root folder, in my case, quiver-invoice, and type grunt server. You may need to troubleshoot a bit, and Grunt is great about descriptive error messages. You'll know you've succeeded once your browser automatically popups open a new tab, navigates to http://127.0.0.1:9000/#/ and you see a friendly Angular welcome message!
It's about time to deploy your dummy application. Check out the next article in this series: Single Page App Architecture: Angular Deploy
Agile... good for some things