Hello Master Detail: Your Fourth Ionic Framework App

Hello Master Detail: Your Fourth Ionic Framework App

posted in: Uncategorized | 1

In the third post of this series, Hello Modules: Your Third Ionic Framework App, we learned how to split up our existing app into a more scaling architecture using modules. In this fourth instalment, we’ll look at adding a master list which links to a details view by modifying our existing Page2 state. You can follow along on the Ionic Playground.

Quick Review

Just as a quick review, we have two states, main and page2, with a controller for each. We also have a userService factory that is used by our page2 state. These views are split up into three modules: ionicApp, ionicApp.Main, and ionicApp.Page2.

/**************************************************
+	Module:		ionicApp
**************************************************/
angular.module('ionicApp', ['ionic','ionicApp.Main','ionicApp.Page2'])

.config(function($urlRouterProvider){  
  //Default Route for the whole app
  $urlRouterProvider.otherwise('/main');
});

/**************************************************
+	Module:		ionicApp.Main
+	Exports:
+		MainCtrl
**************************************************/
angular.module('ionicApp.Main', ['ionic'])

.config(function($stateProvider, $urlRouterProvider){
  $stateProvider
  .state('main', {
    url: "/main",
    templateUrl: "templates/main.html",
    controller: 'MainCtrl'
  });
})

.controller("MainCtrl",function(){
  console.log("Main Controller says: Hello World");
});

/**************************************************
+	Module:		ionicApp.Page2
+	Exports:
+		userService
+		Page2Ctrl
**************************************************/
angular.module('ionicApp.Page2', ['ionic'])

.config(function($stateProvider, $urlRouterProvider){
  $stateProvider
  .state('page2', {
		url: "/page2",
		templateUrl: "templates/page2.html",
		controller: "Page2Ctrl"
	})
})

.factory('userService', function($http) {
	return {
		getUsers: function(){
			return $http.get('https://randomuser.me/api/?results=10').then(function(response){
				return response.data.results;
			});
		}
	}
})

.controller("Page2Ctrl",function($scope, userService){
	userService.getUsers().then(function(users){
		$scope.users = users;
	});
});

Goal

Our goal by the end of this tutorial is to change our Page2 state to have a master list state where tapping on each list item takes us to a details view of that list item. In the process we will be renaming our Page2 state to UserList and adding a new state called UserListDetail.

What is the Master Detail Pattern?

A Master Detail Interface is a common pattern in computer systems where a master list is displayed, and when an item is selected, more details about that item are displayed in a separate view. This can be found all over the place from a contact list (list of contacts being the master list, clicking on a contact shows you the “details” of that contact) to a text messages app, to search results.

Read More: Ionic: Master Detail Pattern

Renaming Page2

We are going to start by renaming our module, controller, view, and state and fixing the references to them. First, rename the module itself:

//Before
angular.module('ionicApp.Page2', ['ionic'])

//After
angular.module('ionicApp.UserList', ['ionic'])

Make sure to update our app module to point to this new module name.

//Before
angular.module('ionicApp', ['ionic','ionicApp.Main','ionicApp.Page2'])

//After
angular.module('ionicApp', ['ionic','ionicApp.Main','ionicApp.UserList'])

Next the controller:

