The idea is to dynamically generate a tabbed navigation using Angular and UI Bootstrap.
I’ve done this before, but this time around I needed the ability to deep link into a tab. That is, if a user bookmarks /someapp/tab2, then the 2nd tab should be active with its content showing.
Instead of using ngRouter, which is a bit simplistic, I decided to use UI Router. UI Router is not without quirks and bugs, but it does give the opportunity to setup multiple, named “states” for an application, and can manage nested states and routes through associated URLs. One of the first steps in working with UI Router is configuring the known states
var app = angular.module("routedTabs", ["ui.router", "ui.bootstrap"]); app.config(function($stateProvider, $urlRouterProvider){ $urlRouterProvider.otherwise("/main/tab1"); $stateProvider .state("main", { abstract: true, url:"/main", templateUrl:"main.html" }) .state("main.tab1", { url: "/tab1", templateUrl: "tab1.html" }) .state("main.tab2", { url: "/tab2", templateUrl: "tab2.html" }) .state("main.tab3", { url: "/tab3", templateUrl: "tab3.html" }); });
In the above code, “main” is a parent state with three children (tab1, tab2, and tab3). Each child has an associated URL (which will be appended to the parent URL) and a template. Each child template will plug into the parent template of main.html, which itself has to plug into the application shell.
In other words, the shell of the application uses the ui-view directive to position the parent template (main.html).
<body ng-app="routedTabs" class="container"> <div ui-view></div> </body>
This is not much different than using ngRouter and its ngView directive, but UI router also allows for main.html to use another ui-view directive where one of the child templates will appear.
<div ng-controller="mainController"> <tabset> <tab ng-repeat="t in tabs" heading="{{t.heading}}" select="go(t.route)" active="t.active"> </tab> </tabset> <h2>View:</h2> <div ui-view></div> </div>
This view requires a controller to provide the tab data.
app.controller("mainController", function($rootScope, $scope, $state) { $scope.tabs = [ { heading: "Tab 1", route:"main.tab1", active:false }, { heading: "Tab 2", route:"main.tab2", active:false }, { heading: "Tab 3", route:"main.tab3", active:false }, ]; $scope.go = function(route){ $state.go(route); }; $scope.active = function(route){ return $state.is(route); }; $scope.$on("$stateChangeSuccess", function() { $scope.tabs.forEach(function(tab) { tab.active = $scope.active(tab.route); }); }); });
The only reason to listen for UI router’s $stateChangeSuccess is to keep the right tab highlighted if the URL changes. It’s a bit of a hack and actually makes me wonder if using tabs from UI Bootstrap is worth the extra code, or if it would be easier to write something custom and integrate directly with UI router.
If you want to try the code for yourself, here it is on Plunkr.