OdeToCode IC Logo

Dynamic Tabs with AngularJS and UI Bootstrap

Wednesday, August 14, 2013

I’ve been working on a  data management tool where I want to give users “workspace” areas. Each workspace is encapsulated inside of a tab, and a user can add a new workspace by clicking an icon in the tab bar, as in the following picture.

image

This feature isn’t too difficult to put together using AngularJS plus UI Bootstrap. UI Bootstrap provides directives and templates to work with Bootstrap components like tabs, accordions, alerts, and dialogs.

The first bit of code here is the TabsParentController, which is responsible for managing multiple workspaces.

module.controller("TabsParentController", function ($scope) {

    var setAllInactive = function() {
        angular.forEach($scope.workspaces, function(workspace) {
            workspace.active = false;
        });
    };

    var addNewWorkspace = function() {
        var id = $scope.workspaces.length + 1;
        $scope.workspaces.push({
            id: id,
            name: "Workspace " + id,
            active: true
        });
    };

    $scope.workspaces =
    [
        { id: 1, name: "Workspace 1", active:true  },
        { id: 2, name: "Workspace 2", active:false }
    ];

    $scope.addWorkspace = function () {
        setAllInactive();
        addNewWorkspace();
    };       

});

Most of the tricky parts come in the HTML markup, which uses UI Bootstrap directives to create the tabs. You can see one tab created for each workspace, plus a static tab with the “+” sign icon.

<div ng-controller="TabsParentController">
    <tabset>
        <tab ng-repeat="workspace in workspaces"
             heading="{{workspace.name}}"
             active=workspace.active>
            <div ng-controller="TabsChildController"  
                 ng-init="workspace=workspace">
                <div>
                    {{workspace.id}} : {{ workspace.name}}
                </div>
                <input type="text" ng-model="workspace.name"/>
            </div>     
        </tab>
        <tab select="addWorkspace()">
            <tab-heading>
                <i class="icon-plus-sign"></i>
            </tab-heading>
        </tab>
    </tabset>
</div>

The hardest part was figuring out how to tell the TabsChildController which workspace object to use. Although prototypal inheritance is a nice way to share information between a parent and child controller, in this case the child controller inherits all the workspaces from its parent, and doesn’t know which specific workspace to use.

To get around this problem I used an ngInit directive to create a workspace attribute in the child controller’s scope. The value of the workspace is the workspace used from the outer repeater scope. This is confusing if you haven’t worked with Angular for awhile, I think, so if you think of a better solution I’m all ears!