//Before
.controller("Page2Ctrl",function($scope, userService){

//After
.controller("UserListCtrl",function($scope, userService){

Finally the state. Notice we are changing the name, the url, the templateUrl, and the controller. Make sure that a template exists the matches the URL (Aka: Rename the existing template).

.config(function($stateProvider, $urlRouterProvider){
  $stateProvider
  .state('UserList', {
		url: "/UserList",
		templateUrl: "templates/UserList.html", //Make sure this actually exists!
		controller: "UserListCtrl"
	})
})

We’re missing just one small detail. Let’s update the template of the Main state to link to this new state.

//Before
<p>Hello World, how are you? <a href="#/page2">Go to Page 2</a></p>

//After
<p>Hello World, how are you? <a href="#/UserList">Go to Page 2</a></p>

Master List

Now, let’s update UserList state (our master list) to link to the details view that we will create in the next step. We will also do a couple cosmetic fixes like removing padding from around the list and removing the “I am page 2!” text.

Fist, the easy part, the cosmetics.

<ion-header-bar class="bar-stable">
	<h1 class="title">User List</h1>
</ion-header-bar>
<ion-content>
	<ion-list>
		<ion-item class="item-avatar" ng-repeat="item in users">
			<img src="{{item.user.picture.thumbnail}} " />
			<h2>{{item.user.name.first}} {{item.user.name.last}}</h2>
			<p>{{item.user.location.city}} {{item.user.password}}</p>
		</ion-item>
	</ion-list>
</ion-content>

Next, we will add the ui-sref attribute to our ion-item and give it a the name of the details state (UserListDetail). We will also pass in the $index of the current item so the details view knows which item to display.

ui-sref maps a state name (with or without parameters) to a URL. This is useful in that the state name and the URL of the state are independent.

$index is the index of which item we are on in the array. Think of it like if you were doing a for loop over the array.

<ion-item class="item-avatar" ng-repeat="item in users" ui-sref='UserListDetail({index: $index})'>
	<img src="{{item.user.picture.thumbnail}} " />
	<h2>{{item.user.name.first}} {{item.user.name.last}}</h2>
	<p>{{item.user.location.city}} {{item.user.password}}</p>
</ion-item>

master list

Details State

For our details UserListDetail state, we will need to create a new view, new state configuration, modify our userService factory, and create a new controller.

View

Lets start with the view. We’ll create a view that has the user’s full name in the header bar, a card layout with the information from the master list, as well as their email and phone number.

<ion-header-bar class="bar-stable">
	<h1 class="title">{{item.user.name.first}} {{item.user.name.last}}</h1>
</ion-header-bar>
<ion-content>
	<div class="list card">
		<div class="item item-avatar">
			<img src="{{item.user.picture.thumbnail}} " />
			<h2>{{item.user.name.first}} {{item.user.name.last}}</h2>
			<p>{{item.user.location.city}} {{item.user.password}}</p>
		</div>
		<div class="item item-icon-left">
			<i class="icon ion-email"></i>
			{{item.user.email}}
		</div>
		<div class="item item-icon-left">
			<i class="icon ion-ios-telephone"></i>
			{{item.user.phone}}
		</div>
	</div>
</ion-content>

State Configuration

Next, lets set up the state. To do this, we’ll update the .config we already have for the UserList module.

.config(function($stateProvider, $urlRouterProvider){
  $stateProvider
  .state('UserList', {
		url: "/UserList",
		templateUrl: "templates/UserList.html",
		controller: "UserListCtrl"
	})
	.state('UserListDetail', {
		url: "/UserListDetail/:index",
		templateUrl: "templates/UserListDetail.html",
		controller: "UserListDetailCtrl"
	})
})

You’ll notice in the URL of the new UserListDetail state, we have at the end :index. This is a URL Parameter that will be replaced by the index of whatever item we clicked on. Recall from our master view we are passing in the index of the current item.

ui-sref='UserListDetail({index: $index})'

For example, if we clicked on the third item the URL would become something like this: /UserListDetail/2.

Factory

The next thing we need to do is modify our userService to support getting a single user. To do this, we need to do two things. First, we need to have a private users array where the initial call to getUsers will store all of the users. Then, we will create a new method of our factory, getUser, which will grab an item from the users array given an index.

First, the private variable.

.factory('userService', function($http) {
	var users = [];

	return {
	//etc

Next, let’s modify getUsers to populate that variable.

getUsers: function(){
	return $http.get('https://randomuser.me/api/?results=10').then(function(response){
		users = response.data.results; //Populate the new variable
		return response.data.results;
	});
},

Finally, the new getUser method:

getUser: function(index){
	return users[index];
}

Our final factory looks like this:

.factory('userService', function($http) {
  var users = [];
  
	return {
		getUsers: function(){
			return $http.get('https://randomuser.me/api/?results=10').then(function(response){
				users = response.data.results;
				return response.data.results;
			});
		},
		getUser: function(index){
		  return users[index];
		}
	}
})

Controller

Finally, our UserListDetailCtrl controller needs to grab the index parameter via the $stateParams and then set $scope.item by calling the userService.getUser method and passing in the index.

.controller("UserListDetailCtrl",function($scope, $stateParams, userService){
	var index = $stateParams.index;
	$scope.item = userService.getUser(index);
});

details

Conclusion

Through the steps in this tutorial, we have updated our app to have a master detail pattern where additional information about a user is displayed when selected from a list. This is a very popular and common pattern and you will more definitely use it when creating most apps.

Questions? Comment below!

My name is Andrew McGivery. I currently work full time as an application developer at Manulife Financial in Canada. My current passion is building and leading highly engaged teams where employee happiness, learning, and growth is a priority.

One Response

  1. Thanks for these posts….. Very detailed and very helpful for new developers.

Leave a Reply