Directives and Services of Ionic

In this article by Arvind Ravulavaru, author of Learning Ionic, we are going to take a look at Ionic directives and services, which provides reusable components and functionality that can help us in developing applications even faster.

(For more resources related to this topic, see here.)

Ionic Platform service

The first service we are going to deal with is the Ionic Platform service ($ionicPlatform). This service provides device-level hooks that you can tap into to better control your application behavior.

We will start off with the very basic ready method. This method is fired once the device is ready or immediately, if the device is already ready.

All the Cordova-related code needs to be written inside the $ionicPlatform.ready method, as this is the point in the app life cycle where all the plugins are initialized and ready to be used.

To try out Ionic Platform services, we will be scaffolding a blank app and then working with the services. Before we scaffold the blank app, we will create a folder named chapter5. Inside that folder, we will run the following command:

ionic start -a "Example 16" -i app.example.sixteen example16 blank

Once the app is scaffolded, if you open www/js/app.js, you should find a section such as:

.run(function($ionicPlatform) {
  $ionicPlatform.ready(function() {
    // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
    // for form inputs)
    if(window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
    }
    if(window.StatusBar) {
      StatusBar.styleDefault();
    }
  });
})

You can see that the $ionicPlatform service is injected as a dependency to the run method. It is highly recommended to use $ionicPlatform.ready method inside other AngularJS components such as controllers and directives, where you are planning to interact with Cordova plugins.

In the preceding run method, note that we are hiding the keyboard accessory bar by setting:

cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);

You can override this by setting the value to false. Also, do notice the if condition before the statement. It is always better to check for variables related to Cordova before using them.

The $ionicPlatform service comes with a handy method to detect the hardware back button event. A few (Android) devices have a hardware back button and, if you want to listen to the back button pressed event, you will need to hook into the onHardwareBackButton method on the $ionicPlatform service:

var hardwareBackButtonHandler = function() {
  console.log('Hardware back button pressed');
  // do more interesting things here
}
        $ionicPlatform.onHardwareBackButton(hardwareBackButtonHandler);

This event needs to be registered inside the $ionicPlatform.ready method preferably inside AngularJS's run method. The hardwareBackButtonHandler callback will be called whenever the user presses the device back button.

A simple functionally that you can do with this handler is to ask the user if they want to really quit your app, making sure that they have not accidently hit the back button.

Sometimes this may be annoying. Thus, you can provide a setting in your app whereby the user selects if he/she wants to be alerted when they try to quit. Based on that, you can either defer registering the event or you can unsubscribe to it.

The code for the preceding logic will look something like this:

.run(function($ionicPlatform) {
    $ionicPlatform.ready(function() {
        var alertOnBackPress = 
localStorage.getItem('alertOnBackPress');           var hardwareBackButtonHandler = function() {             console.log('Hardware back button pressed');             // do more interesting things here         }           function manageBackPressEvent(alertOnBackPress) {             if (alertOnBackPress) {                 $ionicPlatform.onHardwareBackButton(hardwareBackButtonHandler);             } else {                 $ionicPlatform.offHardwareBackButton(hardwareBackButtonHandler);             }         }           // when the app boots up         manageBackPressEvent(alertOnBackPress);           // later in the code/controller when you let         // the user update the setting         function updateSettings(alertOnBackPressModified) {             localStorage.setItem('alertOnBackPress',
alertOnBackPressModified);             manageBackPressEvent(alertOnBackPressModified)         }       }); })

In the preceding code snippet, we are looking in localStorage for the value of alertOnBackPress. Next, we create a handler named hardwareBackButtonHandler, which will be triggered when the back button is pressed. Finally, a utility method named manageBackPressEvent() takes in a Boolean value that decides whether to register or de-register the callback for HardwareBackButton.

With this set up, when the app starts we call the manageBackPressEvent method with the value from localStorage. If the value is present and is equal to true, we register the event; otherwise, we do not. Later on, we can have a settings controller that lets users change this setting. When the user changes the state of alertOnBackPress, we call the updateSettings method passing in if the user wants to be alerted or not. The updateSettings method updates localStorage with this setting and calls the manageBackPressEvent method, which takes care of registering or de-registering the callback for the hardware back pressed event.

