The air is getting a little thin in App-Development-Orbit! Luckily Part 3 of this series about developing Ionic apps with Generator-M-Ionic 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 before we extend the project we made space-ready in part 2.
So you have your app all set up and have 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.
1$http.get('https://echo.getpostman.com/get') //$http service
2.then(function (response) {
3 console.log(response); //promise setup
4});
Yeah! As simple as that!
What if the customer changes the URL of the endpoint (that's the beta version of the API) to
https://blabber.getpostman.com
.
"OK, if that keep's 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.
1.constant('Config', {
2 API_ENDPOINT: 'https://blabber.getpostman.com'
3 // ..
4});
1$http.get(Config.API_ENDPOINT + '/get')
2.then(function (response) {
3 console.log(response);
4});
Genius!
The customer tells you that some 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
.
"This will be annoying! I will need to exchange the endpoint URL all the time!"
Worry not! Environments to the rescue!
There's two JSON files in app/main/constants/
which you will fill with the endpoints:
1// env-dev.json
2{
3 "API_ENDPOINT": "https://blabber.getpostman.com"
4}
1// env-prod.json
2{
3 "API_ENDPOINT": "https://echo.getpostman.com"
4}
Then modify your controller code:
1$http.get(Config.ENV.API_ENDPOINT + '/get')
2.then(function (response) {
3 console.log(response);
4});
Now if you run one of these:
1gulp watch
2# or
3gulp build
4# or
5gulp --cordova "run <platform>"
6# 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:
1angular.module('main')
2.constant('Config', {
3
4 // gulp environment: injects environment vars
5 ENV: {
6 /*inject-env*/
7 'API_ENDPOINT': 'https://blabber.getpostman.com'
8 /*endinject*/
9 },
10 // ..
11});
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
.
1gulp watch --env=prod
Cool, huh?
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. The best part is that 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.
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 bright red!
1XMLHttpRequest cannot load https://echo.getpostman.com/. The
2'Access-Control-Allow-Origin' header contains the invalid value ''.
3Origin 'http://localhost:3000' is therefore not allowed access.
"Hello, improperly configured REST API!" Believe me, my team and I have been beaten by issues with CORS (Cross-Origin Resource Sharing) MANY times.
So I created a little guide on how to deal with CORS issues & proxying. If you're dealing with CORS or proxying, it's a great read! There's a variety of options and depending in your project some might work better than others.
One of the most straight forward strategy, which is covered in my guide, 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:
1$http.get('https://echo.getpostman.com/get')
2.then(function (response) {
3 console.log(response);
4});
In order to configure the built-in proxy, I'll just start gulp watch
with the following parameters:
1gulp 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.
1$http.get('/proxy/get')
2.then(function (response) {
3 console.log(response);
4});
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 fits nicely with the generator's Environments feature:
1$http.get(Config.ENV.API_ENDPOINT + '/get')
2.then(function (response) {
3 console.log(response);
4});
With the contents of your environment JSON files being:
1// env-dev.json
2{
3 "API_ENDPOINT": "/proxy"
4}
1// env-prod.json
2{
3 "API_ENDPOINT": "https://echo.getpostman.com"
4}
And then switch between firing against the proxy or directly at the API using the --env
flag:
1# will default to --env=dev and thus use the proxy
2gulp watch --proxyPath=/proxy --proxyMapTo=https://echo.getpostman.com
3
4# the built version will fire directly against the API
5gulp build --env=prod
As simple as that! No more manual, tedious, error-prone changing of the endpoints.
Quite useful, isn't it?
Well just typing that whole thing also is kind of tedious:
1gulp watch --proxyPath=/proxy --proxyMapTo=https://echo.getpostman.com
Especially if you add --env=prod
or --no-open
to the list. The latter 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! That's why we built this neat little task called gulp defaults
.
The syntax is fairly easy:
1gulp 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. Yes! It even reminds me which flags I've set on every run, so I don't get confused.
Neat! Learn more about Gulp defaults, how to clear, overwrite and share defaults across your team via Git in our Gulp defaults guide.
The first testable version of your app is ready. The customer has the following requirements for the hand-over:
Good thing you're using Generator-M-Ionic, which has nice tools for all of that!
Building your app with Cordova is just as easy as running your app on a device:
1gulp --cordova "build ios"
Remember that this implicitly runs gulp build
first and then triggers Cordova's build command, as explained in our Development Introduction. This command above is equivalent to the following statements:
1gulp build
2gulp --cordova "build ios" --no-build
However, for proper code-signing, it sometimes is necessary to go with Cordova's prepare command. 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:
1gulp --cordova "prepare ios" # also runs `gulp build` first
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. Luckily, building an app for each environment is just as easy:
1# build with dev environment
2gulp --cordova "build ios" --env=dev
3
4# build with prod environment
5gulp --cordova "build ios" --env=prod
The --env=dev
flag is not necessary, since the environment always defaults to the development environment with every command. However, I like to be explicit here to make the code more readable.
Ok, done with building apps for different APIs! Next challenge!
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 them up and use them.
After proper configuration, all you need to do to build with different resources is add the --res
flag. Check this out.
1# build with dev set of resources
2gulp --cordova "build ios" --res=dev
3
4# build with prod set of resources
5gulp --cordova "build ios" --res=prod
Our code now builds with different icons and splash screens, as defined in the dev and prod environments.
Next, how do we display the date and time of the build in our app?
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 will probably 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 build numbers and version number into the app.
Guess what? There's a guide for doing both of that. But for now to keep things simple, we'll just stick with injecting a date.
You may have noticed that in your Config
constant is another block that looks similar to the one where the environment variables get injected. That block is the place where you can inject variables into your app from the command line using gulp watch
and inject any tasks that use gulp build
implicitly. See how:
1# running $(date +%s) will only work on Unix
2gulp watch --buildVars="buildTime:$(date +%s),buildNo:12"
Running the above command will produce this output in the Config
const:
1angular.module('main')
2.constant('Config', {
3
4 // gulp environment: injects environment vars
5 ENV: {
6 /*inject-env*/
7 'API_ENDPOINT': 'https://blabber.getpostman.com'
8 /*endinject*/
9 },
10
11 // gulp build-vars: injects build vars
12 BUILD: {
13 /*inject-build*/
14 'buildTime': '1462972664',
15 'buildNo': '12'
16 /*endinject*/
17 }
18
19});
Now, you can map in one of your controllers:
1this.build = Config.BUILD;
Using the Angular date filter in the matching template, show the date and build number in your app:
1<ion-item class="item item-icon">
2 <!-- multiply by 1000 to convert from unix to JS timestamp -->
3 {{ctrl.build.buildTime * 1000 | date:'medium'}}
4</ion-item>
5<ion-item class="item item-icon">
6 {{ctrl.build.buildNo}}
7</ion-item>
The only thing left to do is to merge whatever we just did for our build: build the app with different environments and different sets of resources, and inject variables from the command line:
1# build dev version
2gulp build --env=dev --res=dev --buildVars="buildTime:$(date +%s),buildNo:12"
3gulp --cordova "build ios" --no-build
4
5# build prod version
6gulp build --env=prod --res=prod --buildVars="buildTime:$(date +%s),buildNo:12"
7gulp --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 are actually doing. Do not forget the --no-build
option when running the Cordova build or it will implicitly run gulp build
and overwrite your previous gulp build
without any of the options you supplied earlier.
Now that you have built two runnable apps, how do you get them to your customer for testing purposes?
Pick the one that suits you best, and get that customer feedback!
*confettiandglitter rainingdownonyou*
You've done it! Big and warm-hearted congratulations!
You fought yourself all the way through adventure-island's toddler-playground, grabbed your development pick-axe and dragged yourself on top of that mountain, only to jump in that space suit and explore app-orbit! 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.
Stay curious, there's many more things to explore and don't forget to firmly pat yourself on the back! You have earned it!
We're living in exiting times; Angular 2 and Ionic 2 are around the corner, and Typescript and ES6 are becoming viable choices. 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!
Feedback, ideas, comments regarding this blog post or any of the features discussed here are very welcome at our Generator-M-Ionic's Github repository, or the Generator-M-Ionic Gitter Chat.
Author: Jonathan Grupp
Headline illustrations: Christian Kahl
Special thanks to Volker Hahn, Mathias Maier & Tim Lancina
Originally published July 7, 2016 on the Ionic Blog in a slightly modified version.