Creating a basic MVC project

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:

  • Define a view (template file) to display the contact list
  • Define a controller in order to handle interactions with the list
  • Provide the necessary model logic in order to provide contacts' information.

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.

Creating the view

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.

Creating the list view

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 list
  • ion-item: This indicates a single data node, which can be rendered in a list

Since 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.

Note

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>

Creating the controller

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:

  1. We defined a module using the core angular.module() function, which takes the following parameters:
    • The first parameter is phonebook.controllers, which is the name of the module.
    • The second parameter is an empty list, which indicates that this module has no external dependencies. If it did, we would list their names here so that the AngularJS dependency injection system can resolve them during runtime.
  2. Having created a module, we attach a controller to it using the (aptly named) core function controller(). The following arguments are passed to this function:
    • ContactsCtrl, which is the name of the controller, is the first argument.
    • The second argument is a 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.

Connecting the view and controller

We have the following two choices if we need to bring our newly created view and controller together:

  • We can use an inline view, where we put a reference to the view and controller directly in the index.html file or another template, which is in turn loaded from index.html
  • We can use a router to associate the view and its controller with a certain path within the application

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:

  1. The 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.
  2. We use the $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:
    • The current path within the app is root (/),that is, we are in this state whenever we are at the initial path of the app.
    • The template to be loaded for this state is templates/contacts.html, which is the same view template that we created earlier.
    • The controller associated with this view is 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.
  3. Finally, we configure a default route for our app. This is the route our navigation will resolve to if no other valid route is provided. In our case, we always default to the start screen.

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:

  • The script tag simply imports the phonebook.controllers module.
  • The 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

  • The 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.
  • Finally, 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.

Testing the connection

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:

Testing the connection

Note

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.

Creating the model

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.

Services

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:

  • It provides modular interfaces to work with a certain aspect of your app's business logic. Your model can be built around several services, each providing a single, essential feature of your app's functionality.
  • It is efficient, since you only have a single instance of each service available throughout your entire app.
  • It makes it easier to extend your app, as units of functionality can be defined and injected wherever they are needed.

Now that we have established how awesome services are, the natural question is, how are we going to go about their creation?

Creating services

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:

  • Constants: The most simple service, this is used to define a single constant, which is available throughout the entire application. It varies from the other four in the sense that it is immediately instantiated when an app starts up, which means that it can be used during the configuration phase of the app's lifecycle. Constants are often used to contain constant values such as base URLs.
  • Values: This is similar to constants, with the notable exception that it is not available during the configuration phase of the app. It may also be used in decorators.
  • Factories: Whereas constants and values are used to store simple values, factories begin making things much more interesting:
    • They provide factory functions, which can be used to define logic rather than just values. This means that a factory can provide multiple functions, which compute values based on input.
    • Factories can have dependencies, which means that you can construct them using other services.
    • Factories are lazily instantiated, which means that they are only instantiated when they are needed.

    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.

  • Services: Services are very, very similar to factories. While there are some minor differences in semantics, their real contribution is to provide a concise syntax. Using a factory or service is almost always a matter of preference. Oh, and in case you are wondering about the name, the AngularJS developers themselves regret calling it services, likening it to naming your child child.
  • Providers: Finally, the most advanced recipe is the provider, which offers the full range of functionality offered by services (no, not the ones that we just mentioned; we are talking about the actual services—the naming was a pretty bad idea, wasn't it?) In particular, Providers allow you to expose the service to configuration during the 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.

Creating a factory

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.247.11