This is one powerful example that showcases the power of AngularJS when combined with Cordova to provide APIs to manage your application easily.

This example may seem a bit complex at first, but most of the services that you are going to consume will be quite similar. There will be events that you need to register and de-register conditionally, based on preferences. So, I thought this would be a good place to share an example such as this, assuming that this concept will grow on you.

registerBackButtonAction

The $ionicPlatform also provides a method named registerBackButtonAction. This is another API that lets you control the way your application behaves when the back button is pressed.

By default, pressing the back button executes one task. For example, if you have a multi-page application and you are navigating from page one to page two and then you press the back button, you will be taken back to page one. In another scenario, when a user navigates from page one to page two and page two displays a pop-up dialog when it loads, pressing the back button here will only hide the pop-up dialog but will not navigate to page one.

The registerBackButtonAction method provides a hook to override this behavior. The registerBackButtonAction method takes the following three arguments:

  • callback: This is the method to be called when the event is fired
  • priority: This is the number that indicates the priority of the listener
  • actionId (optional): This is the ID assigned to the action

By default the priority is as follows:

  • Previous view = 100
  • Close side menu = 150
  • Dismiss modal = 200
  • Close action sheet = 300
  • Dismiss popup = 400
  • Dismiss loading overlay = 500

So, if you want a certain functionality/custom code to override the default behavior of the back button, you will be writing something like this:

var cancelRegisterBackButtonAction = 
$ionicPlatform.registerBackButtonAction(backButtonCustomHandler,
201);

This listener will override (take precedence over) all the default listeners below the priority value of 201—that is dismiss modal, close side menu, and previous view but not above the priority value.

When the $ionicPlatform.registerBackButtonAction method executes, it returns a function. We have assigned that function to the cancelRegisterBackButtonAction variable. Executing cancelRegisterBackButtonAction de-registers the registerBackButtonAction listener.

The on method

Apart from the preceding handy methods, $ionicPlatform has a generic on method that can be used to listen to all of Cordova's events (https://cordova.apache.org/docs/en/edge/cordova_events_events.md.html).

You can set up hooks for application pause, application resume, volumedownbutton, volumeupbutton, and so on, and execute a custom functionality accordingly.

You can set up these listeners inside the $ionicPlatform.ready method as follows:

var cancelPause = $ionicPlatform.on('pause', function() {
            console.log('App is sent to background');
            // do stuff to save power
        });
 
var cancelResume = $ionicPlatform.on('resume', function() {
            console.log('App is retrieved from background');
            // re-init the app
        });
 
        // Supported only in BlackBerry 10 & Android
var cancelVolumeUpButton = $ionicPlatform.on('volumeupbutton', 
function() {             console.log('Volume up button pressed');             // moving a slider up         });   var cancelVolumeDownButton = $ionicPlatform.on('volumedownbutton',
function() {             console.log('Volume down button pressed');             // moving a slider down         });

The on method returns a function that, when executed, de-registers the event.

Now you know how to control your app better when dealing with mobile OS events and hardware keys.

Content

Next, we will take a look at content-related directives. The first is the ion-content directive.

Navigation

The next component we are going to take a look at is the navigation component. The navigation component has a bunch of directives as well as a couple of services.

The first directive we are going to take a look at is ion-nav-view.

When the app boots up, $stateProvider will look for the default state and then will try to load the corresponding template inside the ion-nav-view.

Tabs and side menu

To understand navigation a bit better, we will explore the tabs directive and the side menu directive.

We will scaffold the tabs template and go through the directives related to tabs; run this:

ionic start -a "Example 19" -i app.example.nineteen example19 tabs 

Using the cd command, go to the example19 folder and run this:

  ionic serve

This will launch the tabs app.

If you open www/index.html file, you will notice that this template uses ion-nav-bar to manage the header with ion-nav-back-button inside it.

Next open www/js/app.js and you will find the application states configured:

.state('tab.dash', {
    url: '/dash',
    views: {
      'tab-dash': {
        templateUrl: 'templates/tab-dash.html',
        controller: 'DashCtrl'
      }
    }
  })

Do notice that the views object has a named object: tab-dash. This will be used when we work with the tabs directive. This name will be used to load a given view when a tab is selected into the ion-nav-view directive with the name tab-dash.

If you open www/templates/tabs.html, you will find a markup for the tabs component:

<ion-tabs class="tabs-icon-top tabs-color-active-positive">
 
  <!-- Dashboard Tab -->
  <ion-tab title="Status" icon-off="ion-ios-pulse" icon-on="ion-
ios-pulse-strong" href="#/tab/dash">     <ion-nav-view name="tab-dash"></ion-nav-view>   </ion-tab>     <!-- Chats Tab -->   <ion-tab title="Chats" icon-off="ion-ios-chatboxes-outline"
icon-on="ion-ios-chatboxes" href="#/tab/chats">     <ion-nav-view name="tab-chats"></ion-nav-view>   </ion-tab>     <!-- Account Tab -->   <ion-tab title="Account" icon-off="ion-ios-gear-outline" icon-
on="ion-ios-gear" href="#/tab/account">     <ion-nav-view name="tab-account"></ion-nav-view>   </ion-tab>   </ion-tabs>

The tabs.html will be loaded before any of the child tabs load, since tab state is defined as an abstract route.

The ion-tab directive is nested inside ion-tabs and every ion-tab directive has an ion-nav-view directive nested inside it. When a tab is selected, the route with the same name as the name attribute on the ion-nav-view will be loaded inside the corresponding tab.

Very neatly structured!

You can read more about tabs directive and its services at http://ionicframework.com/docs/nightly/api/directive/ionTabs/.

Next, we are going to scaffold an app using the side menu template and go through the navigation inside it; run this:

ionic start -a "Example 20" -i app.example.twenty example20 sidemenu

Using the cd command, go to the example20 folder and run this:

  ionic serve

This will launch the side menu app.

We start off exploring with www/index.html. This file has only the ion-nav-view directive inside the body. Next, we open www/js/app/js. Here, the routes are defined as expected. But one thing to notice is the name of the views for search, browse, and playlists. It is the same—menuContent—for all:

.state('app.search', {
    url: "/search",
    views: {
      'menuContent': {
        templateUrl: "templates/search.html"
      }
    }
})

If we open www/templates/menu.html, you will notice ion-side-menus directive. It has two children ion-side-menu-content and ion-side-menu. The ion-side-menu-content displays the content for each menu item inside the ion-nav-view named menuContent. This is why all the menu items in the state router have the same view name.

The ion-side-menu is displayed on the left-hand side of the page. You can set the location on the ion-side-menu to the right to show the side menu on the right or you can have two side menus.

Do notice the menu-toggle directive on the button inside ion-nav-buttons. This directive is used to toggle the side menu.

If you want to have the menu on both sides, your menu.html will look as follows:

<ion-side-menus enable-menu-with-back-views="false">
  <ion-side-menu-content>
    <ion-nav-bar class="bar-stable">
      <ion-nav-back-button>
      </ion-nav-back-button>
 
      <ion-nav-buttons side="left">
        <button class="button button-icon button-clear ion-
navicon" menu-toggle="left">         </button>       </ion-nav-buttons>       <ion-nav-buttons side="right">         <button class="button button-icon button-clear ion-
navicon" menu-toggle="right">         </button>       </ion-nav-buttons>     </ion-nav-bar>     <ion-nav-view name="menuContent"></ion-nav-view>   </ion-side-menu-content>     <ion-side-menu side="left">     <ion-header-bar class="bar-stable">       <h1 class="title">Left</h1>     </ion-header-bar>     <ion-content>       <ion-list>         <ion-item menu-close ng-click="login()">           Login         </ion-item>         <ion-item menu-close href="#/app/search">           Search         </ion-item>         <ion-item menu-close href="#/app/browse">           Browse         </ion-item>         <ion-item menu-close href="#/app/playlists">           Playlists         </ion-item>       </ion-list>     </ion-content>   </ion-side-menu>   <ion-side-menu side="right">     <ion-header-bar class="bar-stable">       <h1 class="title">Right</h1>     </ion-header-bar>     <ion-content>       <ion-list>         <ion-item menu-close ng-click="login()">           Login         </ion-item>         <ion-item menu-close href="#/app/search">           Search         </ion-item>         <ion-item menu-close href="#/app/browse">           Browse         </ion-item>         <ion-item menu-close href="#/app/playlists">           Playlists         </ion-item>       </ion-list>     </ion-content>   </ion-side-menu> </ion-side-menus>

You can read more about side menu directive and its services at http://ionicframework.com/docs/nightly/api/directive/ionSideMenus/.

This concludes our journey through the navigation directives and services. Next, we will move to Ionic loading.

Ionic loading

The first service we are going to take a look at is $ionicLoading. This service is highly useful when you want to block a user's interaction from the main page and indicate to the user that there is some activity going on in the background.

To test this, we will scaffold a new blank template and implement $ionicLoading; run this:

ionic start -a "Example 21" -i app.example.twentyone example21 blank

Using the cd command, go to the example21 folder and run this:

  ionic serve

This will launch the blank template in the browser.

We will create an app controller and define the show and hide methods inside it. Open www/js/app.js and add the following code:

.controller('AppCtrl', function($scope, $ionicLoading, $timeout) {
 
    $scope.showLoadingOverlay = function() {
        $ionicLoading.show({
            template: 'Loading...'
        });
    };
    $scope.hideLoadingOverlay = function() {
        $ionicLoading.hide();
    };
 
    $scope.toggleOverlay = function() {
        $scope.showLoadingOverlay();
 
        // wait for 3 seconds and hide the overlay
        $timeout(function() {
            $scope.hideLoadingOverlay();
        }, 3000);
    };
 
})

We have a function named showLoadingOverlay, which will call $ionicLoading.show(), and a function named hideLoadingOverlay(), which will call $ionicLoading.hide(). We have also created a utility function named toggleOverlay(), which will call showLoadingOverlay() and after 3 seconds will call hideLoadingOverlay().

We will update our www/index.html body section as follows:

<body ng-app="starter" ng-controller="AppCtrl">
    <ion-header-bar class="bar-stable">
        <h1 class="title">$ionicLoading service</h1>
    </ion-header-bar>
    <ion-content class="padding">
        <button class="button button-dark" ng-click="toggleOverlay()">
            Toggle Overlay
        </button>
    </ion-content>
</body>

We have a button that calls toggleOverlay().

If you save all the files, head back to the browser, and click on the Toggle Overlay button, you will see the following screenshot:

Learning Ionic

As you can see, the overlay is shown till the hide method is called on $ionicLoading.

You can also move the preceding logic inside a service and reuse it across the app. The service will look like this:

.service('Loading', function($ionicLoading, $timeout) {
    this.show = function() {
        $ionicLoading.show({
            template: 'Loading...'
        });
    };
    this.hide = function() {
        $ionicLoading.hide();
    };
 
    this.toggle= function() {
        var self  = this;
        self.show();
 
        // wait for 3 seconds and hide the overlay
        $timeout(function() {
            self.hide();
        }, 3000);
    };
 
})

Now, once you inject the Loading service into your controller or directive, you can use Loading.show(), Loading.hide(), or Loading.toggle().

If you would like to show only a spinner icon instead of text, you can call the $ionicLoading.show method without any options:

$scope.showLoadingOverlay = function() {
        $ionicLoading.show();
    };

Then, you will see this:

Learning Ionic

You can configure the show method further. More information is available at http://ionicframework.com/docs/nightly/api/service/$ionicLoading/.

You can also use the $ionicBackdrop service to show just a backdrop. Read more about $ionicBackdrop at http://ionicframework.com/docs/nightly/api/service/$ionicBackdrop/.

You can also checkout the $ionicModal service at http://ionicframework.com/docs/api/service/$ionicModal/; it is quite similar to the loading service.

Popover and Popup services

Popover is a contextual view that generally appears next to the selected item. This component is used to show contextual information or to show more information about a component.

To test this service, we will be scaffolding a new blank app:

ionic start -a "Example 23" -i app.example.twentythree example23 
blank

Using the cd command, go to the example23 folder and run this:

  ionic serve

This will launch the blank template in the browser.

We will add a new controller to the blank project named AppCtrl. We will be adding our controller code in www/js/app.js.

.controller('AppCtrl', function($scope, $ionicPopover) {
 
    // init the popover
    $ionicPopover.fromTemplateUrl('button-options.html', {
        scope: $scope
    }).then(function(popover) {
        $scope.popover = popover;
    });
 
    $scope.openPopover = function($event, type) {
        $scope.type = type;
        $scope.popover.show($event);
    };
 
    $scope.closePopover = function() {
        $scope.popover.hide();
        // if you are navigating away from the page once
        // an option is selected, make sure to call
        // $scope.popover.remove();
    };
 
});

We are using the $ionicPopover service and setting up a popover from a template named button-options.html. We are assigning the current controller scope as the scope to the popover. We have two methods on the controller scope that will show and hide the popover. The openPopover method receives two options. One is the event and second is the type of the button we are clicking (more on this in a moment).

Next, we update our www/index.html body section as follows:

<body ng-app="starter" ng-controller="AppCtrl">
    <ion-header-bar class="bar-positive">
        <h1 class="title">Popover Service</h1>
    </ion-header-bar>
    <ion-content class="padding">
        <button class="button button-block button-dark" ng-
click="openPopover($event, 'dark')">             Dark Button         </button>         <button class="button button-block button-assertive" ng-
click="openPopover($event, 'assertive')">             Assertive Button         </button>         <button class="button button-block button-calm" ng-
click="openPopover($event, 'calm')">             Calm Button         </button>     </ion-content>     <script id="button-options.html" type="text/ng-template">         <ion-popover-view>             <ion-header-bar>                 <h1 class="title">{{type}} options</h1>             </ion-header-bar>             <ion-content>                 <div class="list">                     <a href="#" class="item item-icon-left">                         <i class="icon ion-ionic"></i> Option One                     </a>                     <a href="#" class="item item-icon-left">                         <i class="icon ion-help-buoy"></i> Option
Two                     </a>                     <a href="#" class="item item-icon-left">                         <i class="icon ion-hammer"></i> Option
Three                     </a>                     <a href="#" class="item item-icon-left" ng-
click="closePopover()">                         <i class="icon ion-close"></i> Close                     </a>                 </div>             </ion-content>         </ion-popover-view>     </script> </body>

Inside ion-content, we have created three buttons, each themed with a different mood (dark, assertive, and calm). When a user clicks on the button, we show a popover that is specific for that button. For this example, all we are doing is passing in the name of the mood and showing the mood name as the heading in the popover. But you can definitely do more.

Do notice that we have wrapped our template content inside ion-popover-view. This takes care of positioning the modal appropriately.

The template must be wrapped inside the ion-popover-view for the popover to work correctly.

When we save all the files and head back to the browser, we will see the three buttons. Depending on the button you click, the heading of the popover changes, but the options remain the same for all of them:

Learning Ionic

Then, when we click anywhere on the page or the close option, the popover closes.

If you are navigating away from the page when an option is selected, make sure to call:

$scope.popover.remove();

You can read more about Popover at http://ionicframework.com/docs/api/controller/ionicPopover/.

Our GitHub organization

With the ever-changing frontend world, keeping up with latest in the business is quite essential. During the course of the book, Cordova, Ionic, and Ionic CLI has evolved a lot and we are predicting that they will keep evolving till they become stable. So, we have created a GitHub organization named Learning Ionic (https://github.com/learning-ionic), which consists of code for all the chapters. You can raise issues, submit pull requests and we will also try and keep it updated with the latest changes. So, you can always refer back to GitHub organization for the possible changes.

Summary

In this article, we looked at various Ionic directives and services that help us develop applications easily.

Resources for Article:


Further resources on this subject:


You've been reading an excerpt of:

Learning Ionic

Explore Title