This documentation is for the current version (v1.2). If you are looking for old v1.1 documentation you can find it here.

Getting Started

Installation

Either install via bower or download it

bower install --save mobile-angular-ui

Using the boilerplate

If you want to have your Mobile Angular UI project bootstrapped in a minute you can use the generator-mobileangularui generator for Yeoman.

npm install -g bower yo gulp generator-mobileangularui

Using the generator is super-easy and fast

mkdir myApp
cd myApp
yo mobileangularui

You can find a complete tutorial to start a Phonegap project with mobile-angular-ui here.


Initial Setup

You can initialize your Angular application declaring mobile-angular-ui as a dependence.

angular.module('myApp', ["mobile-angular-ui"]);

Here is a more complete example including ngRoute:

angular.module('myApp', [
  "ngRoute",
  "mobile-angular-ui",
]).config(function($routeProvider) {
      $routeProvider.when('/', {
        // ...
      });
      // ...
  });

If you wish you can avoid to load whole mobile-angular-ui modules at all and pick a subset of the loaded modules to fit your needs. This is how the mobile-angular-ui module is defined with its dependencies.

angular.module('mobile-angular-ui', [
  'mobile-angular-ui.core.activeLinks',       /* adds .active class to current links */
  'mobile-angular-ui.core.fastclick',         /* polyfills overflow: auto */
  'mobile-angular-ui.core.sharedState',       /* SharedState service and directives */
  'mobile-angular-ui.core.outerClick',        /* outerClick directives */
  'mobile-angular-ui.components.modals',            /* modals and overlays */
  'mobile-angular-ui.components.switch',            /* switch form input */
  'mobile-angular-ui.components.sidebars',          /* sidebars */
  'mobile-angular-ui.components.scrollable',        /* uiScrollable directives */
  'mobile-angular-ui.components.capture',           /* uiYieldTo and uiContentFor directives */
  'mobile-angular-ui.components.navbars'            /* navbars */
]);

Html head section setup

Distributed package content
Regular usage files
Examples
Seting up a mobile only app
<link rel="stylesheet" href="mobile-angular-ui/dist/css/mobile-angular-ui-base.min.css" />
<script src="angular.min.js">
</script>
<script src="angular-route.min.js">
</script>
<script src="mobile-angular-ui/dist/js/mobile-angular-ui.min.js">
</script>
Seting up a responsive app
<link rel="stylesheet" href="mobile-angular-ui/dist/css/mobile-angular-ui-hover.min.css" />
<link rel="stylesheet" href="mobile-angular-ui/dist/css/mobile-angular-ui-base.min.css" />
<link rel="stylesheet" href="mobile-angular-ui/dist/css/mobile-angular-ui-desktop.min.css" />
<script src="angular.min.js">
</script>
<script src="angular-route.min.js">
</script>
<script src="mobile-angular-ui/dist/js/mobile-angular-ui.min.js">
</script>
Enabling built-in gestures modules (not stable yet)
<!-- Required to use $drag, $swipe and Translate services -->
<script src="/dist/js/mobile-angular-ui.gestures.min.js"></script>
Use the migration module to move from v1.1 to v1.2
<!-- Only required to import legacy syntax and directives. You won't need it 
unless you are attempting to move an app from Mobile Angular UI 1.1 to 1.2 -->
<script src="/dist/js/mobile-angular-ui.migrate.min.js"></script>
Use with other css frameworks (ie. Topcoat)
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/topcoat/0.8.0/css/topcoat-mobile-dark.css" />
<script src="angular.min.js">
</script>
<script src="angular-route.min.js">
</script>
<script src="mobile-angular-ui/dist/js/mobile-angular-ui.core.min.js">
</script>
<script src="/dist/js/mobile-angular-ui.gestures.min.js"></script>


Basic Layout

<body ng-app="myApp">

  <!-- Sidebars -->
  <div class="sidebar sidebar-left"><!-- ... --></div>
  <div class="sidebar sidebar-right"><!-- ... --></div>

  <div class="app">
    <div class="navbar navbar-app navbar-absolute-top"><!-- Top Navbar --></div>
    <div class="navbar navbar-app navbar-absolute-bottom"><!-- Bottom Navbar --></div>

    <!-- App body -->

    <div class='app-body'>
      <div class='app-content'>
        <ng-view></ng-view>
      </div>
    </div>
  </div><!-- ~ .app -->

  <!-- Modals and Overlays -->
  <div ui-yield-to="modals"></div>

</body>


Support and Compatibility

Mobile Angular UI 1.2 is compatible with:

  • AngularJs 1.2 and 1.3
  • Boostrap 3.x sources *
* = Mobile Angular UI is automatically built from Bootstrap .less sources. Bootstrap less are compiled with mobile-angular-ui stylesheets and then passed to a pipeline that transforms them to mobilified stylesheets.

Browser Support

Anything that can run Bootstrap 3 and Angular 1.3 and supports CSS3 transitions and transforms should fit:

  • Chrome
  • Firefox
  • Safari/IOS Safari
  • Android Browser 2.3+
  • Chrome for Android
  • Blackberry Browser
  • IE Mobile
  • Opera/Opera Mobile
  • IE10+
  • IEMobile 10+

