A boilerplate and Reference app providing tools and best practices to kickstart your app development
For background, read my blog post.
Release notes
Wiki
Introduction
Installation and usage
Gulp file
Project structure
Contribute
Starting with this version (released November 8, 2016), the project uses the Ionic CLI 2.x. This causes some BREAKING CHANGES if you try to upgrade to the new version of the quickstarter (it's not an issue of course if you start a new project using the quickstarter).
(the big advantage of using the Ionic 2.x CLI is that it supports not only Ionic 1 apps, e.g. the ionic-quickstarter, but also Ionic 2 apps - you can switch between Ionic 1 and Ionic 2 mode with a command line parameter)
The most important difference is that the configuration file ionic.project
has been renamed to
ionic.config.json
, and works slightly different. The quickstarter project has been modified so that it is
compatible with the new Ionic CLI (to do this, the gulp.js
file has been modified, among others).
The project has also been upgraded to use a newer Ionic framework version (1.3.x) and Angular version (1.5.x), I've fixed a number of issues on iOS (improved iOS compatibility), and I've added Crosswalk for better performance and compatibility on Android (note: this will make your APK approx. 20 MB bigger, but in my experience it's worth it).
For all of this to work properly, you should upgrade your nodejs
and cordova
versions, if you haven't
already. See this Wiki page for
details.
I've added two useful sections to the Wiki:
Please make a habit of consulting the Release Notes as these contain important info on new versions.
I intend to keep this starter app up to date with the newest versions of Ionic etc, and to add features/improvements. See Todo and Roadmap on the Wiki, and feel free to contribute.
NOTE: this starter app is based on Ionic 1.3.x and Angular 1.5.x.
The upcoming new Ionic 2 release will, in all likelihood, address 80-90% of the issues which this starter app aims to address (e.g. build system, SASS/styling, common Ionic 'gotchas' and so on). So, "post-Ionic 2" this starter (and most other starters) will probably lose a lot of their relevance.
Ionic Quickstarter is based on the "tabs starter" project from Ionic, but has the following extras:
- An improved gulp.js file (with a build process optimized for production, with template caching etc)
- Improved project structure (a modular app structure suitable for bigger apps)
- Application script files (Javascript) will be automatically included in your index.html by the gulp build process
- Per environment, you can define different values for constants (e.g. appKey and so on) which are then written into config.js by the appropriate gulp task
- Unit test support using Karma and Jasmine
- Typescript Definition Files ('tsd' files) which enable autocomplete/intelli-sense features when you use an IDE such as WebStorm or Visual Studio
- Signup and login flow implemented with Firebase (with the flexibility to add other implementations)
- Support for the two main Ionic UI patterns: side menus and tabs, and a Slider integration
- Includes some commonly used features, for instance form validation using ng-messages and improved logging
- For easier diagnostics and troubleshooting once your app is "out in the wild" (running on users' devices), you can use remote logging with Logentries (see description on the Wiki)
- Basic image support: Cordova camera, image cropping, storing images in local storage, and displaying images
- Supports internationalization (I18N) using the angular-translate library (currently only an English language file is supplied, it's easy to add other languages)
- Provides workarounds for a number of well-known issues in Ionic apps (swipe to close menu, hardware back button etc)
- Incorporates a number of 'best practices' for AngularJS and Ionic (e.g. "Controller as" syntax)
- A modular SASS setup including some handy utility styles and best practices for customization of colors, fonts etc.
- Reusable services and directives containing some commonly used utility functions that you can call in your app
- An 'appModule' utility function that makes managing your AngularJS modules slightly easier
- Currently based on Ionic v.1.3.x and AngularJS 1.5.x; tested on 3 devices: iPhone 4, Android smartphone, Android tablet
The first two topics (Project structure and Gulp file) will be explained below.
To keep this README short (it's already too long), I'm putting the rest of the information on the Wiki.
First, install nodejs
, cordova
and the Ionic command line tools, as described on the Ionic Getting Started page.
It is also recommended to have the following command line tools installed:
gulp
and bower
Check if these are installed, and if not install them 'globally' using npm
with the "-g" (global) flag.
See here for a detailed explanation on how to install these tools.
Note: I have recently upgraded the Quickstarter to work with the following versions of nodejs, Ionic CLI and Cordova, so these are the versions that I recommend because they work well together with the newer Ionic framework version (1.3.x) and Cordova plugins:
node v6.9.1
npm v3.10.8
ionic (CLI) v2.1.4
Cordova v6.4.0
For details see the "installation" page on the Wiki here.
Tip: instead of locally installing all of the above tools, an alternative may be to use the Ionic Box. This can be an attractive option, especially for Windows users.
Assuming that you have all of the above installed successfully, open a terminal and "cd" into the directory where you want to install your app.
Run the following commands (note: below the app is named myapp
, replace this by your app's name):
git clone https://github.com/leob/ionic-quickstarter mv ionic-quickstarter myapp cd myapp # Unless you want to contribute to development of ionic-quickstarter, REMOVE the Git repo now: rm -rf .git
If you want to put your app under source control and you use Git, type the command:
git init
NOTE: the next step (editing ionic.config.json and config.xml to change the app name) is OPTIONAL. You can skip this step and do it later (or not at all), if you want. If you want to do it, then edit the following two files using a text editor:
ionic.config.json config.xml
In these files, replace the name app
with the name you want to give to your app as follows:
- in
ionic.config.json
: at line 2 ("name": "app"), replaceapp
with your app name - in
config.xml
: at line 2, replacecom.ionicframework.app
with your package name (e.g.com.mycompany.myapp
), and at line 3 replaceapp
with your app name
Finish the installation by entering the following commands:
npm install bower install ionic state restore --plugins
Finally, if you want to add Android and/or iOS as a runtime platform, type one or both of the following commands:
ionic platform add android ionic platform add ios
The project is now ready to run. To run it in a browser, type (I advise to ALWAYS use the -c
option):
ionic serve -c
or any other variation, e.g. (using the "labs" feature, logging to the console, and defaulting to Chrome):
ionic serve -l -c --browser google-chrome
Click through the app: try the tabs, menus and so on.
If you click the menu item log out
then you will be presented with the login page. In
development mode this is a 'fake' login page. To log in, simply type an arbitrary email address (can be fake too), and
for the password type the text password
.
NOTE: if, after executing ionic serve
you get a blank page in your browser with the message
Error: ENOENT: no such file or directory ... index.html
then it indicates that the "gulp-inject" process wasn't
able to create an index.html file from the index-template.html file.
In this happens, I would advise you to run "ionic serve" with the arguments '-l' and '-c', so:
ionic serve -l -c
In some cases, the addition of "-l" in itself seems to be already enough to fix the problem (not sure about this).
However if the problem remains, then look at the messages in your console (terminal) window, which were enabled through the "-c" argument. Normally you should see an error message/stacktrace in the terminal/console which should tell you what is going wrong.
One possible cause for the above error seems to be the libsass bindings not found
error which was discussed
above. However as said above this error SHOULD not occur anymore since I've upgraded the gulp-sass
version.
I've set up the gulp file and the starter app in such a way that there are essentially 3 distinct 'modes':
- 'development' mode which is what you use when running "ionic serve" (running in the browser)
- 'test' mode: used when you run 'gulp test' or 'karma start'
- 'production' mode which is what you use with "gulp build" and "ionic run" (running on a device)
As I've currently set it up, these modes are quite different.
In development mode, the gulp build process is simple: no minification, concatenation etc.
By default, in development mode, the various services (signup, login etc) use a "mock" implementation with fake data (but you can easily override this through configuration parameters).
To define configuration parameters for development mode, add them to src/js/config/config-dev.json
.
The gulp
build process will write these values to src/js/config/config.js
.
In production mode (used on a real device), the gulp build process does a complete build including minification, concatenation etc, and the app runs with 'real' services.
(e.g. the Firebase service for signup/login, but you can replace this with an implementation of your own)
To define configuration parameters for development mode, add them to src/js/config/config-prod.json
.
The gulp
build process will write these values to src/js/config/config.js
.
Test mode (karma/jasmine) also uses the 'lightweight' build process and 'mock' services.
We also support end-to-end (E2E) testing, see here for details.
For more details on configuring and using development, test and production mode, see the Wiki.
Here is a warning for people who use the Ionic View app in conjunction with the ionic upload
command to test
their app (by the way, I don't really recommend using Ionic View, see my comments about it on the Wiki).
If you do an ionic upload
, then by default it will take your app from the src
folder, not from www
.
This is because ionic upload
takes the setting from the ionic.config.json
file.
So that would mean that you'd see a development build, not a production build, when viewing your app in Ionic View.
If you don't want this (i.e. if you want a production build in Ionic View) then you should (temporarily) change
src
to www
in ionic.config.json
, do a gulp build
and a ionic upload
, and then change
ionic.config.json back to src
.
Note that the same principles apply if you want to test a production build under ionic serve
(so not in the
Ionic View app but in a browser).
However in this case you need to take one extra step to prevent ionic serve
from overwriting your production
build: you need to run ionic serve
with the --nogulp
argument.
So the workflow then becomes:
- temporarily change
src
towww
inionic.config.json
- run the command:
gulp build
- run the command:
ionic serve --nogulp
- when you are done, change
ionic.config.json
back tosrc
In production mode (if you run on a device with ionic build
or ionic run
) then by default Firebase.com will
be used for login/authentication. This is because in "production mode" the settings in the config-prod.json
file
are used, which set devMode = false
and testMode = false
.
These flags, in turn, cause the user service to point to the Firebase.com implementation (see https://github.com/leob/ionic-quickstarter/blob/master/src/js/app/user/services/user.service.js to understand how this works).
If you want to run in production mode but do NOT want to use Firebase.com but another implementation (for instance the 'mock' implementation), then you can do this in two ways:
- modify the values in
config-prod.json
: if you setdevMode = true
then the "mock" user service implementation will be used - modify the code of https://github.com/leob/ionic-quickstarter/blob/master/src/js/app/user/services/user.service.js to make it use the implementation that you want (for instance the 'mock' implementation)
For more details on configuring Firebase, see the wiki.
The image functionality (taking a picture, cropping a picture, and so on) only works on a device, because it needs
Cordova, and camera hardware obviously. So, you will need to use 'production mode' (that is, gulp build
and
ionic run
or ionic build
).
As explained in the previous section, in production mode authentication will use the Firebase.com implementation by default.
If you do not want this, then you can change the values in the config-prod.json
file, or you can change
the code of https://github.com/leob/ionic-quickstarter/blob/master/src/js/app/user/services/user.service.js (see the
explanation above).
For more details on using the image functionality, see the wiki.
If you want to add a Javascript library (pre-made JS component) to the app, you will need to go through the following four steps.
(example used below: the "fus-messages" component, https://github.com/fusionalliance/fus-messages)
- Install it with bower, e.g:
bower install fus-messages --save
(this also updates your project'sbower.json
file). - Add the library path (e.g.
'./src/lib/fus-messages/dist/fus-messages.js'
) to yourgulp.js
file so thatgulp build
knows how to copy the library file(s) during the build. You need to add it to thelib
section of thepaths
variable ingulp.js
, and normally you would choose the minified version (ending in.min.js
) of the library. - Add the library path to your project's
index-template.html
file inside ascript
tag, for instance:<script src="lib/fus-messages/dist/fus-messages.js"></script>
- Finally, add the library's module name (e.g.
fusionMessages
) to the list of your app dependencies inside theapp.module
statement in your project'sapp.js
file.
Sometimes, after doing a production build/run (gulp build
followed by ionic build
or ionic run
) you
will see nothing but a blank page when starting the app on your device.
The most common cause is that you have the infamous AngularJS 'modulerr' error. To debug and fix this, please consult this Wiki page.
The gulp.js file supports the following commands:
gulp default
andgulp watch
gulp jshint
gulp test
andgulp test-single
gulp build
Here is how you use these commands.
Normally you don't run these commands manually. They will be executed automatically when you run ionic serve
.
This is done through a configuration section in the ionic.config.json
file:
"gulpStartupTasks": [
"default",
"watch"
]
You can run this to hint/lint your Javascript code. Just execute:
gulp jshint
Use these commands to run your tests via the Karma test runner.
gulp test
runs the tests and then keeps watching (and re-running) them until you abort the command, while
gulp test-single
runs the tests only once and then exits.
You use this command to generate a production build to run on a real device. Invoke it like this:
gulp build
ionic run
Replace ionic run
by ionic build
if you want to perform a build instead of a run.
To support bigger apps, the starter app is structured differently than the basic 'tabs starter app'.
The tabs starter app lumps all the route definitions and controllers in one Javascript file, and puts the html templates in a separate directory.
Instead, we've chosen to organize the files on a Module basis: each Module is in its own directory containing the Javascript (controllers etc) and the HTML (templates) for that Module. This makes it easier to keep a large app organized and maintainable.
As an example, here is the default structure (slightly simplified) after installing the starter app:
/
├── scss
│ └── ionic.app.scss
│
├── src
│ ├── css
│ │
│ ├── img
│ │
│ ├── js
│ │ ├── app
│ │ │ │
│ │ │ ├── auth
│ │ │ │ ├── forgotPassword
│ │ │ │ │ ├── forgotPassword.html
│ │ │ │ │ └── forgotPassword.js
│ │ │ │ │
│ │ │ │ ├── login
│ │ │ │ │ ├── loggedout.html
│ │ │ │ │ ├── login.html
│ │ │ │ │ ├── login.ctrl.js
│ │ │ │ │ ├── login.router.js
│ │ │ │ │ └── logout.ctrl.js
│ │ │ │ │
│ │ │ │ └── signup
│ │ │ │ ├── signup.ctrl.js
│ │ │ │ ├── signup.html
│ │ │ │ └── signup.router.js
│ │ │ │
│ │ │ ├── user
│ │ │ │ ├── models
│ │ │ │ │ └── user.js
│ │ │ │ │
│ │ │ │ └── services
│ │ │ │ ├── user.service.js
│ │ │ │ ├── mock
│ │ │ │ │ └── user.service.mockImpl.js
│ │ │ │ └── firebase
│ │ │ │ └── user.service.firebaseImpl.js
│ │ │ │
│ │ │ app.js
│ │ │
│ │ ├── config
│ │ │ ├── config-base.json
│ │ │ ├── config-dev.json
│ │ │ ├── config-prod.json
│ │ │ └── config.js [GENERATED]
│ │ │
│ │ ├── modules.js
│ │ │
│ │ └── templates.js
│ │
│ ├─── lib
│ │ ├── angular
│ │ ├── ionic
│ │ ├── ngCordova
│ │ └── firebase
│ │
│ ├ index-template.html
│ └ index.html [GENERATED]
│
└── www
The structure shown above is slightly simplified, but the idea is as follows.
The app's sources (Javascript, HTML, CSS) sit under src
instead of under the default location www
.
This is different than a 'standard' Ionic app, the reason is because of how the build process works.
During a production build (gulp build
), the sources (under src
) are minified and concatenated and so on and
the products (build artifacts, the minified/concatenated files) are then placed in the www
directory, where
Cordova (through the ionic run
or ionic build
process) will pick them up.
This setup keeps the sources under src
cleanly separated from the build artifacts under www
.
General principle: ONE DIRECTORY == ONE MODULE (and one subdirectory == 1 sub module).
So you can remove a module by removing that directory (but then you still need to remove the module reference from app.js - the script include in index.html will be removed automatically by the build process).
Example: in the structure shown above you can see two Modules: 'app.auth' and 'app.user'.
'app.auth' has 3 sub modules: 'app.auth.login', 'app.auth.signup' and 'app.auth.forgotPassword'.
'app.user' does not have sub modules.
Each (sub)module is an AngularJS module ('angular.module(...')), and each module is in its own directory containing all of the Javascript and HTML files making up that module.
In the example of the 'forgotPassword' module, you see that the directory contains 2 files: a template file (forgotPassword.html) and a Javascript file (forgotPassword.js). The Javascript file contains the route definition (UI-router $stateProvider) and the controller definition.
In the example of the 'login' module you see that the directory contains 5 files: 2 template files (login.html and loggedOut.html) and 3 Javascript files. In this case you see that we've put the route definitions into a separate file (login.router.js) and each of the two controllers also in separate files.
Whether or not to put the route definitions and controllers in one Javascript file or in separate files is up to you and will probably depend on the complexity of the code ('forgotPassword' is simple enough for all the Javascript code to be put into one file).
The 'app.user' module is a module that doesn't define controllers, routes or templates; it only contains services (and models). However, it is perfectly possible (and often logical) to have a module that contains everything: controllers, routes, templates, services and so on. An example of this is the 'app.mainPage' module (not shown above).
Note that during the production build process all of the separate files (Javascript and HTML) will be minified and concatenated into one Javascript file for efficiency reasons.
Note: apart from the directory structure (the 1 directory == 1 module principle), we've also introduced a file naming convention.
For instance:
Scripts defining in which controllers are defined are named 'feature.ctrl.js', where 'feature' is the name of the feature, e.g. "login".
Scripts defining services are named 'feature.service.js', and their implementations (of any) are named 'feature.service.mockImpl.js', 'feature.service.firebaseImpl.js'.
The naming scheme is probably quite intuitive, but you can find a full explanation in the Wiki: Naming conventions.
Services which can be reused across modules are in a separate directory services
.
A service (for instance the UserService) can have multiple implementations, for instance a "mock" implementation and a "Firebase" implementation. To illustrate, here is the code for userService.js:
angular.module('app.user')
.factory('UserService', function ($injector, APP) {
if ( APP.testMode) {
return $injector.get('UserServiceMockImpl');
} else {
return $injector.get('UserServiceFirebaseImpl');
}
});
Depending on "flags" set in the config.js (in this case, APP.testMode), the factory instantiates either a Mock
implementation or a Firebase implementation of the user service. These implementations are in subdirectories below the
service
directory.
Using this approach, service implementations can easily be changed or swapped out without the client code (controllers or other services) noticing anything.
The ability to run with 'mock' implementations makes it easy to develop quickly without having to perform a complicated production setup, and in test mode, the mock implementations makes running your unit tests a lot faster of course.
Contributions are welcome. For details, see the Contributing section on the Wiki.