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!


Comments
gravatar Chowdhury Wednesday, August 14, 2013
Hi Scott, Don't know if any better or not, but {{$parent.workspace.id}} : {{ $parent.workspace.name}} achieves the same without the need for ng-Init in the markup. Here is a plunk with your sample code. http://plnkr.co/edit/CAlrAzHO2THuglQrsTIi?p=preview Thanks.
gravatar Scott Wednesday, August 14, 2013
@Chowdhury: Perfect, I like much better.
gravatar john Friday, August 23, 2013
just curious -- why would you want this instead of having user use his/her browser to manage between the different workspaces (i.e. open each workspace in a new tab). Thanks for sharing
gravatar scott Monday, August 26, 2013
@john: Good question, perhaps I'm overdoing this...
Comments are closed.

My Pluralsight Courses

K.Scott Allen OdeToCode by K. Scott Allen
What JavaScript Developers Should Know About ECMAScript 2015
The Podcast!