Note n.1: Some visual effects may be only available in some of the browsers above (ie. blur effect in overlays). A decent fallback is provided in any case.

Note n.2: It may work even with IE9 (although not supported), but it may require a lot of effort:

  • Forget about transitions: sidebars use transitions events to hide show. Adding should fix this.
  • Be sure to enable Media Queries.
  • Replace/override any 3d transforms with equivalent margin or 2d transforms prefixed with -ms-
  • Polyfill any other missing features

Note n.3: Currently some experimental features like those from gestures module are not supported by all of the browsers above, hopefully support will be the broadest possibile as these features will stabilize.

Note n.4: If you notice frozen application on older devices be aware that this may be mainly due to device resources scarcity and not due to your application or framework you are using. If you have to forcefully support them try to lower your application footprint as much as you can.

See also:


Differences with Bootstrap 3

  1. Although Mobile Angular UI aims to retain most of Bootstrap CSS style, some minor changes are employed to achieve a more mobile-friendly look and feel for default components.
  2. It adds new components like sidebars, absolute-positioned navbars, switches, overlays, scrollables ..
  3. It does not rely on jQuery or bootstrap.js at all (all of the interactions is reproduced without coding through core module directives)
  4. It uses font-awesome in place of glyphicons
  5. Responsive css rules for components -sm, -md, *-lg are moved out of the default bundle
  6. Responsive grid is divided in two parts: .col-xs-* and .col-sm-* classes are included in base.css, .col-md-* and .col-lg-* classes are included in desktop.css.
  7. Breadcrumbs and pagination are just not the best way to navigate a mobile app so their are only included in desktop.css.
  8. .container is always fluid.

Overview


Core

Stand alone usage

.core module is required by mobile-angular-ui, anyway you can use it alone.

angular.module('myApp', ['mobile-angular-ui.core']);
Description

It has all the core functionalities of Mobile Angular UI. It aims to act as a common base for a UI framework providing services and directives to create components and implement UI interactions with angular.

NOTE
  • It has no dependency on Bootstrap.
  • It is not related to mobile apps only.
  • It is not requiring CSS support.
  • You can use it on any Angular Application and with any CSS framework.


Description

mobile-angular-ui.activeLinks module sets up .active class for a elements those href attribute matches the current angular $location url. It takes care of excluding both search part and hash part from comparison.

.active classes are added/removed each time one of $locationChangeSuccess or $includeContentLoaded is fired.

Usage

Just declare it as a dependency to your app unless you have already included one of its super-modules.

angular.module('myApp', ['mobile-angular-ui.core.activeLinks']);

NOTE: if you are using it without Bootstrap you may need to add some css to your stylesheets to reflect the activation state of links. I.e.

a.active {
  color: blue;
}


Capture

Description

The capture module exposes directives to let you extract markup which can be used in other parts of a template using uiContentFor and uiYieldTo directives.

It provides a way to move or clone a block of markup to other parts of the document.

This method is particularly useful to setup parts of the layout within an angular view. Since blocks of html are transplanted within their original $scope is easy to create layout interactions depending on the context. Some tipical task you can accomplish with these directives are: setup the navbar title depending on the view or place a submit button for a form inside a navbar.

Usage

Declare it as a dependency to your app unless you have already included some of its super-modules.

angular.module('myApp', ['mobile-angular-ui.core.capture']);

Use ui-yield-to as a placeholder.

<!-- index.html -->

<div class="navbar">
  <div ui-yield-to="title" class="navbar-brand">
    <span>Default Title</span>
  </div>
</div>

<div class="app-body">
  <ng-view class="app-content"></ng-view>
</div>

Use ui-content-for inside any view to populate the ui-yield-to content.

<!-- myView.html -->

<div ui-content-for="title">
  <span>My View Title</span>
</div>

Since the original scope is preserved you can use directives inside ui-content-for blocks to interact with the current scope. In the following example we will add a navbar button to submit a form inside a nested view.

<!-- index.html -->

<div class="navbar">
  <div ui-yield-to="navbarAction">
  </div>
</div>

<div class="app-body">
  <ng-view class="app-content"></ng-view>
</div>
<!-- newCustomer.html -->

<form ng-controller="newCustomerController">

  <div class="inputs">
    <input type="text" ng-model="customer.name" />  
  </div>

  <div ui-content-for="navbarAction">
    <button ng-click="createCustomer()">
      Save
    </button>
  </div>

</form>
app.controller('newCustomerController', function($scope, Store){
  $scope.customer = {};
  $scope.createCustomer = function(){
    Store.create($scope.customer);
    // ...
  }
});

If you wish you can also duplicate markup instead of move it. Just add duplicate parameter to uiContentFor directive to specify this behaviour.

<div ui-content-for="navbarAction" duplicate>
  <button ng-click="createCustomer()">
    Save
  </button>
