Advanced Workflows for Building Rock-Solid Ionic Apps, Part 3
Jonathan Grupp is a software engineer at M-Way Solutions, the owner and maintainer of Generator-M-Ionic, and a frequent contributor to Ionic. He has written a three-part series on advanced workflows for building rock-solid Ionic apps. This is Part 3.
The air is getting a little thin in app-development orbit! Luckily, Part 3 of this series comes with a sweet oxygen mask full of life-saving elements: environments, CORS proxies, and build tools for app icons, splash screens, build variables, and app delivery. Take a deep breath and extend the project we made space-ready in Part 2.
Gulp Environments
You have your app all set up, and you added plugins and other components, while implementing your app logic. At some point, you will probably want to exchange data with an external backend. No problem: Just inject the good old Angular $http service, handle all the HTTP calls with that nice Angular promise API, and you’re done. For instance, let’s suppose I want to shoot some requests at the Postman Echo API.
$http.get('https://echo.getpostman.com/get')
.then(function (response) {
console.log(response);
});
Yeah! It’s as simple as that!
The customer changes the URL of the endpoint (that’s the beta version of the API):
https://blabber.getpostman.com
.
Urgh. “OK, if that keeps happening, I’m going to put the URL in a constant,” you might think. Clever! Luckily, your project already comes with a Config
constant in app/main/constants/config-const.js
. Just adapt your code.
.constant('Config', {
API_ENDPOINT: 'https://blabber.getpostman.com'
// ..
});
$http.get(Config.API_ENDPOINT + '/get')
.then(function (response) {
console.log(response);
});
The customer tells you that some of the features you need to implement only work with the beta API
https://blabber.getpostman.com
, and some only work with the production APIhttps://echo.getpostman.com
.
You might worry that you’ll need to exchange the endpoint URL all the time, but worry not; Environments to the rescue! There’s two JSON files in app/main/constants/
, which you will fill with the endpoints:
// env-dev.json
{
"API_ENDPOINT": "https://blabber.getpostman.com"
}
// env-prod.json
{
"API_ENDPOINT": "https://echo.getpostman.com"
}
Modify your controller code:
$http.get(Config.ENV.API_ENDPOINT + '/get')
.then(function (response) {
console.log(response);
});
Now, if you run one of these:
gulp watch
# or
gulp build
# or
gulp --cordova "run <platform>"
# or anything similar
Without any flags, it will default to the dev environment, and your HTTP calls will end up against the API_ENDPOINT
of your env-dev.json
: https://blabber.getpostman.com
. How? Because the environments task injects the contents of the JSON file in the ENV
object of your Config
constant:
angular.module('main')
.constant('Config', {
// gulp environment: injects environment vars
ENV: {
/*inject-env*/
'API_ENDPOINT': 'https://blabber.getpostman.com'
/*endinject*/
},
// ..
});
On the other hand, adding the --env=prod
flag like below will send your HTTP calls to https://echo.getpostman.com
, the API_ENDPOINT
of your env-prod.json
.
gulp watch --env=prod
YEAH!
What’s this sorcery? Environments come in handy for a variety of tasks: managing different API keys, tokens, logging levels, feature switches, and, as we just found out, API endpoints. And the best part: you can have as many environments as you want! A more detailed explanation of this incomprehensible, yet so appealing, piece of magic is available in our Environments Guide.
CORS & Proxying
Phew, we took care of that API-spaghetti. But wait, what’s that? Suddenly, something like this shows up in your development console as you send an HTTP request. And it is in radiant red!
XMLHttpRequest cannot load https://echo.getpostman.com/. The
'Access-Control-Allow-Origin' header contains the invalid value ''.
Origin 'http://localhost:3000' is therefore not allowed access.
Most of us have been there: “Hello, improperly configured REST API!”. And believe me, my team and I have been beaten by issues with CORS (Cross-Origin Resource Sharing) MANY times, so I wrote down a little guide on how to deal with CORS issues & Proxying in general. There are a variety of options, and depending in your project, some might work better than others.
One of the most straightforward ones, which is also covered in the guide I just mentioned, is the built-in proxy that comes with your Generator-M-Ionic project.
Remember that piece of code from earlier? This will actually run into a CORS error using that API endpoint:
$http.get('https://echo.getpostman.com/get')
.then(function (response) {
console.log(response);
});
In order to configure the built-in proxy, I’ll just start gulp watch
with the following parameters:
gulp watch --proxyPath=/proxy --proxyMapTo=https://echo.getpostman.com
And now, the following request to my proxy will give me the response from https://echo.getpostman.com/get
that I want.
$http.get('/proxy/get')
.then(function (response) {
console.log(response);
});
In fact, now I can send any request of the pattern /proxy/**/*
, and it’ll be mapped to https://echo.getpostman.com/**/*
through the proxy. This goes especially nicely with the generator’s Environments feature, because then you can just code it like this:
$http.get(Config.ENV.API_ENDPOINT + '/get')
.then(function (response) {
console.log(response);
});
With the contents of your environment JSON files being:
// env-dev.json
{
"API_ENDPOINT": "/proxy"
}
// env-prod.json
{
"API_ENDPOINT": "https://echo.getpostman.com"
}
And then switch between firing against the proxy or directly at the API using the --env
flag:
# will default to --env=dev and thus use the proxy
gulp watch --proxyPath=/proxy --proxyMapTo=https://echo.getpostman.com
# the built version will fire direclty against the API
gulp build --env=prod
As simple as that! No more manual, tedious, and error-prone changing of the endpoints.
Quite useful, isn’t it?
Default tasks
But yes, I get it, typing that whole thing also is kind of tedious:
gulp watch --proxyPath=/proxy --proxyMapTo=https://echo.getpostman.com
Especially if you add --env=prod
or --no-open
to the list, which I like to use to prevent the browser from opening a new window every time. Wouldn’t it be nice if my project just knew that I want those flags to run with gulp watch
? We thought so, too! So that’s why we built this neat little task called gulp defaults
. The syntax is fairly easy:
gulp defaults --set="watch --no-open --proxyPath=/proxy --proxyMapTo=https://echo.getpostman.com"
Now, every time I run gulp watch
, Gulp will add those three flags for me. Woohoo! It even reminds me which flags I’ve set on every run, so I don’t get confused.
Learn more about Gulp defaults and how to clear, overwrite, and share defaults across your team via Git in our Gulp defaults Guide.
Building and delivering your app
The first testable version of your app is ready. You’re proud, and the customer is eagerly waiting to test your app. The customer has the following requirements for the hand-over:
– deliver two apps
– one for each of the APIs and to not confuse the customer:
– a different app icon and splash screen for each version
– show the date and time of the build in the app
Generator-M-Ionic has tools for all of that.
Building your app
Building your app with Cordova is just as easy as running your app on a device:
gulp --cordova "build ios"
Remember that this implicitly runs gulp build
first and then triggers Cordova’s build command, as explained in our Development Introduction, and is the equivalent of running these two one after another:
gulp build
gulp --cordova "build ios" --no-build
However, for proper code-signing, it’s sometimes necessary to go with Cordova’s prepare command and then open the platform’s project located in platforms/
and perform the code-signing, building and all that, for instance, in Xcode when building for iOS.
gulp --cordova "prepare ios" # also runs `gulp build` first
Build for different APIs
Since we’ve already configured our environments properly, building for two different APIs is just as easy as developing for two different APIs. And we already know how to do that using gulp watch
and the --env
flag. To build one app for each environment is just as easy:
# build with dev environment
gulp --cordova "build ios" --env=dev
# build with prod environment
gulp --cordova "build ios" --env=prod
The --env=dev
flag is not necessary, since the environment always defaults to the dev environment with every command but I like to be explicit here to make it more obvious what’s happening.
Ok, done! Building different apps for each API. Next challenge!
Build with different app icons and splash screens
I’m not going to go into the particulars of this one, since there’s a perfectly nice Icons and splash screens Guide in our documentation. Whether you just have one set of icons and splash screens or ten, it’s fairly simple to set up and use. After proper configuration all you need to do in order to build with different resources is to add the --res
flag like so:
# build with dev set of resources
gulp --cordova "build ios" --res=dev
# build with prod set of resources
gulp --cordova "build ios" --res=prod
So, final frontier! How do we display the date and time of the build in our app?
Injecting variables from the command line
At some point, especially when you’re automating the build process of your apps by continuously integrating with Jenkins or Travis you might want to inject data into your app through the command line. Granted, showing the date and time of a build in the app is not a very useful use-case. In reality, you’re more likely to find yourself wanting to append build numbers into your app’s version number, which in turn is read from the config.xml
and inject both of them in the app. And guess what? There’s a guide for doing both of those things. But for now, to keep things simple and for the lack of a better example, we’ll just stick with injecting something simple, like a date.
You may have noticed that in your Config
constant is another block that looks similar to the one where the whole environment variables get injected. That block is exactly the place where you can inject variables into your app from the command line using gulp watch
and any tasks that uses gulp build
implicitly. See how:
# running $(date +%s) will only work on Unix
gulp watch --buildVars="buildTime:$(date +%s),buildNo:12"
Running the above command will produce this output in the Config
const:
angular.module('main')
.constant('Config', {
// gulp environment: injects environment vars
ENV: {
/*inject-env*/
'API_ENDPOINT': 'https://blabber.getpostman.com'
/*endinject*/
},
// gulp build-vars: injects build vars
BUILD: {
/*inject-build*/
'buildTime': '1462972664',
'buildNo': '12'
/*endinject*/
}
});
So you can map in one of your controllers:
this.build = Config.BUILD;
And using the Angular date filter in the matching template show the date and build number in your app:
<ion-item class="item item-icon">
<!-- multiply by 1000 to convert from unix to JS timestamp -->
{{ctrl.build.buildTime * 1000 | date:'medium'}}
</ion-item>
<ion-item class="item item-icon">
{{ctrl.build.buildNo}}
</ion-item>
Putting it all together
The only thing left to do is to merge all of what we just did for our build. Build the app with different environments, different sets of resources, and inject variables from the command line:
# build dev version
gulp build --env=dev --res=dev --buildVars="buildTime:$(date +%s),buildNo:12"
gulp --cordova "build ios" --no-build
# build prod version
gulp build --env=prod --res=prod --buildVars="buildTime:$(date +%s),buildNo:12"
gulp --cordova "build ios" --no-build
At this point, I like to explicitly separate the web app build from the Cordova build, so it doesn’t get messy, and it’s a little more apparent what we’re actually doing. Don’t forget the --no-build
option when running the Cordova part of your build, or it will implicitly run gulp build
and overwrite your previous gulp build
without any of the options you supplied earlier.
Deliver
Now that you have built two runnable apps, how do you get them to your customer for testing purposes?
- an easy way to do so is via the Ionic Platform services, once you’ve integrated them into your project using the Ionic CLI. See how, in our Ionic Platform Integration Guide
Bang!
***confettiandglitter rainingdownonyou***
You’ve done it!
You learned how to set up a project with Generator-M-Ionic, learned about its file structure, Git, quality assurance with linting & testing, sub-generators, Sass, Plugins, a lot of Gulp tasks, ecosystems, environments, CORS, proxies, build vars, and many many other things.
Sparkly future
We’re living in exiting times. Angular 2 and Ionic 2 are around the corner, Typescript and ES6 are becoming viable choices, and we’re on it! Would you like to see a version of Generator-M-Ionic with those technologies? What features would you like with that? Let us know!
Get in touch
Feedback, ideas, and comments regarding this blog post or any of the features discussed here are very welcome in either the comments section below, at our Generator-M-Ionic’s Github repository or the Generator-M-Ionic Gitter Chat.
Credits
Author: Jonathan Grupp
Headline illustrations: Christian Kahl
Special thanks to Volker Hahn, Mathias Maier, and Tim Lancina