Introduction
In this tutorial, I will explain you step by step how to create a modern, hybrid, mobile application (iOS and Android) of your WordPress website using the latest technologies. We'll be using Ionic Framework, ECMAScript 6, npm, webpack, and Apache Cordova.
At the end of this tutorial you will get the following application. It has only three modules, a Home module that displays your latest posts, a Post module that displays a specific post, and a Menu module that displays the menu.
1. Tools
Ionic Framework
The beautiful, open source front-end SDK for developing amazing mobile apps with web technologies.
Ionic Framework ecosystem is large, including Ionic CLI (command line tool), Ionic Push (easy push notifications), and Ionic Platform (backend services). It is currently one of the top open-source projects on GitHub with more than 19,000 stars and over 600,000 apps created.
Ionic covers all your application's needs. However, for this tutorial I will only focus on Ionic Framework (or Ionic SDK), which is a set of AngularJS directives (Web Components) and services.
ECMAScript 6 (ES6)
ECMAScript 2015 (6th Edition) is the current version of the ECMAScript Language Specification standard. ES6 got officially approved and published as a standard on June 17, 2015 by the ECMA General Assembly.
ECMAScript 6 gives you access to a lot of new features, many of which are inspired by CoffeeScript, including as arrow functions, generators, classes, and let scoping. Even though ES6 got approved recently, you can use it right now using a JavaScript compiler, such as Babel.
Node Package Manager (npm)
Node Package Manager is the most popular package manager in the world. The number of packages is growing faster than Ruby, Python, and Java combined. npm runs on Node.js.
Why Not Bower?
We opt for npm, because using both Bower and npm in the same project is painful and CommonJS support with Bower isn't straightforward. CommonJS defines a module format to solve JavaScript scope outside the browser and npm supports this. CommonJS modules can be required using ES5 or ES6.
// ES5 var angular = require('angular');
// ES6 import angular from "angular";
webpack
In my opinion, webpack has been a game changer in the industry, exit complicated Grunt or Gulp scripts that you need to maintain. webpack allows you to require any type of file (.js, .coffee, .css, .scss, .png, .jpg, .svg, etc.) and pipe them through loaders to generate static assets that are available to your application.
The difference with Grunt and Gulp is that the majority of your needs (minification and compilation) can be covered by just adding some configuration, there's no need to create scripts. For instance, requiring a Sass file, compiling it, autoprefixing it, and injecting the resulting minified CSS into your application will be as simple as this:
{ test: /\.scss$/, loader: "style!css!autoprefixer!sass" }
I don't think I need to show you the equivalent using Gulp or Grunt. I think you get my point.
2. Prerequisites
This tutorial assumes that you have:
- a basic knowledge of AngularJS and Ionic
- a WordPress website ready to be queried (a local installation is fine)
- a machine with Node.js, npm, Bower (we'll need it for some dependencies)
- Git installed with write access without sudo on the project folder
3. Installation
Before we get started, you will need to install two things:
- a WordPress plugin that turns your blog into a RESTFUL API
- the application itself
RESTFUL API
To fetch the posts for your WordPress installation, you will need to install WP REST API plugin. Make sure that you install version 1.2.x as version 2.x is on its way.
- In WordPress, go to Plugins > Add New.
- Search for WP REST API (WP API).
- Click Install Now to install the plugin.
- If the installation is successful, click Activate Plugin to activate it.
If the installation was successful, open a browser and enter http://example.com/wp-json. This should give you a response similar to the one below.
{ "name": "Lorem Ipsum blog", "description": "Just another WordPress site", "URL": "http://yourDomainName.com/wp-json", "routes": {}, "authentication": {}, "meta": {} }
Application
To install the application, clone the repository, using the following commands.
# Clone the repository and give it a name (here myTutorial) $ git clone https://github.com/tutsplus/Hybrid-WordPressIonicAngularJS.git myTutorial # Open the project $ cd myTutorial
Next, create a configuration file and install the dependencies.
# Copy the default config to your personal config $ cp config/default.config.json config/config.json # Install dependencies $ npm install
To make sure both the application and the REST API work together, open config/config.json. This is your personal configuration file, which is ignored by Git. Change the base URL of the API to the one for your WordPress installation.
{ "api": { "baseUrl": "http://yourDomainName.com/wp-json" } }
Run npm run devserver
and open http://localhost:8080/webpack-dev-server/ in a browser. If everything works as expected, you should be in front of a running application that displays your WordPress posts. I have created a demo application to give you an idea of what to expect.
Now that you can see the result of what we are after, let me go through the details. Note that the following code samples are simplified. You can find the source code on GitHub.
4. Dependencies
The npm install
command installed several libraries. Some of them are direct dependencies while the rest are development dependencies.
Direct Dependencies
The direct dependencies are dependencies that your application needs in order to run properly when built.
"dependencies": { "ionic-sdk": "^1.0.0", "wp-api-angularjs": "^1.0.0" }
Notice that the application doesn't directly depend on AngularJS, because ionic-sdk already includes angular.js, angular-animate.js, angular-sanitize.js, and angular-ui-router.js.
wp-api-angularjs (WordPress WP API client for AngularJS) is a set of AngularJS services that allow communication with the REST API plugin that you installed earlier. You can see the complete list of dependencies on GitHub.
Development Dependencies
Development dependencies are mostly webpack loaders. Loaders are functions that take the source of a resource file, apply some changes, and return the new source. We need loaders that handle .scss, .js (ES6), .html, and .json. You can see a complete list of development dependencies on GitHub.
5. Application Architecture
I have been developing AngularJS applications for a long time and after a lot of experimenting I have committed to the following architecture:
- a file that can be edited live under the
src/
or/lib
folder - every AngularJS module needs a proper folder
- every module file
*.module.js
must define a unique namespace (and be the only place where this namespace appears) - every module file
*.module.js
must declare all its dependencies (even if dependencies are already injected in the app) - every module file
*.module.js
must declare all its configs, controllers, services, filters, etc. - every config, controller, service, filter, etc. must export a function (CommonJS)
- if a module needs a specific style, the .scss file must live within the module
These recommendations are powerful as they assure that you to have loosely coupled modules that can be shared by several applications without running into problems.
This is what the application folder structure looks like:
lib/ ├── menu/ │ └── menu.module.js │ └── menu.html ├── home/ │ └── home.module.js │ └── home.config.js │ └── home.controller.js │ └── home.html ├── post/ │ └── post.module.js │ └── post.config.js │ └── post.controller.js │ └── post.html ├── scss/ │ └── _variables.scss │ └── bootstrap.scss ├── index.js ├── index.html
Entry Point
When using webpack, an entry point is necessary. Our entry point is lib/index.js. It contains our application's basic dependencies (such as ionic.bundle that contains AngularJS), our home-made modules, and adds the Sass entry point.
// Ionic, Angular & WP-API client import 'ionic-sdk/release/js/ionic.bundle'; import 'wp-api-angularjs/dist/wp-api-angularjs.bundle'; // Our modules import modHome from './home/home.module.js'; import modPost from './post/post.module.js'; import modMenu from './menu/menu.module.js'; // Style entry point import './scss/bootstrap';
Now that we have imported our dependencies we can create our application module. Let's call our app prototype. It has ionic
, wp-api-angularjs
, and our home-made modules as dependencies.
// Create our prototype module let mod = angular.module('prototype', [ 'ionic', 'wp-api-angularjs', modHome, modMenu, modPost ]);
Once the module is created, we can export it as a standard CommonJS module.
export default mod = mod.name;
This is a great example of what an AngularJS module should look like.
Routing
Our application has a side menu <ion-side-menu ui-view="menu">
in which the Menu module will be rendered. It also has a content section <ion-nav-view name="content">
in which the Home and Post modules will appear.
The ui-view
directive is part of the UI-router that Ionic uses. It tells $state
(UI-router service) where to place your templates. Similarly, the name
directive attached to <ion-nav-view>
is a custom Ionic directive that is using ui-view
underneath. You can consider both directives identical.
Here is a simplified version of the root
state, the state that all modules share:
export default function($stateProvider) { 'ngInject'; return $stateProvider.state('root', { abstract: true, views: { '@': { template: `<ion-side-menus><ion-side-menu-content><ion-nav-bar class="bar-positive"></ion-nav-bar><ion-nav-view name="content"></ion-nav-view></ion-side-menu-content><ion-side-menu side="left" ui-view="menu"></ion-side-menu></ion-side-menus>` } } }); }
For more information about named views, please refer to the documentation on GitHub.
Menu Module
lib/ ├── menu/ │ └── menu.module.js │ └── menu.html
The Menu module is very simple. Its purpose is to add a menu inside <ion-side-menu>
. Without this module, the side menu would be blank. The menu module declares only a config file, it has ionic
and ui.router
as dependencies.
import modConfig from './menu.config'; let mod = angular.module('prototype.menu', [ 'ionic', 'ui.router' ]); mod.config(modConfig); export default mod = mod.name;
The most interesting part is the configuration. We do not want to create a state for the Menu module as it is available everywhere. Instead, we decorate the root
state with the menu content. With the ui-view="menu"
being defined in the root
state, we need to use menu@root
to refer to it.
export default function($stateProvider) { 'ngInject'; $stateProvider.decorator('views', (state, parent) => { let views = parent(state); if (state.name === 'root') { views['menu@root'] = { template: require("./menu.html") }; } return views; }); }
Home Module
lib/ ├── home/ │ └── home.module.js │ └── home.config.js │ └── home.controller.js │ └── home.html
home.module.js
The Home module displays the latests posts of your WordPress website. It has a config file, a controller, and it depends on the following libraries:
ionic
ui.router
wp-api-angularjs
import modConfig from './home.config'; import modController from './home.controller'; let mod = angular.module('prototype.home', [ 'ionic', 'ui.router', 'wp-api-angularjs' ]); mod.config(modConfig); mod.controller('HomeController', modController); export default mod = mod.name
home.config.js
The config adds a new state, root.home
, with the /home
URL that has a template and a controller (both living within the module).
export default function($stateProvider) { 'ngInject'; $stateProvider.state('root.home', { url: "/home", views: { 'content@root': { template: require("./home.html"), controller: "HomeController as homeCtrl" } } }); }
home.controller.js
This is a simplified version of the Home controller logic. It contains two functions:
loadMore
: This function populatesvm.posts
. It uses the$wpApiPosts
service, which is part of the wp-api-angularjs library.refresh
: This function removes posts and callsloadMore
again.
export default function($scope, $log, $q, $wpApiPosts) { 'ngInject'; var vm = this; vm.posts = []; vm.loadMore = loadMore; vm.refresh = refresh; function refresh() { vm.posts = null; loadMore().finally(() => $scope.$broadcast('scroll.refreshComplete')); } function loadMore() { return $wpApiPosts.$getList().then((response) => { vm.posts = (vm.posts) ? vm.posts.concat(response.data) : response.data; $scope.$broadcast('scroll.infiniteScrollComplete'); }); } }
home.html
The template has a ion-refresher
directive that allows users to reload the page by pulling the page down. It also has a ion-infinite-scroll
directive that calls the loadMore
function when reached. Posts are displayed using the ng-repeat
directive.
Tip: Use the track by
expression for better performance. It minimizes DOM manipulation when a post is updated.
<ion-view><ion-nav-title>Home</ion-nav-title><ion-content><ion-refresher pulling-text="Pull to refresh" on-refresh="homeCtrl.refresh()"></ion-refresher><div class="list card" ng-repeat="post in homeCtrl.posts track by post.ID"><!-- THE POST DETAILS --></div><ion-infinite-scroll immediate-check="true" on-infinite="homeCtrl.loadMore()"></ion-infinite-scroll></ion-content></ion-view>
Post Module
lib/ ├── post/ │ └── post.module.js │ └── post.config.js │ └── post.controller.js │ └── post.html
The Post module displays only one post. It has a config file, a controller, and it depends on the same librairies as the Home module.
post.module.js
import modConfig from './post.config'; import modController from './post.controller'; let mod = angular.module('prototype.post', [ 'ionic', 'ui.router', 'wp-api-angularjs' ]); mod.config(modConfig); mod.controller('PostController', modController); export default mod = mod.name
Similar to the Home module, the config adds a new state, root.post
, with the /post/:id
URL. It also registers a view and a controller.
post.config.js
export default function($stateProvider) { 'ngInject'; $stateProvider.state('root.post', { url: "/post/:id", views: { 'content@root': { template: require("./post.html"), controller: "PostController as postCtrl" } } }); }
post.controller.js
The controller retrieves the post specified in the url /post/:id
via the $stateParams
service (UI router service).
export default function ($scope, $log, $wpApiPosts, $stateParams) { 'ngInject'; var vm = this; vm.post = null; $scope.$on('$ionicView.loaded', init); function init() { $wpApiPosts.$get($stateParams.id).then((response) => { vm.post = response.data; }); } }
post.html
The template has a ion-spinner
directive that displays a loader while the data is being fetched from the WordPress REST API. When the post is loaded, we use an Ionic card to render the author avatar, the post title, and the post content.
Tip: Use the bindOnce
expression, ::
, (introduced in Angular 1.3) to avoid watching data that will not change over time.
<ion-view><ion-nav-title>{{postCtrl.post.title}}</ion-nav-title><ion-content><ion-spinner ng-if="!postCtrl.post"></ion-spinner><div class="list card" ng-if="postCtrl.post"><div class="item item-avatar"><img ng-src="{{::postCtrl.post.author.avatar}}"><h2>{{::postCtrl.post.author.name}}</h2><p>{{::postCtrl.post.date | date:'medium'}}</p></div><div class="item item-body"><img class="full-image" ng-src="{{::postCtrl.post.featured_image.attachment_meta.sizes.medium.url}}"><h2>{{::postCtrl.post.title}}</h2><p ng-bind-html="::postCtrl.post.content"></p></div></div></ion-content></ion-view>
Style (Sass)
lib/ ├── scss/ │ └── _variables.scss │ └── bootstrap.scss
The bootstrap.scss
file that we imported in our entry point is as simple as this:
@import "./variables"; @import "~ionic-sdk/scss/ionic";
First, we import our variables. We then import the Ionic styles. Importing our variables before Ionic allows us to overwrite whatever Sass variables Ionic has declared.
For example, if you want the positive color to be red instead of blue, you can overwrite it like this:
$positive: red !default;
6. Android and iOS
Installation
Run the following commands inside the project folder and chose the platform you want to build for.
$ cp config.dist.xml config.xml $ npm run installCordova Which platforms do you want to build? (android ios):
In addition to installing platforms within the /platforms
folder, the script will install one plugin. For the demo, we need the cordova-plugin-whitelist
plugin. It is necessary to allow the application to query the WordPress REST API we created earlier.
If you open config.xml, you will see that we allow access to any kind of origin (<access origin="*" />
). Of course, this is only for demo purposes. If you deploy your application to production, then make sure you restrict access like this:
<access origin="http://example.com" />
Android
Prerequisites
- Android SDK
- Ant
Running the npm run runAndroid
command is a shortcut for rm -rf www/* && webpack && cordova run android
. This removes everything within the www folder, dumps a non-minified version of the app in it, and runs the android
command. If an Android device is connected (run adb devices
to make sure), the command will load the app on the device, otherwise it will use the Android emulator.
# Run Android $ npm run runAndroid
iOS
Prerequisites
- OS X
- Xcode
If you do not have an Apple device, you should install the iOS Simulator. It's really good and better than the Android emulator.
$ sudo npm install -g ios-sim
Running npm run runIosEmulator
is a shortcut for rm -rf www/* && webpack && cordova run ios
. The npm run runIosDevice
command is a shortcut for rm -rf www/* && webpack && cordova run ios --device
.
# Run iOS $ npm run runIosEmulator $ npm run runIosDevice
Conclusion
With this tutorial, I've tried to show you how easy it is to create a hybrid, mobile application for your WordPress website. You should now be able to:
- create loosely coupled modules that respect CommonJS specs
- import CommonJS modules with ECMAScript 6
- use the WordPress REST API client side (with wp-api-angularjs)
- leverage Ionic Framework to create a great user interface
- use webpack to bundle your application
- use Cordova to run the application on iOS and Android
If you want to go further, then take a look at a project I created few months ago, WordPress Hybrid Client.
WordPress Hybrid Client
WordPress Hybrid Client (WPHC) is an open-source project available on GitHub that helps you to create iOS and Android versions of your WordPress website for free. WPHC is based on the same technology stack that we used in this tutorial.
WPHC includes the following features:
- push notifications
- bookmarks (offline mode)
- Google Analytics support
- automatic content updates
- social buttons
- accessibility (post font size)
- multiple languages (English, French, Chinese)
- infinite scroll
- syntax highlighter for tech blogs
- image caching
- app rating