</div>
uiYieldTo directive parameters
uiYieldTo An unique name to reference the placeholder.
[content] The default content. Default content is restored each time on $routeChangeStart.
uiContentFor directive parameters
uiContentFor An unique name to reference the placeholder.
duplicate Indicates whether the content should be duplicated instead of moved.
[content] The content to take place in the placeholder.


Fastclick

Description

mobile-angular-ui.core.fastclick module sets up fastclick.js to remove click delays on the whole document.

Usage

Just declare it as a dependency to your app unless you have already included one of its super-modules.

angular.module('myApp', ['mobile-angular-ui.core.fastclick']);


Outer Click

Description

Provides a directive to specifiy a behaviour when click/tap events happen outside an element. This can be easily used to implement eg. close on outer click feature for a dropdown.

Usage

Declare it as a dependency to your app unless you have already included some of its super-modules.

angular.module('myApp', ['mobile-angular-ui.core.outerClick']);

Use ui-outer-click to define an expression to evaluate when an Outer Click event happens. Use ui-outer-click-if parameter to define a condition to enable/disable the listener.

<div class="btn-group">
  <a ui-turn-on='myDropdown' class='btn'>
    <i class="fa fa-ellipsis-v"></i>
  </a>
  <ul 
    class="dropdown-menu"
    ui-outer-click="Ui.turnOff('myDropdown')"
    ui-outer-click-if="Ui.active('myDropdown')"
    role="menu"
    ui-show="myDropdown" 
    ui-state="myDropdown"
    ui-turn-off="myDropdown">

    <li><a>Action</a></li>
    <li><a>Another action</a></li>
    <li><a>Something else here</a></li>
    <li class="divider"></li>
    <li><a>Separated link</a></li>
  </ul>
</div>
uiOuterClick directive parameters
uiOuterClick Expression to evaluate when an Outer Click event happens.
uiOuterClickIf Condition to enable/disable the listener. Defaults to true.


Shared State

Description

SharedState aims to provide a proper way to create directives and components that previously used scope variables to communicate.

mobile-angular-ui.core.sharedState is composed by the homonymous service SharedState and a group of directives to access it.

It acts as a BUS between UI elements to share UI related state that is automatically disposed when all scopes requiring it are destroyed.

eg.

app.controller('controller1', function($scope, SharedState){
  SharedState.initialize($scope, 'myId');
});

app.controller('controller2', function(SharedState){
  SharedState.toggle('myId');
});

Data structures retaining statuses will stay outside angular scopes thus they are not evaluated against digest cycle until its necessary. Also although statuses are sort of global variables SharedState will take care of disposing them when no scopes are requiring them anymore.

A set of ui-* directives are available to interact with SharedState module and will hopefully let you spare your controllers and your time for something that is more meaningful than this:

$scope.activeTab = 1;

$scope.setActiveTab = function(n) {
  $scope.activeTab = n;
};

Usage

Declare it as a dependency to your app unless you have already included some of its super-modules.

angular.module('myApp', ['mobile-angular-ui.core.sharedState']);

Use ui-state directive to require/initialize a state from the target element scope

Example: Tabs


<div class="tabs" ui-state="activeTab">

  <ul class="nav nav-tabs">
    <li ui-class="{'active': activeTab == 1)}">
      <a ui-set="{'activeTab': 1}">Tab 1</a>
    </li>
    <li ui-class="{'active': activeTab == 2)}">
      <a ui-set="{'activeTab': 2}">Tab 2</a>
    </li>
    <li ui-class="{'active': activeTab == 3)}">
      <a ui-set="{'activeTab': 3}">Tab 3</a>
    </li>
  </ul>

  <div ui-if="activeTab == 1">
    Tab 1
  </div>

  <div ui-if="activeTab == 2">
    Tab 2
  </div>

  <div ui-if="activeTab == 3">
    Tab 3
  </div>

</div>

NOTE: ui-toggle/set/turnOn/turnOff responds to click/tap without stopping propagation so you can use them along with ng-click too. You can also change events to respond to with ui-triggers attribute.

Any SharedState method is exposed through Ui object in $rootScope. So you could always do ng-click="Ui.turnOn('myVar')".

Since SharedState is a service you can initialize/set statuses through controllers too:

app.controller('myController', function($scope, SharedState){
  SharedState.initialize($scope, "activeTab", 3);
});

As well as you can use ui-default for that:

<div class="tabs" ui-state="activeTab" ui-default="thisIsAnExpression(5 + 1 - 2)"></div>

SharedState Service Api Reference

SharedState exposes methods to interact with statuses to create, read and update statuses. A state is a global variable identified by an id.

initialize(scope, id, options)

Initialize, or require if already intialized, a state identified by id within the provided scope, making it available to the rest of application.

A SharedState is bound to one or more scopes. Each time initialize is called for an angular scope this will be bound to the SharedState and a reference count is incremented to allow garbage collection.

This reference count is decremented once the scope is destroyed. When the counter reach 0 the state will be disposed.

