Skip to content

Instantly share code, notes, and snippets.

@davisford
Created November 20, 2012 18:54
Show Gist options
  • Save davisford/4120142 to your computer and use it in GitHub Desktop.
Save davisford/4120142 to your computer and use it in GitHub Desktop.
Flexible Angular Routing + Twitter Bootstrap - composite Views
extends layout
block content
// navbar
div.navbar.navbar-fixed(ng-controller="NavCtrl")
div.navbar-inner
div.container
a.btn.btn-navbar(data-toggle="collapse", data-target=".nav-collapse")
span.icon-bar
a.brand(href="home")
img#logo(src="/images/logo.png")
div.nav-collapse
ul.nav.top-menu
li(ng-class="navClass('home')")
a(href="home", ng-class="{ on: isHome }") Home
li(ng-class="navClass('devices')")
a(href="devices", ng-class="{ on: isDevices }") Devices
li(ng-class="navClass('tracking')")
a(href="tracking", ng-class="{ on: isTracking }") Tracking
li(ng-class="navClass('alerts')")
a(href="alerts", ng-class="{ on: isAlerts }") Alerts
// fluid container
div.container-fluid#content(ng-switch="renderPath[0]")
div(ng-switch-when="home")
p home
div(ng-switch-when="split")
div.span3
div.well.sidebar-nav
ul.nav.nav-list
li.nav-header Select a device
div.span9(ng-switch="renderPath[1]")
div(ng-switch-when="devices")
p Devices
div(ng-switch-when="tracking")
p Tracking
div(ng-switch-when="alerts")
p alerts
angular.module('myApp', ['ssAngular'])
.config(function ($routeProvider, $locationProvider) {
$routeProvider
.when("/home", { action: "home.view" })
.when("/devices", { action: "devices.view" });
$locationProvider.html5Mode(true);
})
.controller('AppCtrl', function($scope, $route, $routeParams) {
render = function () {
var renderAction = $route.current.action,
renderPath = renderAction.split(".");
$scope.renderAction = renderAction;
$scope.renderPath = renderPath;
$scope.isHome = (renderPath[0] == "home");
$scope.isDevices = (renderPath[0] == "devices");
}
$scope.$on("$routeChangeSuccess",
function ($currentRoute, $previousRoute) {
render();
});
})
.controller('HomeCtrl', function ($scope) {
// etc
})
.controller('DeviceCtrl', function ($scope) {
// etc.
});
/*global angular:false */
angular.module('myApp', ['ssAngular']).config(function($routeProvider, $locationProvider) {
$routeProvider.when("/home", {
action: "home.view"
}).when("/devices", {
action: "split.devices"
}).when("/tracking", {
action: "split.tracking"
}).when("/alerts", {
action: "alerts.view"
});
$locationProvider.html5Mode(true);
}).controller('AppCtrl', function($scope, $route, $routeParams) {
// update the rendering of the page
render = function() {
// pull "action" value out of current route
var renderAction = $route.current.action;
// render path so we can start conditionally
// rendering parts of the page
var renderPath = renderAction.split(".");
var isHome = (renderPath[0] == "home");
var isDevices = (renderPath[0] == "devices");
var isTracking = (renderPath[0] == "tracking");
var isAlerts = (renderPath[0] == "alerts");
$scope.renderAction = renderAction;
$scope.renderPath = renderPath;
$scope.isHome = isHome;
$scope.isDevices = isDevices;
$scope.isTracking = isTracking;
$scope.isAlerts = isAlerts;
};
$scope.$on("$routeChangeSuccess", function($currentRoute, $previousRoute) {
render();
});
}).controller('HomeCtrl', function($scope) {
}).controller('DeviceCtrl', function($scope) {
}).controller('NavCtrl', ['$scope', '$location', function($scope, $location) {
// this toggles the 'active' class on/off in the navbar
$scope.navClass = function(page) {
var current = $location.path().substring(1);
return page === current ? 'active' : '';
};
}]);

Here I'm using SocketStream, AngularJS, Jade, and Twitter Bootstrap. I'm also using ss-angular here, but I'm not doing anything with it (yet) -- although I think it may help in the angular bootstrap process.

