Our goal in this chapter is to create an app that can pull information from the contacts storage of the local device and display it to the user (a phonebook, if you like). To do so, we need to do the following things:
You may recall that this workflow fits nicely with the overall architecture of AngularJS, which follows the MVC pattern. We will take care of each item in turn.
Go ahead and add the following folder to your projects:
www/templates
Here, we will store all the view templates that we will use throughout our project. In this folder, let's create our first view file, as follows:
www/templates/contacts.html
If we need to add additional views to our app, we will do it in the same manner. Partitioning our views like this not only makes organization easier, but also boosts performance, since HTML will only be loaded on demand when it is needed, rather than all at once.
Let's add some content in order to create an actual view:
<ion-view view-title="contacts"> <ion-content> <ion-list> <ion-item> </ion-item> </ion-list> </ion-content> </ion-view>
Don't concern yourself with the list-related tags yet; we will get to what they do in a moment. For now, let's look at the two outer ones:
ion-view
: This tag tells Ionic that this is a view that can be dynamically loaded from other parts of the application. We add the view-title attribute to it in order to create a label that can be used to refer to the view.ion-content
: This tag designates a content area in the view, which is especially good at displaying scrolling data. Since we want to display a list of contacts of an unknown length, this is what we will want to wrap our list in.Lists are some of the most ubiquitous data structures in apps everywhere. So, it is no surprise that most frameworks provide powerful tools to work with them. Ionic is certainly no exception.
Have a look at the code that we added earlier. Especially note the tags inside the ion-content
tag:
<ion-view view-title="contacts"> <ion-content> <ion-list> <ion-item> </ion-item> </ion-list> </ion-content> </ion-view>
The following two tags encapsulate the majority of Ionic's list rendering capabilities and are generally everything that you need in order to display the list:
ion-list
: This indicates that the wrapped content is a listion-item
: This indicates a single data node, which can be rendered in a listSince each item in our list will be a single contact, we will wrap each of them in an ion-item
tag.
At present, our view does not really do much apart from displaying an empty list. We need to add some markup in order to show the details of the contact wrapped in each item. Let's do something basic first, such as just showing the name and mobile number of the contact, if any:
<ion-view view-title="contacts"> <ion-content> <ion-list> <ion-item> <h2>{{contact.name}}</h2> <p>{{contact.number}}</p> </ion-item> </ion-list> </ion-content> </ion-view>
Here, we defined two Angular expressions to render the name and number of a contact during runtime. The contact in this case is simply a JavaScript Object Notation (JSON) object holding information about a given person in our contact list. Next, we will see how to assign a concrete value to this JSON object; this will take place in the associated controller.
As of AngularJS 1.3, which is the version that is officially shipped with Ionic at the time of writing this book, it is possible to make expressions behave in a bind-once fashion. This means that they take on the values that they initially compute to, and the values are skipped by the AngularJS DOM update cycle after this, which improves performance. In our case, this works well, since we will only display our data once after fetching it, as we will see later. The downside of this method at this point in time is that updates to the data model will not be reflected in this view.
To make your expressions bind-once, add two colons (::
) before them, as follows:
<ion-view view-title="contacts"> <ion-content> <ion-list> <ion-item> <h2>{{::contact.name}}</h2> <p>{{::contact.number}}</p> </ion-item> </ion-list> </ion-content> </ion-view>
You may recall that the controller is the glue between your view and model (that is, your business logic). Its primary responsibility is to handle interactions with your UI from the user and delegate the processing of an appropriate response to the model.
In the previous chapter, the example controllers were located in a separate JavaScript file, where they were declared as part of a module. This is a good design practice, since it makes it much easier to structure your app.
Note that our blank project does not have such a file. So, let's start by creating it. Add the following file to your project:
www/js/controllers.js
In this file, let's add the following:
angular.module('phonebook.controllers', []) controller('ContactsCtrl', function ($scope) { });
Let's recap in brief to see what the preceding code does:
angular.module()
function, which takes the following parameters:phonebook.controllers
, which is the name of the module.controller()
. The following arguments are passed to this function:ContactsCtrl
, which is the name of the controller, is the first argument.function
that defines what the controller does. In this controller, we will define any and all the actions that are taking place in the segment of the app controlled by this controller. Note that this function takes the $scope
parameter (we will get to what it does a bit later). Just like the dependency list in the module, this parameter and all the others passed to the function (there can be any number of parameters or none at all) denote a dependency of this controller, which will be resolved at runtime using the dependency injection system.For now, this is all we need in order to have a full-fledged controller. Next, we will need to connect it with our view.
We have the following two choices if we need to bring our newly created view and controller together:
index.html
file or another template, which is in turn loaded from index.html
Even though we only have one view for now, we will go for the second option. This might seem redundant, but it makes it much easier to structure the app in the event that we want to add new navigation states later on (spoiler—we will!)
Routing is normally configured in the app.js
file. So, that is where we will go next. Open the file and make sure that it has the following content:
angular.module('phonebook', ['ionic', 'phonebook.controllers']) .run(function ($ionicPlatform) { $ionicPlatform.ready(function () { if (window.cordova&&window.cordova.plugins.Keyboard) { cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true); } if (window.StatusBar) { StatusBar.styleDefault(); } }); }) .config(function ($stateProvider, $urlRouterProvider) { $stateProvider .state('contacts', { url: '/', templateUrl: 'templates/contacts.html', controller: 'ContactsCtrl' }); $urlRouterProvider.otherwise('/'); });
Let's consider what is going on here, particularly in the config
function. Here, we set up the core navigation settings for our app by configuring the routing module exposed by $stateProvider
and $urlRouterProvider
. By default, Ionic uses the
ui-router
(for more information, visit https://github.com/angular-ui/ui-router). This router is state-oriented. That is, it lets you structure your app as a state machine, where each state can be connected to a path and a set of views and controllers. This setup makes it very easy to work with nested views, which is a frequent case when developing for mobile devices, where navigation elements like tabs, side menus, and the like are very common.
Knowing this, let's consider what this code actually does:
config
function itself takes two arguments, $stateProvider
and $urlRouterProvider
. Both are configuration interfaces belonging to ui-router
and can be used to configure the router when the app bootstraps.$stateProvider
argument in order to add the state contacts to our app. When the application is in this state, we make the following properties hold:/
),that is, we are in this state whenever we are at the initial path of the app.templates/contacts.html
, which is the same view template that we created earlier.ContactsCtrl
, which was defined earlier as well. Since our phonebook.controllers
module is loaded as a dependency for our app (see the first line of app.js
), we only need to name the controller, and the dependency injection system will do the rest.That's all we need to get the routing going! Next, we need to make sure that the index.html
file has the necessary content to load all the files that we configured so far, including the new contacts view. Open it, and make sure that it has the following content:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width"> <title></title> <link href="lib/ionic/css/ionic.css" rel="stylesheet"> <link href="css/style.css" rel="stylesheet"> <!-- ionic/angularjsjs --> <script src="lib/ionic/js/ionic.bundle.js"></script> <!-- cordova script (this will be a 404 during development) --> <script src="cordova.js"></script> <!-- your app's js --> <script src="js/controllers.js"></script> <script src="js/app.js"></script> </head> <body ng-app="phonebook"> <ion-nav-bar class="bar-stable"> <ion-nav-back-button></ion-nav-back-button> </ion-nav-bar> <ion-nav-view></ion-nav-view> </body> </html>
Pay attention to the highlighted parts:
script
tag simply imports the phonebook.controllers
module.ion-nav-bar
tag creates a standard navigation bar, which displays content specific to the current navigation context that the app is in.If you own an Android device, this will be similar to the action bar at the top, where you have your app logo, the name of the current view, and so on
Likewise, if you own an iPhone, this bar will be the bottom bar, which commonly holds the app's navigation tabs
ion-nav-back-button
tag creates a button to go backwards from the current navigation context to the previous one, much like a back button in a browser.ion-nav-view
is a special tag, which tells AngularJS where the routing system should bind the templates. In our case, this is where the templates/contacts.html
template will be rendered when our navigation context is the contacts state, as we defined in our router config
earlier.This is all we need for a basic setup of our app. To make sure it runs, let's try it out in the emulator. From the root of your project folder, run the following in a terminal or command line:
ionic serve
You should see the following output of the preceding command:
Depending on the browser you run your emulator in, you may not see the top or bottom bar. This is not generated by Ionic, but rather by a Chrome plugin, which automatically sets the size of the window to match that of an iPhone 6. The plugin is called Mobile/Responsive Web Design Tester, and we recommend that you try it for your own projects.
We now have a working view and controller. Next, we need a model that can provide the data we need in response to user input.
In AngularJS, it is considered good design practice to keep as little logic in the controllers as possible. Remember that controllers should only be the glue between the view and the model. They should ideally not be responsible for doing any of the heavy fetching and crunching of data. For that, AngularJS provides the services.
Services are objects that are injected into other components of your app on demand by the dependency injection system. If you look back at the app.js
code, you have seen some of them already; both the $stateProvider
and $urlRouterProvider
arguments fall in this category.
If you look at advanced AngularJS apps, you will start seeing services pretty much everywhere. They make up a large part of almost any app and can be used to contain almost any kind of functionality. For example, we can create a service that encapsulates access to a given REST API, allowing us to query it by a set of utility functions while the service itself handles connections to the server, security, and so on. Likewise, we can define a service that represents a set of mathematical operations, which can be fed simple data in order to get arithmetic results.
Services are singletons, which means that only a single instance of each exists during runtime.
Implementing your business logic in this fashion is important for several reasons, some of which are as follows:
Now that we have established how awesome services are, the natural question is, how are we going to go about their creation?
AngularJS provides several ways to create services. These ways are referred to as recipes. There are five of them in total—constants, values, providers, factories, and services. Each varies in complexity and the use cases that are suitable for them:
decorators
.For our app here this is the recipe that we will be using, and as such you will see its example soon. In fact, we contend that factories will fit most of the available use cases for most apps.
config
phase of the app before the service is actually used during the run
phase. It is worth while (and perhaps surprising) to note that the provider is actually the only recipe for services; the previous four are just syntactic sugar simplifying its use. Because it is so complex, it is an overkill for most cases, which is why there are other options to choose from depending on how complex your model logic is.So much knowledge, so little space! Let's put what we have learned to good use by actually creating a factory to retrieve contacts.
Like controllers, it is customary to place your service definitions in their own file (or files, if you prefer to have each service recipe in its own file. Here, we use a common file for all of them). In your project directory, create the following file:
js/services.js
In this file, put the following:
angular.module('phonebook.services', []) .factory('contactsFactory', function contactsFactory() { return { all: function () { return []; } } });
Let's have a look at what we have done:
We created a module named phonebook.services
to host our services
We defined a basic factory
service named contactsFactory
The service which exposes a single utility method is called all
, which currently does not do anything (we will change this soon, don't worry).
Now, we need to modify the app.js
and index.html
files in order to make the app aware of the new service. Make sure that the app.js
file starts with the following:
angular.module('phonebook', ['ionic', 'phonebook.controllers','phonebook.services'])
This injects the services module into the main app. Now, we just need to import the file the module is located in. To do so, make sure that your JavaScript imports in index.html
look like this:
<!-- your app's js --> <script src="js/controllers.js"></script> <script src="js/services.js"></script> <script src="js/app.js"></script>
That's it! We now have the groundwork of our app in place. However, if you run the emulator again, you will notice that not much has changed. We are still greeted by the same blank screen. Now, it is time to add some actual content to our app by loading the contact list.
3.135.247.11