Valid options are:

  • defaultValue: the initialization value, it is taken into account only if the state id is not already initialized.

  • exclusionGroup: specify an exclusion group for the state. This means that for boolean operations (ie. toggle, turnOn, turnOf) when this state is set to true, any other state that is in the same exclusionGroup will be set to false.

setOne(id, value)

Set the value of the state identified by id to the value parameter.

setMany(object)

Set multiple statuses at once. ie.

SharedState.setMany({ activeTab: 'firstTab', sidebarIn: false });
set(idOrObject, [value])

Just a shorthand for both setOne and setMany. When called with only one parameter that is an object it is the same of setMany, otherwise is the same of setOne.

turnOn(id)

Set shared state identified by id to true. If the shared state has been initialized with exclusionGroup option it will also turn off (set to false) all other statuses from the same exclusion group.

turnOff(id)

Set shared state identified by id to false.

toggle(id)

If current value for shared state identified by id evaluates to true it calls turnOff on it otherwise calls turnOn. Be aware that it will take into account exclusionGroup option. See #turnOn and #initialize for more.

get(id) → object

Return the current value of the state identified by id.

isActive(id) → bool

Return true if the boolean conversion of #get(id) evaluates to true.

active(id) → bool

Alias for #isActive.

isUndefined(id) → bool

Return true if #get(id) evaluates to true.

equals(id, value) → bool

Return true if #get(id) is exactly equal (===) to value param.

eq(id, value) → bool

Alias for #equals.

values () → object

Returns an object with all the status values currently stored. It has the form of {statusId: statusValue}.

Bear in mind that in order to spare resources it currently returns just the internal object retaining statuses values. Thus it is not intended to be modified and direct changes to it will be not tracked or notified.

Just clone before apply any change to it.

Events

mobile-angular-ui.state.initialized.ID → (currentValue)

Broadcasted when #initialize is called for a new state not referenced by any scope currently.

mobile-angular-ui.state.destroyed.ID

Broadcasted when a state is destroyed.

mobile-angular-ui.state.changed.ID → (newValue, previousValue)

Broadcasted when the value of a state changes.

$scope.$on(`mobile-angular-ui.state.changed.uiSidebarLeft`, function(e, newVal, oldVal) {
  if (newVal === true) {
    console.log('sidebar opened');
  } else {
    console.log('sidebar closed');
  }
});

Directives

uiState

Calls SharedState#initialize on the scope relative to the element using it.

Params:

  • uiState: Required. (string) the shared state id
  • uiDefault: (expression) the default value
uiToggle

Calls SharedState#toggle when triggering events happens on the element using it.

Params:

  • uiToggle: Required. (string) the target shared state
  • uiTriggers: (string) the event triggering the call. Defaults to click tap
uiTurnOn

Calls SharedState#turnOn when triggering events happens on the element using it.

Params:

  • uiTurnOn: Required. (string) the target shared state
  • uiTriggers: (string) the event triggering the call. Defaults to click tap
uiTurnOff

Calls SharedState#turnOff when triggering events happens on the element using it.

Params:

  • uiTurnOff: Required. (string) the target shared state
  • uiTriggers: (string) the event triggering the call. Defaults to click tap
uiSet

Calls SharedState#set when triggering events happens on the element using it.

Params:

  • uiSet: Required. (expression) the object to pass as parameter to SharedState#set
  • uiTriggers: (string) the event triggering the call. Defaults to click tap
uiIf

Same as ngIf but evaluates condition against SharedState statuses too

Params

  • uiIf: Required. (expression) a condition to decide wether to attach the element to the dom
uiHide

Same as ngHide but evaluates condition against SharedState statuses too

Params

  • uiHide: Required. (expression) a condition to decide wether to hide the element
uiShow

Same as ngShow but evaluates condition against SharedState statuses too

Params

  • uiShow: Required. (expression) a condition to decide wether to show the element
uiClass

A simplified version of ngClass that evaluates in context of SharedState too, it only suppors the {'className': expr} syntax.

Params

  • uiClass: Required. (expression) an object of the form {'className': expr}, where expr decides wether the class should appear to element's class list

Components

Stand alone usage

.components module is required by mobile-angular-ui, and requires some .core features automatically.

angular.module('myApp', ['mobile-angular-ui.components']);
Description

It has directives and services providing mobile friendly components like navbars and sidebars. It requires mobile-angular-ui.base.css to work properly.


Modals and Overlays

Modals are basically the same of Bootstrap 3 but you have to use uiState with ngIf/uiIf or ngHide/uiHide to activate/dismiss it. By default both modals and overlay are made always showing up by css rule .modal {display:block}, so you can use it with ngAnimate and other angular directives in a simpler way.

Note:

For modals and overlays to cover the whole page you have to attach them as child of body element. To achieve this from a view is a common use for contentFor/yieldTo directives:

<body ng-app="myApp">

  <!-- ... -->

  <!-- Modals and Overlays -->
  <div ui-yield-to="modals"></div>

</body>

Then you can wrap your modals and overlays in contentFor:

<div ui-content-for="modals">
  <div class="modal"><!-- ... --></div>
</div>

Modals

<div ui-content-for="modals">
  <div class="modal" ui-if="modal1" ui-state='modal1'>
    <div class="modal-backdrop in"></div>
    <div class="modal-dialog">
      <div class="modal-content">
        <div class="modal-header">
          <button class="close" 
                  ui-turn-off="modal1">&times;</button>
          <h4 class="modal-title">Modal title</h4>
        </div>
        <div class="modal-body">
          <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio quo illum nihil voluptatem earum optio repellendus, molestias illo facere, ea non. Possimus assumenda illo accusamus voluptatibus, vel corporis maxime quam.</p>
        </div>
        <div class="modal-footer">
          <button ui-turn-off="modal1" class="btn btn-default">Close</button>
          <button ui-turn-off="modal1" class="btn btn-primary">Save changes</button>
        </div>
      </div>
    </div>
  </div>
</div>

Overlays

Overlay are just like to modals except they looks more native in mobile devices with a blurred overlay in background.

You can create an overlay adding .modal-overlay class to a modal:

<div ui-content-for="modals">
  <div class="modal modal-overlay" ui-if='modal2' ui-state='modal2'>
    <div class="modal-dialog">
      <div class="modal-content">
        <div class="modal-header">
          <button class="close"
                  ui-turn-off="modal2">&times;</button>
          <h4 class="modal-title">Modal title</h4>
        </div>
        <div class="modal-body">
          <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias, amet harum reiciendis enim adipisci magni fugit suscipit eaque corporis? Saepe eius ipsum optio dolorum a qui adipisci, reprehenderit totam temporibus!</p>
        </div>
        <div class="modal-footer">
          <button ui-turn-off="modal2" class="btn btn-default">Close</button>
          <button ui-turn-off="modal2" class="btn btn-primary">Save changes</button>
        </div>
      </div>
    </div>
  </div>
</div>


Bootstrap default navbars are awesome for responsive websites, but are not the best with a small screen. Also fixed positioning is yet not an option to create navbars standing in top or bottom of the screen.

Mobile Angular Ui offers an alternative to bootstrap navbars that is more suitable for mobile.

It uses scrollable areas to avoid scroll issues. In the following figure you can see the difference between fixed navbars and navbars with absolute positioning.

Here is the basic markup to achieve this.

<div class="app">
  <div class="navbar navbar-app navbar-absolute-top">
    <!-- ... -->
  </div>

  <div class="navbar navbar-app navbar-absolute-bottom">
    <!-- ... -->
  </div>

  <div class="app-body">
    <ng-view></ng-view>
  </div>
</div>

As you can notice the base class is .navbar-app while the positioning is obtained adding either .navbar-absolute-top or .navbar-absolute-bottom class.

Mobile Navbar Layout

Top navbar in mobile design most of the times follows a clear pattern: a centered title surrounded by one or two action buttons, the back or the menu buttons are two common examples.

Twitter Bootstrap ships with a different arrangement of components for navbars since they are supposed to host an horizontal navigation menu.

.navbar-app is specifically designed to support this different type of interaction and arrangement.

Consider the following example:

<div class="navbar navbar-app navbar-absolute-top">

  <div class="navbar-brand navbar-brand-center">
    Navbar Brand
  </div>

  <div class="btn-group pull-left">
    <div class="btn btn-navbar">
      Left Action
    </div>
  </div>

  <div class="btn-group pull-right">
    <div class="btn btn-navbar">
      Right Action
    </div>
  </div>
</div>

.navbar-brand-center is a specialization of BS3's .navbar-brand. It will render the title centered and below the two button groups. Note that .navbar-brand-center will position the title with absolute positioning ensuring that it will never cover the buttons, which would cause interaction problems.


Scrollable

One thing you'll always have to deal with approaching mobile web app development is scroll and position:fixed bugs.

Due to the lack of support in some devices fixed positioned elements may bounce or disappear during scroll. Also mobile interaction often leverages horizontal scroll eg. in carousels or sliders.

We use overflow:auto to create scrollable areas and solve any problems related to scroll.

Since overflow:auto is not always available in touch devices we use Overthrow to polyfill that.

Markup for any scrollable areas is as simple as:

<div class="scrollable">
  <div class="scrollable-content">...</div>
</div>

This piece of code will trigger a directive that properly setup a new Overthrow instance for the .scrollable node.

Headers and footers

.scrollable-header/.scrollable-footer can be used to add fixed header/footer to a scrollable area without having to deal with css height and positioning to avoid breaking scroll.

<div class="scrollable">
  <div class="scrollable-header"><!-- ... --></div>
  <div class="scrollable-content"><!-- ... --></div>
  <div class="scrollable-footer"><!-- ... --></div>
</div>

scrollTo

.scrollable-content controller exposes a scrollTo function: scrollTo(offsetOrElement, margin)

You have to require it in your directives to use it or obtain through element().controller:

var elem = element(document.getElementById('myScrollableContent'));
var scrollableContentController = elem.controller('scrollableContent');

// - Scroll to top of containedElement
scrollableContentController.scrollTo(containedElement);

// - Scroll to top of containedElement with a margin of 10px;
scrollableContentController.scrollTo(containedElement, 10);

// - Scroll top by 200px;
scrollableContentController.scrollTo(200);

ui-scroll-bottom/ui-scroll-top

You can use ui-scroll-bottom/ui-scroll-top directives handle that events and implement features like infinite scroll.

<div class="scrollable">
  <div class="scrollable-content section" ui-scroll-bottom="loadMore()">
    <ul>
      <li ng-repeat="item in items">
        {{item.name}}
      </li>
    </ul>
  </div>
</div>


Creating sidebars

Sidebars can be placed either in left side or right side adding respectively .sidebar-left and .sidebar-right classes.

<div class="sidebar sidebar-left">
  <div class="scrollable">
    <h1 class="scrollable-header app-name">My App</h1>  
    <div class="scrollable-content">
      <div class="list-group" ui-turn-off='uiSidebarLeft'>
        <a class="list-group-item" href="#/link1">Link 1 
          <i class="fa fa-chevron-right pull-right"></i></a>
        <a class="list-group-item" href="#/link2">Link 2
          <i class="fa fa-chevron-right pull-right"></i></a>
      </div>
    </div>
  </div>
</div>

<div class="sidebar sidebar-rigth">
  <!-- -->
</div>

Interacting with sidebars

Under the hood sidebar uses SharedState exposing respective statuses: uiSidebarLeft and uiSidebarRight

<a href ui-toggle='uiSidebarLeft'>Toggle sidebar left</a>

<a href ui-toggle='uiSidebarRight'>Toggle sidebar right</a>

You can put ui-turn-off='uiSidebarLeft' or ui-turn-off='uiSidebarLeft' inside the sidebar to make it close after clicking links inside them.

By default sidebar are closed by clicking/tapping outside them.


Toggle Switch

The ui-switch directive (not to be confused with ng-switch) lets you create a toggle switch control bound to a boolean ngModel value.

It requires ngModel. You can also use ngChange in conjunction with [switch] to trigger functions in response to model changes.

<ui-switch  ng-model="invoice.paid"></ui-switch>

Gestures

This module will not work with ngTouch cause it is intended, among offering more features, to be a drop-in replacement for it.

Be aware that ngTouch is still not playing well with fastclick.js and its usage with mobile-angular-ui is currently discouraged anyway.

mobile-angular-ui.gestures is still in a early stage of development and is not required by the framework.

If you are looking for something more complete and production-ready I suggest you to look for other projects like angular-hammer.

Usage

.gestures module is not required by mobile-angular-ui module. It has no dependency on other modules and is intended to be used alone with any other angular framework.

You have to include mobile-angular-ui.gestures.min.js to your project in order to use it. Ie.

<script src="/dist/js/mobile-angular-ui.gestures.min.js"></script>
angular.module('myApp', ['mobile-angular-ui.gestures']);
Description

It has directives and services to support swipe and drag gestures.

It does not need any .css to work.

Swipe

Swipe module is stable and ready to use. It is intended to be a drop-in replacement of ngTouch $swipe service and ng-swipe-* directives, that offers better interface and improvements.

It is recomended to use this in place of ngTouch in order to avoid conflicts with fastclick.

Description

An adaptation of ngTouch.$swipe. It is basically the same despite of:

  1. It does not require ngTouch thus is better compatible with fastclick.js
  2. It allows to unbind

It also provides ng-swipe-left and ng-swipe-right. The only difference with ngTouch ones is that they allow for nesting.

Usage

$swipe service:

var unbind = $swipe.bind(elem, options);

// Call this only if you need to detatch `$swipe`
// before the element is destroyed:
unbind();

ng-swipe-* directives:

Swiping the inner element wont call the outer handler (just f2 is executed and not f1).

<div ng-swipe-left='fn1()'>
  <!-- ... -->
  <div ng-swipe-left='fn2()'>
    <!-- ... -->
  </div>
  <!-- ... -->
</div>

Example: Opening left sidebar on swipe-right.

<div class="app" ng-swipe-right='Ui.turnOn("uiSidebarLeft")'>
  <!-- ... -->
</div>


Drag

This service is incomplete yet and may be not supported properly on any devices. A better implementation is in progress. Any help regarding this concern will be extremely appreciated! Source code here: https://github.com/mcasimir/mobile-angular-ui/blob/master/src/js/gestures/drag.js

Description

$drag Service wraps $swipe to extend its behavior moving target element through css transform according to the $swipe coords thus creating a drag effect.

$drag interface is very close to $swipe:

app.controller('MyController', function($drag, $element){
  var unbindDrag = $drag.bind($element, {
   // drag callbacks
   // - rect is the current result of getBoundingClientRect() for bound element
   // - cancelFn issue a "touchcancel" on element
   // - resetFn restore the initial transform
   // - undoFn undoes the current movement
   // - swipeCoords are the coordinates exposed by the underlying $swipe service
   start: function(rect, cancelFn, resetFn, swipeCoords){},
   move: function(rect, cancelFn, resetFn, swipeCoords){},
   end: function(rect, undoFn, resetFn, swipeCoords) {};
   cancel: function(rect, resetFn){},

   // constraints for the movement
   // you can use a "static" object of the form:
   // {top: .., lelf: .., bottom: .., rigth: ..}
   // or pass a function that is called on each movement 
   // and return it in a dynamic way.
   // This is useful if you have to constraint drag movement while bounduaries are
   // changing over time.

   constraint: function(){ return {top: y1, left: x1, bottom: y2, right: x2}; }, // or just {top: y1, left: x1, bottom: y2, right: x2}

   // instantiates the Trasform according to touch movement (defaults to `t.translate(dx, dy);`)
   // dx, dy are the distances of movement for x and y axis after constraints are applyied
   transform: function(transform, dx, dy, currSwipeX, currSwipeY, startSwipeX, startSwipeY) {},

   // changes the Transform before is applied to element (useful to add something like easing or accelleration)
   adaptTransform: function(transform, dx, dy, currSwipeX, currSwipeY, startSwipeX, startSwipeY) {}

  });

  // This is automatically called when element is disposed so it is not necessary
  // that you call this manually but if you have to detatch $drag service before
  // this you could just call:
  unbindDrag();
});

Main differences with $swipe are:

  • bound elements will move following swipe direction automatically
  • coords param take into account css transform so you can easily detect collision with other elements.
  • start, move, end callback receive a cancel funcion that can be used to cancel the motion and reset the transform.
  • you can configure the transform behavior passing a transform function to options.
  • you can constraint the motion through the constraint option (setting relative movement limits)

Usage

Example 1. Drag to dismiss

app.directive('dragToDismiss', function($drag, $parse, $timeout){
  return {
    restrict: 'A',
    compile: function(elem, attrs) {
      var dismissFn = $parse(attrs.dragToDismiss);
      return function(scope, elem, attrs){
        var dismiss = false;

        $drag.bind(elem, {
          constraint: {
            minX: 0, 
            minY: 0, 
            maxY: 0 
          },
          move: function(c) {
            if( c.left >= c.width / 4) {
              dismiss = true;
              elem.addClass('dismiss');
            } else {
              dismiss = false;
              elem.removeClass('dismiss');
            }
          },
          cancel: function(){
            elem.removeClass('dismiss');
          },
          end: function(c, undo, reset) {
            if (dismiss) {
              elem.addClass('dismitted');
              $timeout(function() { 
                scope.$apply(function() {
                  dismissFn(scope);  
                });
              }, 400);
            } else {
              reset();
            }
          }
        });
      };
    }
  };
});

Example 2. Touch enabled "deck of cards" carousel directive

app.directive('carousel', function(){
  return {
    restrict: 'C',
    scope: {},
    controller: function($scope) {
      this.itemCount = 0;
      this.activeItem = null;

      this.addItem = function(){
        var newId = this.itemCount++;
        this.activeItem = this.itemCount == 1 ? newId : this.activeItem;
        return newId;
      };

      this.next = function(){
        this.activeItem = this.activeItem || 0;
        this.activeItem = this.activeItem == this.itemCount - 1 ? 0 : this.activeItem + 1;
      };

      this.prev = function(){
        this.activeItem = this.activeItem || 0;
        this.activeItem = this.activeItem === 0 ? this.itemCount - 1 : this.activeItem - 1;
      };
    }
  };
});

app.directive('carouselItem', function($drag) {
  return {
    restrict: 'C',
    require: '^carousel',
    scope: {},
    transclude: true,
    template: '<div class="item"><div ng-transclude></div></div>',
    link: function(scope, elem, attrs, carousel) {
      scope.carousel = carousel;
      var id = carousel.addItem();

      var zIndex = function(){
        var res = 0;
        if (id == carousel.activeItem){
          res = 2000;
        } else if (carousel.activeItem < id) {
          res = 2000 - (id - carousel.activeItem);
        } else {
          res = 2000 - (carousel.itemCount - 1 - carousel.activeItem + id);
        }
        return res;
      };

      scope.$watch(function(){
        return carousel.activeItem;
      }, function(n, o){
        elem[0].style['z-index']=zIndex();
      });


      $drag.bind(elem, {
        constraint: { minY: 0, maxY: 0 },
        adaptTransform: function(t, dx, dy, x, y, x0, y0) {
          var maxAngle = 15;
          var velocity = 0.02;
          var r = t.getRotation();
          var newRot = r + Math.round(dx * velocity);
          newRot = Math.min(newRot, maxAngle);
          newRot = Math.max(newRot, -maxAngle);
          t.rotate(-r);
          t.rotate(newRot);
        },
        move: function(c){
          if(c.left >= c.width / 4 || c.left <= -(c.width / 4)) {
            elem.addClass('dismiss');  
          } else {
            elem.removeClass('dismiss');  
          }          
        },
        cancel: function(){
          elem.removeClass('dismiss');
        },
        end: function(c, undo, reset) {
          elem.removeClass('dismiss');
          if(c.left >= c.width / 4) {
            scope.$apply(function() {
              carousel.next();
            });
          } else if (c.left <= -(c.width / 4)) {
            scope.$apply(function() {
              carousel.next();
            });
          }
          reset();
        }
      });
    }
  };
});


Transform

This service is incomplete yet and works only with 2d transforms. Although this can be enough for many use cases a better implementation is in progress. Any help regarding this concern will be extremely appreciated! Source code here: https://github.com/mcasimir/mobile-angular-ui/blob/master/src/js/gestures/transform.js

Description

Transform is the underlying service used by $drag to deal with css trasform matrix in a simpler and vendor-angnostic way.

Usage

Consider the following html:

<div id="myElem" style="transform: translateX: 20px;"></div>
var e = document.getElementById('myElem');
var t0 = Transform.fromElement(e);

// It takes into account current transform applyed to elems
console.log(t0.getTranslation().x);
// -> 20;

t0.rotate(90);

// Set t0 to element ignoring previous transform.
t0.set(e);

var t1 = new Transform();

t1.translate(12, 40);

// merges t1 with current trasformation matrix applied to element.
t1.apply(e);

Utils

Utils contains just css shorthands to achieve mobile-friendly layouts.

Sections

Sections are containers for application body. By default .app container will have no padding and no background. To add them you should use sections.

You can make any element a section adding .section class on it.

<div class="section">
  <!-- contents -->  
</div>

Section acts as .container-fluid and has padding and background.

Layout Variations

  • .section-wide removes horizontal padding
  • .section-condensed removes vertical padding
  • .section-break adds a margin-bottom and shadow (useful if you have more than a section on the same view)
  • .section-default uses

Theming

You can use any of .section-default, .section-primary, .section-success, .section-info, .section-warning or .section-danger to change the appearance of sections according to bootstrap style variations.

Example:

<div class="section section-wide section-info">
  <!-- contents -->  
</div>

Justified Blocks

Mobile screens are small and it's common to have 2 or three buttons justified. To achieve this behaviour you can use the .justified class in any parent block of a group of items. This will apply a table layout to parent and a table-cell layout to its children.

Example:

<div class="btn-group justified nav-tabs">
  <a class="btn">Tab 1</a>
  <a class="btn">Tab 2</a>
  <a class="btn">Tab 3</a>
</div>

Recipes

Since mobile-angular-ui exposes low level primitives to create components the following is a list of snippets to create most common components for an UI.

You can easily implement your own or invent different ones. As you will notice they will be just html snippets, similar to what you could achieve with an higher level css+js framework like Bootstrap, jquery Mobile etc.

Tabs

<ul class="nav nav-tabs" ui-state='activeTab' ui-default='1'>
  <li ui-class="{'active': activeTab == 1}">
    <a ui-set="{'activeTab': 1}">Tab 1</a>
  </li>
  <li ui-class="{'active': activeTab == 2}">
    <a ui-set="{'activeTab': 2}">Tab 2</a>
  </li>
  <li ui-class="{'active': activeTab == 3}">
    <a ui-set="{'activeTab': 3}">Tab 3</a>
  </li>
</ul>

<div ui-if="activeTab == 1">
  <h3 class="page-header">Tab 1</h3>
  <!-- ... -->
</div>

<div ui-if="activeTab == 2">
  <h3 class="page-header">Tab 2</h3>
  <!-- ... -->
</div>

<div ui-if="activeTab == 3">
  <h3 class="page-header">Tab 3</h3>
  <!-- ... -->
</div>

Accordion

<div class="panel-group" ui-state='myAccordion' ui-default='2'>
  <div class="panel panel-default" ng-repeat="i in [1,2,3]">
    <div class="panel-heading" ui-set="{'myAccordion': i}">
      <h4 class="panel-title">
          Collapsible Group Item #{{i}}
      </h4>
    </div>
    <div ui-if="myAccordion == i">
      <div class="panel-body">
        <!-- -->
      </div>
    </div>
  </div>
</div>

Note .dropdown-menu is always visible with mobile-angular-ui css. Thus you should use ui-show/ui-hide/ui-if or ng-* relatives to display or hide it, and ngAnimate or similar for animations.

<div class="btn-group">
  <a ui-turn-on='myDropdown' class='btn'>
    <i class="fa fa-ellipsis-v"></i>
  </a>
  <ul 
    class="dropdown-menu"
    ui-outer-click="Ui.turnOff('myDropdown')"
    ui-outer-click-if="Ui.active('myDropdown')"
    role="menu"
    ui-show="myDropdown" 
    ui-state="myDropdown"
    ui-turn-off="myDropdown">

    <li><a>Action</a></li>
    <li><a>Another action</a></li>
    <li><a>Something else here</a></li>
    <li class="divider"></li>
    <li><a>Separated link</a></li>
  </ul>
</div>

Segmented Navigation

<div class="btn-group justified">
  <a href="#/page1">Page 1</a>
  <a href="#/page2">Page 2</a>
  <a href="#/page3">Page 3</a>
</div>