Technically, this example is really about composite views / routing with Angular - the other stuff is in there b/c that's what I'm using, but you could rip it out and use something completely different.

This example was based off this blog

SocketStream adheres to the single-page-app manifesto. There are ways to get around it, but it goes against the grain of what the framework wants to be. So, let's say you want to build in navigation, and you want to use Angular (why wouldn't you?). Navigation is a little tricky. You could use client-side partials/templates, and use ng-include directive to swap in a new ngView when your route changes (see $route).

AngularJS only allows one view per module, so the view is all or nothing. What if you want complex composite views. Let's say for example, you want a top level navbar, and then some views in the main pane are split - with the left side providing a submenu. Here's how that can be accomplished...

Here's what the navbar looks like with on the home link http://host/home

home

Here's the devices link http://host/devices, note that this is a split pane view. The left side is another submenu navigation list.

devices

Here's the tracking link http://host/tracking, not that this is also a split pane view. The left side shares the same submenu list as the devices link.

tracking

Finally, here's the alerts linke http://host/alerts, which is similar to the home link in that it takes up the whole content pane.

alerts

Note, that you can now add query params to the URL. For example, if you have a list of items in the sidebar, and you select one, that can be tacked onto the url as a query param, and you fire off a request to grab the data for that item. Exercise left for reader...

// This file automatically gets called first by SocketStream and must always exist
// Make 'ss' available to all modules and the browser console
window.ss = require('socketstream');
require('ssAngular');
require('/controllers');
ss.server.on('ready', function () {
// jQuery ready callback
$(function () {
console.log('jquery ready');
});
});
!!! 5
html(lang="en", ng-app="myApp", ng-controller="AppCtrl")
head
!= SocketStream
meta(charset="utf-8")
block title
title My Application Title
meta(name="viewport", content="width=device-width,initial-scale=1.0")
meta(name="description", content="todo")
meta(name="author", content="todo")
// if lt IE 9
script(src="http://html5shim.googlecode.com/svn/trunk/html5.js")
link(rel="shortcut icon", href="ico/favicon.ico")
link(rel="apple-touch-icon-precomposed", sizes="114x114", href="ico/apple-touch-icon-114-precomposed.png")
link(rel="apple-touch-icon-precomposed", sizes="72x72", href="ico/apple-touch-icon-72-precomposed.png")
link(rel="apple-touch-icon-precomposed", href="ico/apple-touch-icon-57-precomposed.png")
body
block content
block footer
footer
p © Me, Inc. 2012
@arxpoetica
Copy link

Ha. Just realized you posted the same link to the blog. :)

@arxpoetica
Copy link

Been doing some further digging into angular and it appears there may actually be another way of doing these changes, and it appears to be officially sanctioned (i.e., the "correct" methodology). Not 100% sure about this, need to do a little more testing, but It involves the "controller" and "resolve" parameters of $routerProvider. There's a video on it over here: http://www.youtube.com/watch?v=P6KITGRQujQ And here's the code: https://github.com/johnlindquist/angular-resolve In the example, obviously, there's only one view, so maybe I'm wrong on this answer, but I'm investigating.

@davisford
Copy link
Author

Hey, github didn't ping me at all about your comments, so I didn't see them until now. I saw John Lindquist's youtube vids. He split them into two videos, one for basic routing with templates, and another that uses resolve. His solution is similar to many others -- use partials to fill in an area with a template. You can do it without ngView, and you can do it with server-side templates, or client-side templates -- the latter being more appropriate for a single-page app.

The resolve stuff is just adding async behavior to it to avoid partial blank loads -- I have to say, I don't care for that at all. I'd prefer to load my whole page as fast as possible, and avoid writing code, just to insert a "loading..." progress bar. But either way -- with or without the resolve stuff, the solution is the same: using templates / partials.

I still think I prefer this method -- I'm going to spend more time on it today and build out more stuff in my real app. I think this method is a tad more flexible and cleaner from a code organization standpoint -- since I don't have to have these tiny files all over the place with pieces of the view -- I still have one main app.jade that shows me everything...and I'm just controlling the hide/show behavior. If you have any other questions -- or if you come across any epiphanies / good ideas, let me know.

@arxpoetica
Copy link

Cool.

@arxpoetica
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment