Going from static to dynamic with Ionic Creator

This post is more than 2 years old.

As I've mentioned more than once now, I'm really happy with how much Ionic Creator has improved recently. For this blog post, I thought it might be useful to demonstrate how you could go from a "static" Ionic Creator proof of concept to a dynamic one that made use of a real API. For hard core developers, this is probably not going to be very helpful. But I imagine Creator will attract folks who may not have a lot of experience working with JavaScript and APIs so I thought a concrete example would be helpful. As always, if anything doesn't make sense, leave me a comment and I'll try my best to help out.

Let's begin by discussing the type of application we're going to build. It will be a simple "Master/Detail" example where the initial page is a list of items and the detail provides - well - detail. As a completely random "not related to anything recent" idea, let's use Star Wars films for our data.

It just so happens that an API exists, SWAPI, that provides information about Star Wars films. In fact, I've already released a helper library for this API: SWAPI-Wrapper. We won't be using that helper in this blog post, but just remember it if you decide to actually use this data in a real application.

Creating the Static Proof of Concept

We'll start off by creating a new application in Ionic Creator. Remember that this is 100% free to try. You only need to pay if you want additional projects. (You can find more detail on their Pricing Page).

Begin by creating a new project, the name doesn't matter, and use the Blank template:

shot1

This will drop you into the editing interface with a blank page. On this page we'll do two things. First, we'll edit the title to give it something that makes sense for the app.

shot2

Then we'll drag a List component onto the page:

shot3

Notice how it adds 3 list items automatically. If you want, you can remove some, or add some, but for now, three is just fine. If you click each one, you can give them a unique text value. While not necessary, I'd go ahead and do that just so you mentally keep in mind what we're actually building.

shot4

Notice that the list items have a "Link" attribute. We can use that to add basic interaction to our demo, but for now, we don't have a page to actually target for that link. Let's fix that by adding a new page. Be sure to use the Blank template again. I gave it a simple title too:

shot5

This page represents the detail view of the film. Right now we don't necessarily know what we're going to show, so let's keep it simple and imagine we'll just show the opening crawl. On the off chance that my readers have never seen a Star Wars film (for shame), this is an example of what I mean:

crawl

For now, let's just use a bit of static text. Drag the Paragraph component onto the page and then edit the content to be something that describes the purpose of the text block.

shot6

Now let's hook up the list from the first page to the detail. Now, in the real application, each list item would link to a page showing different text based on the selection. However, the dynamic aspect will be handled by code we add later on. If you were to demonstrate this dummy app to a client, you may need to make 3 distinct pages so they don't get confused. If you do, don't forget that you can select the page in the left hand nav and click the "Duplicate" icon.

For now, click back to the first page, and select the first list item. Note that you can now select a link that points to the new page.

shot7

Go ahead and do that for all three list items (and again, you don't really need to) and then click the Preview icon on top to test out your beautiful, if fake, application.

shot8

Woot! We're done with the prototype!

Creating the Application - Part 1

Ok, so at this point, we've got a working prototype. The first thing we need to do is get a copy of the code. You can use the Export menu option to open a window showing you four different ways of working with the code. I recommend using the Zip File. While we can create a new application from the code of the prototype directly with the CLI, I think it would be nice to have a copy of the prototype locally to compare and contrast while working on the new version.

shot9

I recommend creating a new folder for this project, and then extracting the zip into a folder. (All of my code for this blog entry is in GitHub, and that's the way I laid out stuff there as well.) Assuming you've done this in a folder called creator_version, we can use the Ionic CLI to create a new application based on the contents. That command is:

ionic start v1 ./creator_version

The "v1" part there is the name of the subdirectory. As you can guess, we're going to iterate a bit from the original Creator version to our final version. Why?

We currently have a static version of the application. It doesn't use any "real" data. Our first iteration is going to make the application dynamic, but it is going to use fake, static data.

Ok, that probably sounds confusing. Let me explain again.

Right now, our list of films is a hard coded list of 3 films.

We're going to create a "Service" in our application responsible for returning the list of films. Our plan is to use SWAPI remote service, but to keep it simple for now we'll build a service that returns 3 'fake' films. We'll then edit the first page to render those films as if they had come from some remote service. Essentially we will go from static to "Dynamic with Fake Data". After we have this running well, we'll then use the "real" service. (This part of the process is very important. So if it doesn't make sense, let me know in the comments.)

Ok, so go into the v1 folder and open it with your favorite editor. We need to modify three things to make the initial page dynamic. Let's start with the template. Right now it is hard coded for three films:


<ion-view title="Star Wars Films">
    <ion-content overflow-scroll="true" padding="true" class="has-header">
        <ion-list>
            <ion-item href="#/page4">A New Hope</ion-item>
            <ion-item href="#/page4">The Empire Strikes Back</ion-item>
            <ion-item href="#/page4">Return of the Jedi</ion-item>
        </ion-list>
    </ion-content>
</ion-view>

We'll begin by removing two of the ion-items and making the third dynamic.


<ion-view title="Star Wars Films">
    <ion-content overflow-scroll="true" padding="true" class="has-header">
        <ion-list>
			<ion-item ng-repeat="film in films" ui-sref="filmTitle({id:film.id})">{{film.title}}</ion-item>
        </ion-list>
    </ion-content>
</ion-view>

There's two important things here. First, we are repeating "film" over "films". We don't have that data yet, but I know when I build it I'll have it return an array of films. I also guess that I'll have a title value and an ID that uniquely identifies it. (This is actually going to be a mistake, but that's ok, it's a good mistake!)

Now let's look at the controller. By default Creator made a blank one for us called starWarsFilmCtrl. We need to edit this to work with data.


.controller('starWarsFilmsCtrl', function($scope,FilmService) {
	$scope.films = [];
	
	FilmService.getFilms().then(function(res) {
		$scope.films = res;		
	});
	
})

Ok, so what in the heck is FilmService? We haven't written it yet! Basically we're setting up the controller to work with a service we'll write later that's going to return our array of data. Note we use $scope.films to set the initial, empty array. We can then call the service and set the result.

Let's go ahead and update the other view first. The detail page begins like so:


<ion-view title="Film Title">
    <ion-content overflow-scroll="true" padding="true" class="has-header">
        <div>
            <p>The opening crawl would go here.</p>
        </div>
    </ion-content>
</ion-view>

We need to make the title and text dynamic. Here is the updated version:


<ion-view>
	<ion-nav-title>{{film.title}}</ion-nav-title>
    <ion-content overflow-scroll="true" padding="true" class="has-header">
        <div>
            <p>{{film.crawl}}</p>
        </div>
    </ion-content>
</ion-view>

Why did we switch to ion-nav-title? See this blog post for an explanation. Basically we need to use that directive to handle dynamic titles.

Ok, so now let's go and update the controller.


.controller('filmTitleCtrl', function($scope,$stateParams,FilmService) {
	$scope.film = {};
	
	FilmService.getFilm($stateParams.id).then(function(res) {
		$scope.film = res;	
	});
	
})

As before, we're using a FilmService that doesn't exist yet. I'm calling the service in a way that makes sense to me (first GetFilms then GetFilm). The $stateParams part relates back to how we handle navigation. In the first view, make note of the ui-sref part. This handles changing to a new state and passing a unique id. In order to make that work, we need to do a small change to the routes.js file. Right now it has this hard coded route:


    .state('filmTitle', {
      url: '/page4',
      templateUrl: 'templates/filmTitle.html',
      controller: 'filmTitleCtrl'
    })

In order to handle recognizing an ID, we modify it to this:


    .state('filmTitle', {
      url: '/page4/:id',
      templateUrl: 'templates/filmTitle.html',
      controller: 'filmTitleCtrl'
    })

Alright - so now for the final part, we build the service. We're going to write it to "agree with" what the controller was expecting. Here's the complete code:


angular.module('app.services', [])

.factory('FilmService', ['$q',function($q){

	return {
		getFilms:function() {
			var deferred = $q.defer();
			
			//temp 
			var films = [
				{
					id:1,
					title:"A New Hope",
					crawl:"ANH crawl"
				},
				{
					id:2,
					title:"The Empire Strikes Back",
					crawl:"ESB crawl"
				},
				{
					id:3,
					title:"Return of the Jedi",
					crawl:"ROTJ crawl"
				}
			];
				
			deferred.resolve(films);
			return deferred.promise;
		},
		getFilm:function(id) {
			var deferred = $q.defer();
			
			//temp
			var film = {
				id:id,
				title:"Film "+id,
				crawl:"Crawl for "+id
			};

			deferred.resolve(film);
			return deferred.promise;
			
			
			
		}	
	};

}]);

So let's quickly review what we did here. We updated the completely static application to be partially dynamic. Both the initial page (the list) and the detail are now dynamic. The controller speaks to the service to ask for data, returns it and makes it available to the templates. The data itself may be static, but every other aspect of the application is now dynamic! Woot.

At this point, I recommend taking the app for test drive just to ensure it is working correctly. Here is it running with ionic serve -l:

shot10

If you want, edit the services file to add a new film. When you reload, you'll see the new item show up.

Creating the Application - Part 2

Alright - so in theory now the only thing we need to is update the services file to use SWAPI. In theory. As I kind of alluded to before, we're going to run into a small issue but that's ok - we're professionals and we can handle it. Using SWAPI is pretty easy (and you can read the docs for a full explanation), so let's begin by making the call to get films use real data.

Here is the updated version:


getFilms:function() {
	var deferred = $q.defer();
			
	$http.get("http://swapi.co/api/films").then(function(res) {
		//console.dir(res.data.results);
		deferred.resolve(res.data.results);
	});
	return deferred.promise;
},

Yeah, that's it. Literally just a call to a URL. As a quick note, we modified the services injected into the file:


.factory('FilmService', ['$http','$q',function($http,$q){

So in theory, as soon as you test this, it works. You should see a complete list of films:

shot11

However, clicking to the detail won't work. Why? The SWAPI doesn't actually return an "id" value. Notice the console.dir on the results in the code snippet above? It's currently commented out, but if you remove the comments, you can see the data yourself. This is also fully documented as well.

So what do we do? We need a "unique" way to identify the film so we can fetch the details. Turns out there is a url property on the film. That points to the film's detail on the API. We have two options here. We can actually modify the data in the service file so that id does exist and uses the URL. Or we can simply modify the template to use the new property. I prefer to keep the template as is and modify the service. Basically my code gets to pretend that SWAPI returned the data as I expected:


$http.get("http://swapi.co/api/films").then(function(res) {
	//console.dir(res.data.results);
	var results = res.data.results.map(function(result) {
		result.id = result.url;
		return result;
	});
	deferred.resolve(results);
});

The last modification is to get film details. We're passing in the URL value so the code here is rather simple:


getFilm:function(url) {
	var deferred = $q.defer();
			
	$http.get(url).then(function(res) {
		//console.dir(res.data);
		deferred.resolve(res.data);
	});

	return deferred.promise;
			
}	

And the result:

shot12

Wrap Up

Obviously every application will be different and our API was especially simple, but I hope this demonstration was useful. If you want to look at the code, you can find it here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/statictodynamic. As I said, please let me know if anything didn't make sense.

You've read the post, now watch the video!

Ok, while not necessarily required reading, here are a few quick notes:
  • Sharp readers may notice that the initial getFilms call actually returns all the data. We could make the application better if we stored that data in the service. Calls to getFilm would just return the appropriate portion of the stored data. What's cool is we can make this modification in the service and nothing else needs to change. This is basic MVC architecture stuff, but again, for folks who may be new to development, this is exactly the reason we use setups like this. It allows for optimizations later that are confined to one file and don't break other parts.
  • Since our application is performing HTTP requests, we really should provide feedback to the user. I'd suggest the Ionic Loading widget. I talked about that here: A quick example of the Ionic Loading Widget
Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Simon Grimm posted on 1/12/2016 at 6:59 PM

Awesome tutorial. Reminds me that I wanted to create a blogpost on the creator as well, seems to be quite useful by now. And the completely random use of Star Wars API makes this tutorial really great :D

One very very little thing... afaik controller names in Angular should be starting with a capital letter!

Comment 2 (In reply to #1) by Raymond Camden posted on 1/12/2016 at 7:01 PM

Good point. I'm just getting 'comfortable' with Angular, not quite at the 'following best practices' part. ;)

Comment 3 by João Faraco posted on 1/13/2016 at 9:04 PM

Love this! I'm getting started with Angular and this tut was great to see a simple practical application using a live API.

One little thing I noticed was the key for the opening crawl -- correct is "opening_crawl", instead of "crawl" ;)

Comment 4 (In reply to #3) by Raymond Camden posted on 1/13/2016 at 9:11 PM

Thanks - should be right in the repo (should be anyway).

Comment 5 (In reply to #4) by mayoralito posted on 1/14/2016 at 6:18 AM

Hi, nice tutorial and explanation. Thanks for sharing your experience.

Comment 6 by Mathias Banda posted on 1/27/2016 at 9:02 AM

Hey Raymond, I played with the stars wars app and it works great on windows devices. The android version just doesn't seem to retrieve data from the swapi server though works with hard-coded data. Any clues anyone? I have made sure the internet permissions are all enabled

Comment 7 (In reply to #6) by Raymond Camden posted on 1/27/2016 at 3:53 PM

Check the console (Chrome Remote Debug) - do you see an error?

Comment 8 by ABA posted on 5/13/2016 at 12:38 PM

Thanks for the tutorial.
I’d like to know how I can do to upgrade my app easily.

I have built an app V1with Ionic Creator with a single page, and exported it in ZIP file. Then I have added the javascript code for the controllers, and done some modifications in
HTML part. It works fine.

Now, I want to add a 2nd page, with Ionic Creator again. But I don’t want to lose the code of my controllers, neither my HTML modifications.

What is the best way to do that?

Is it possible, for instance, to import my app in Ionic Creator, including my modifications?

Comment 9 (In reply to #8) by Raymond Camden posted on 5/13/2016 at 1:49 PM

As far as I know, this is not supported. You could use IC to design the page and then get the output for just that view, but you can't import, nor can you easily bring in an update into your existing app. You would need to do it carefully.

Comment 10 by brandecho posted on 6/11/2016 at 11:58 AM

Great tutorial! 2 questions...
1. Instead of an ionic list component, can the same apply to an ionic card component with dynamic data?
2. Instead of the link going to a details page with a paragraph component, can the dynamic data be displayed using the cells component?

Also, would be great if there was a tutorial on doing the same and using a WordPress JSON API endpoint?

Thanks again for an awesome tutorial.

Comment 11 (In reply to #10) by Raymond Camden posted on 6/11/2016 at 12:27 PM

1) Anything is possible really. I know it sounds kinda lame to say that, but really - you can use any UI item that makes sense for your app.

2) I don't know what the cells component is - but it's the same thing really.

3) As for the WP API, again, possible. It's just another API. ;)

Comment 12 by Web Expert posted on 3/7/2017 at 7:05 AM

Great guide
I have question
how could edit route.js and app.js and index.html on ionic creator?

Comment 13 (In reply to #12) by Raymond Camden posted on 3/7/2017 at 12:18 PM

I haven't worked on Creator since their most recent updates which allow a much greater level of editing, even the JS files. I believe this may be supported - best thing to do is try it.