WordPress and docker – Using phpmyadmin with docker and wordpress and mysql

This is a bonus post. I got curious about running a phpmyadmin instance and realised it was easy to get running.

If you’ve gone through my previous posts:

Or perhaps you already have an instance of wordpress running and a database that has persistence set up. Now you want to add a mysql administration tool into the mix.

For this I did not go out of my way to write a full docker-compose.yml file. I only wanted a phpmyadmin instance running locally for testing any wordpress development.

To do this I ran:

1
2
3
4
$ docker run
    --name myadmin
    --network dockerwordpress_default -d
    --link dockerwordpress_db_1:db -p 8080:80 phpmyadmin/phpmyadmin

The steps involved

The important part is making sure you link to your db and that you run the phpmyadmin container on the same network as the db.

First see what the name of your db container is:

1
$ docker ps

You should something like this:

1
2
3
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
81ba5a434e49 wordpress:latest "docker-entrypoint..." 23 hours ago Up 23 hours 0.0.0.0:8000->80/tcp dockerwordpress_wordpress_1
549f46a13b6b mysql:5.7 "docker-entrypoint..." 23 hours ago Up 23 hours 3306/tcp dockerwordpress_db_1

Your mysql container is the important one for this post. Copy the name, in my case it is “dockerwordpress_db_1”.

Check your networks:

1
$ docker network ls

You should see something like this:

1
2
3
4
5
NETWORK ID NAME DRIVER SCOPE
1db6773f4c13 bridge bridge local
938a9daa793d dockerwordpress_default bridge local
d8e0c5970cdb host host local
40e39b778c65 none null local

In my case I would use “dockerwordpress_default” as the network for my phpmyadmin container.

Using docker run

Now you know enough to run the command that I showed at the start of the post:

1
2
3
4
$ docker run
    --name myadmin
    --network dockerwordpress_default -d
    --link dockerwordpress_db_1:db -p 8080:80 phpmyadmin/phpmyadmin

Explanations of the important parts:

–link dockerwordpress_db_1:db

dockerwordpress_db_1:db this is pointing to the network “dockerwordpress_db_1” and then linking to the mysql database service which you would have started in this post: Running wordpress and mysql using docker compose

–network dockerwordpress_default

dockerwordpress_default You got this when you ran docker network ls above.

Viewing phpmyadmin

If your phpmyadmin container started up successfully you should now be able to connect to it on port 8080.

If your docker is linked to your localhost:

http://localhost:8080

Alternativelyl find your ip by running:

1
$ docker-machine ip

Then

http://yourdckerip:8080

Login to phpmyadmin using the details you used in the docker configurations:

Eg

1
2
MYSQL_USER=wordpress
MYSQL_PASSWORD=wordpress

WordPress and docker – Developing wordpress themes using docker volumes

As promised, here is part 2 of my docker & wordpress posts. Here is how you could develop wordpress themes using docker.

I’m going to quickly run through how to use docker to do wordpress theme development and not loose any changes when you stop docker of switch off your computer.

In order to make sure data persists even when your docker container is no longer running, you need to set up a volume.

You can read the official docker docs here: https://docs.docker.com/engine/admin/volumes/volumes/

I tweaked the docker-compose file below to map the container’s “wp-content” folder to your current working directory you have your docker-compose.yml file.

version: '3'

services:
   db:
     image: mysql:5.7
     volumes:
       - ./db_data:/var/lib/mysql
     restart: always
     env_file: 
       - ./.env

   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     volumes:
       - ./wp-content:/var/www/html/wp-content
     ports:
       - "8000:80"
     restart: always
     env_file: 
       - .env

volumes:
    db_data:
    wp-content:

You can view your wordpress site in your browser by going to:

1
http://localhost:8000

or if you need to use an ip check your docker-machine ip by running:

1
$ docker-machine ip

See the previous post for how to set up the .env file.

You should see two directories listed in the same directory you have your docker-compose.yml file in.

1
2
db_data
wp-content

This is not 100% ideal. For simplicity I’ve set up these folders in the same directory. I will explain in more detail why at the end of the post.

Creating your wordpress theme with docker volume

Now that you have a persistent volume set up you can start to tweak the theme.

You will find the themes in the ./wp-content/themes folder.

In the admin (go to http://localhost:8000/wp-admin, or http://yourmachineip:8000/wp-admin) you will see listed the themes in the Appearance – Themes section.

You can easily delete themes you don’t want, and refresh and see they will no longer be listed in the Themes section in the admin.

You can add new themes and make changes to existing themes and just refresh and you will see the changes in the browser.

A better way to handle volumes:

So I mentioned above, you would not want your volumes necessarily within the same folder as your container files that you have now containerized using docker.

For one, you will want to make your final work into an image with a tag and run the image on a production ready server.

Docker-compose is for development, and should not be your final go live strategy.

Your volumes you should also either point to another container or a safe secure place on your computer / server.

If we’re thinking big picture, and thinking about the deployment part. You will have your docker-compose files separate from your projects, but maybe for simplicity sake you decide to keep them with your project files, that’s fine, but make sure you consciously think about why you want them there, what are the benefits of that structure (I’m just hypothetically asking)?

You should always have your volumes in a safe place, perhaps a dedicated server space that has recovery tactics in place like regular backups, mirrored, clustered. There are many ways to tackle secure, fail safe voluming. Make sure if you are planning a project that is for a client project that will go into the world, that you have planned ahead the entire deployment ecosystem.

A badly managed volume becomes a single point of failure, and one of the main 101s of cloud infrastructure and proper devops thinking should be to remove as many single points of failure as possible.

For the purpose of this blog post though, I’ve kept all of that out. My aim is to allow you to test the concept of wordpres and voluming with as little effort as possible.

I will create a follow up post on how to create a docker image of your code, push your tweaked code into an image, and then you can run from a fully packaged image rather than from a folder.

You’re now dabbling in the realm of containers, so you should be thinking in “image” and moving away from the thought process of “I have x amount of files to push to a server”, but like I say, I’ll touch on that in a follow up post.

Enjoy 🙂 May you experience lots of success in your containerizing.

Just a note on some struggles I had:

In order to test the volumes for this post, I delete the volumes a lot and restarted my computer. I shut down the containers and started them. I really tried to break the volumes. For the most part the volumes persisted well, I had to purposely delete the volume and all the files in order to stop it from persisting.

While I did all that my environment started to lag and I noticed the wordpress container sometimes started up before the database container, and then if I tried to load the wordpress site it did not show up. Since the wordpress container ran before the database was properly configured, I had to rerun the wordpress container to get it to connect with the database container again.

 

Docker and WordPress: Running wordpress and mysql using docker compose

Curious about how to work with docker and wordpress? Here is a quick walk through on how to use docker to run wordpress and mysql using docker compose.

In the next post I’ll show how you can can volume the themes and edit themes in your development process. And in a follow up post I will show how to run your own created wordpress image in kubernetes and in minikube. 🙂

You can get a fairly generic yml file from the official docker website:

https://docs.docker.com/compose/wordpress/

I took the configurations and made a few small tweaks:

docker-compose.yml

version: '3'

services:
   db:
     image: mysql:5.7
     volumes:
       - ./db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: mysql_root_password
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress

   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     ports:
       - "8000:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: wordpress
     env_file: ./.env

volumes:
    db_data:

The above configuration will set up wordpress and mysql with the variable values in the “environment” sections. If will map wordpress from the interal port 80 to external port 8000. It also creates a volume mount from the internal location of  “/var/lib/mysql” to the external location of “./db_data”. This will help with persistent data storage (you won’t loose your data if your container stops).

By “internal” I mean within the container’s environment and by “external” I mean the environment running the docker container, in my case this would be my mac.

Then run the docker compose script:

1
$ docker-compose up -d

Check the docker images are running:

1
$ docker ps

You can then view the wordpress install by checking your docker ip and then pointing to the external port (in this case it will be 8000).

Get your docker’s ip:

1
$ docker-machine ip

The first time you run this, you will be asked to fill in some details about your wordpress blog. The next time you run it, it should all be prepopulated (provided you haven’t deleted your “db_data” folder).

You can also test that the data is persistent by delete the docker containers and images and then downloading the images again and running the docker-compose script. If everything starts up the same way you left it, then your persistent data is working.

I made improvements to the script by pulling out the environment variables into a .env file. This will help make the containers for customizable. I’ve commented out the environment variables in the docker-compose file so you can see what they were.

docker-compose.yaml (v2)

version: '3'

services:
   db:
     image: mysql:5.7
     volumes:
       - ./db_data:/var/lib/mysql
     restart: always
     env_file: ./.env

   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     ports:
       - "8000:80"
     restart: always
     env_file: ./.env

volumes:
    db_data:

Your .env file:

MYSQL_ROOT_PASSWORD=mysql_root_password
MYSQL_DATABASE=wordpress
MYSQL_USER=wordpress
MYSQL_PASSWORD=wordpress

WORDPRESS_DB_HOST=db:3306
WORDPRESS_DB_USER=wordpress
WORDPRESS_DB_PASSWORD=wordpress

To test

Check if your .env variables imported correctly by running the docker exec command:

1
docker exec -it CONTANER-ID bash

Then inside the container you can run:

1
echo $WORDPRESS_DB_USER

If that returns just an empty line, you need to just check the env_file settings and the contents of the file.

Check your container id by running:

1
docker ps

Some useful scripts

Stopping all containers (this will stop ALL, so use with caution):

1
$ docker stop $(docker ps -aq)

Removing all containers (use with caution, this will remove all containers):

1
$ docker rm $(docker ps -aq)

Deleting all images (use with caution! this will remove all images):

1
$ docker rmi $(docker images -q)

Github

View this code on github:
https://github.com/CariZa/DockerComposeWordpress

Test driven development TDD best practices and notes

I was asked to discuss test driven development with our teams and did a bunch of research to share with everyone. Below is what I presented.

Focusing on unit tests, other layers of TDD is:
– regression tests (when things are changed old bugs don’t come back)
– integrations tests (multiple steps, a flow of logic)
– e2e tests (a user journey flow from start to finish)

Research the layers of tdd you could find useful tests to help add to a project’s stability.

The argument for routine

Make TDD a part of your coding routine. The initial setup of the tests might take some TLC and time, but once that is done you should rarely have to do much new development. Once TDD becomes habit it becomes easier to to do.

The below is a combination of online research, experience and advice given by Robert C. Martin in Clean Code.

The 3 laws of TDD:

1. You have to write a failing unit test before you write any code.
2. You must not write more than was is sufficient to make the test fail.
3. You must not write more production code than what is sufficient to make the test pass.

Structure of a test:

Build, Operate, Check or “Arrange, Act, Assert,” (sometimes there’s a 4th step – teardown – cleans after test ends – most frameworks should handle that for you)

1. Build / Arrange

What data do you need? What stubs should you call in? On backend you might want to create a fake model to use.

2. Operate / Act

Usually: Call a method. Or pass values into a method.

3. Check / Assert

Check the result of the Operate / Act step. Is it what you expected?

Make sure the tests are clean

Make sure you give as much thought to the quality of your tests as you do for the quality of your production code. If you let your tests rot, your code will rot.

Clean tests = readability; clarity, simplicity, density.

Guidelines:

One assert per test. Same way that in your code you look at DRY and single responsibility – if you have to assert multiple things in a test relook your code, you are probably doing too much in the actual code of method you are testing.

Test a single assertion.

FIRST

Clean tests follow with 5 FIRST rules, as mentioned in Clean Code:

Fast

– runs quickly, you don’t want to wait 10 minutes for the tests to run, no one will run the tests. The ideal situation is that the tests run continuously while you are coding.

Independent

– one test should not rely on another test running

Repeatable

– You should be able to run tests in staging, prepared, prod. You should not hard code tests to a specific environment.

Self-Validating

– No manual interpretation needed (human eyes don’t define if its passing or not)

Timely

– Should be written before your code.

Avoid

Be aware of these and avoid them

Tightly coupled tests = harder to refactor code.
Tests that depend on other tests to run.
Too many assertions in one test.
Testing other parts of the code that are outside of the method you are testing.

Remember: Test rot = code rot. Keep tests clean.

TDD should be easy and quick. The first warning sign that you are doing incorrect tests and not following best practice is if they take a lot of time.

Goal of TDD

Write tests that “have your back”.
Tests should be quick and easy.
Forces you to write neater code. Code quality goes up. Bugs go down. Rewrites become less, refactoring over rewrites.

Using python docx to create word documents

Python Docx

If you need to create a word doc on a server I recommend using the python package “docx”.

To install docx run

1
pip install python-docx

You can read through the official documentation here:

http://python-docx.readthedocs.io/en/latest/user/install.html

Styling your template

A useful thing to do is to style your template in microsoft word and then save the template and use it as your starting document.

eg

1
document = Document("document_path.docx", "path_to_template.docx"))

Some tricks I have learnt while using docx

First you create your document:

1
document = Document("path/doc.docx")

Then you add elements to your document using the following

  • add_paragraph
  • add_picture
  • add_page_break
  • add_heading
  • add_table
  • add_row

Examples

1
document.add_paragraph('Hello World'

Adding a table

1
2
3
4
table = document.add_table(rows=1, cols=5)
row = table.add_row().cells
index = 0
row[index].add_paragraph('Hello World')

XML

To view the xml of an element use element.xml

Eg

1
2
p = document.add_paragraph('Hello World')
p.xml

Advanced

I ran into a tricky part where I wanted to add content to the middle of an existing document with existing content.

I added this copy to the document “[BOOKMARK HERE]”

And then I used this code to search for that and then to add elements after that copy.

Add a paragraph after a paragraph, or a heading after a paragraph:

1
2
3
4
5
def addParagraphAfterParagraph(self, document, paragraph, bookmark = '[BOOKMARK HERE]'):
  for para in document.paragraphs:
    if para.text == bookmark:
      p = para._p
      p.addnext(paragraph._p)

To add a table after the copy I had to use something like this:

1
2
3
4
5
def addTableAfterParagraph(self, document, table, bookmark = '[BOOKMARK HERE]'):
  for para in document.paragraphs:
    if para.text == bookmark:
      p = para._p
      p.addnext(table._tbl)

To add an image after that copy I had to use something like this:

1
2
3
4
5
6
7
def addImageAfterParagraph(self, document, image_path, width, bookmark = '[IMAGE HERE]'):
  for para in document.paragraphs:
    if para.text == bookmark:
      p = para._p
      para.text = ''
      run = para.add_run()
      run.add_picture(image_path, width=width)

Also remember you have to run these in reverse because you’re adding it after an element, so it will place before the next element each time and immediately after the copy.

 

Using scp to copy from a server and to a server

Copy files from the server to your pc

You need ssh access, view this post to do that:

Add ssh keys and ssh access to a linux server

The command to scp from server to your computer

1
sudo scp -i ~/.ssh/id_rsa user@server:A B

eg

1
sudo scp -i ~/.ssh/id_rsa user@server:/the/filepath/on/server/**/* /filepath/of/computer

/**/* refers to all files and subfolders

Copy files from your pc to the server

The normal way to scp, where you copy files from your computer to the server

1
scp -r A user@server:B

eg:

1
scp -r /path/on/your/computer/ user@server:/path/to/server/destination

-r means recursive (so all files and sub folders)

Wildcards

You can use wildcards to match with a file extension

1
eg *.php

That will copy all php files only.

Javascript built in array methods ES5 and ES6

Inspired by a Sitepoint article:

https://www.sitepoint.com/filtering-and-chaining-in-functional-javascript/

They highlighted built in javascript array methods. I decided to explore two ES5 and ES6 built in array methods; filter and map.

Javascript Array Filter ES5 and ES6

The filter method lets you easily filter an array using a callback style approach. Your callback handles the condition, the filter loops through your array and lets you apply the condition to each value in the array.

1
array.filter(function)

// ES5

1
2
3
4
5
6
7
var list = ['oranges','apples','pears'];
var filteredList = list.filter(
function(value) {
// condition
return value.length <= 5;
}
)

// Result: filteredList = [“pears”]

// ES6

1
2
3
4
let list = ['oranges','apples','pears'];
let filteredList = list.filter(
value => value.length <= 5
)

// Result: filteredList = [“pears”]

Javascript Array Map ES5 and ES6

Use map to loop through elements of an array, on each loop you can create a new element that is appended to a new array. The result of map is a new array, based on the return value in the function in the map declaration.

1
array.map(function)

// ES 5

1
2
3
4
var list = ['oranges','apples','pears'];
var filteredList = list.map(function(value) {
return value.length;
})

// result: filteredList = [7, 6, 5]

// ES 6

1
2
3
4
let list = ['oranges','apples','pears'];
let filteredList = list.map(
value => value.length
)

// result: filteredList = [7, 6, 5]

Chaining javascript array methods; filter and map

As mentioned in the article, a great feature to these array methods is the ability to chain them:

// ES 5

1
2
3
4
5
6
7
8
9
var list = ['oranges','apples','pears'];
var filteredList = list.filter(
function(value) {
// condition
return value.length <= 5;
}
).map(function(value) {
return value.length;
})

// result: filteredList = [5]

// ES 6

1
2
3
4
5
6
let list = ['oranges','apples','pears'];
let filteredList = list.filter(
value => value.length <= 5
).map(
value => value.length
)

// result: filteredList = [5]

Conclusion

There are many more powerful lightweight built in javascript methods that are worth exploring. Taking the time to learn them can help save time and effort.

Ionic 2: Using tabs to navigate

This is a tutorial focusing on adding ionic tabs manually to an ionic 2 project.

Some thoughts on ionic tabs

Think of the tabs as a wrapper, and any pages you want tabbed have to be added to this wrapper.

You don’t add tabs to your pages, you add your pages to your tabs.

Let’s get started…

Here’s a quick little lightweight tutorial. This will help you add tabs to an ionic app that does not already have tabs.

You won’t need much knowledge of typescript and angular 2, but it is advised that you have a basic understanding of these in order to develop with Ionic 2.

You will definitely need to know how to use your terminal/command tool, in order to run the commands to run ionic 2.

You can always skip this tutorial but taking a useful shortcut and just create an ionic app with tabs by running:

1
ionic start [yourAppName] tabs --v2

If you get stuck in a situation like me, where the project you found yourself working on did not already come with tabs, then continue below.

Setup

Make sure you have:

Ionic 2 set up (not Ionic 1)

Follow this

https://ionicframework.com/docs/v2/setup/installation/

Scaffold a blank ionic 2 app

To create your first blank ionic app run

1
ionic start navigation-app blank --v2

NB: Remember the —v2 else you will be creating an ionic 1 application.

You should get this response

  Your Ionic app is ready to go!

Run your application

Navigate into your new application’s folder:

1
cd navigation-app/

Test that your application is working

Run

1
ionic serve

This should open up a browser window and display your newly created mobile application. It will be just a blank page with a grey header at the top that says “Ionic Blank” and some copy on the page below saying something like “The world is your oyster”.

If that does not happen, you may need to investigate your ionic 2 installation. And you may need to just check your start command you just ran.

You should have the following folder in the root of your application:

1
/src

This is the folder you want to do your development in. Do not do changes in a /www folder.

Add the tabs navigation

Generate a tabs navigation component. I personally would not add it as a page, because it is technically not a page. You can add it as a page, I just like my code to be semantic, if I go to the pages folder, every ts file in there should be a page. I don’t want to guess what I am working with.

Use the handy ionic 2 generator to auto generate the component:

Note: use Pascal case for the name of your component in the command line/terminal:

1
ionic g component MyTabs

This will generate a folder called “my-tabs”.

In the components folder of your ionic 2 application.

1
2
3
4
5
6
7
8
9
10
11
src/

    components/

        my-tabs/

            my-tabs.html

            my-tabs.ts

            my-tabs.scss

Note, the .scss file is your component’s SASS file, editing the SASS file is not part of this tutorial’s scope.

Important!

Add to /src/app/app.module.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import { NgModule, ErrorHandler } from '@angular/core';

import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';

import { MyApp } from './app.component';

import { HomePage } from '../pages/home/home';

import { MyTabsComponent } from '../components/my-tabs/my-tabs';

@NgModule({

  declarations: [

    MyApp,

    HomePage,

    MyTabsComponent

  ],

  imports: [

    IonicModule.forRoot(MyApp)

  ],

  bootstrap: [IonicApp],

  entryComponents: [

    MyApp,

    HomePage,

    MyTabsComponent

  ],

  providers: [{provide: ErrorHandler, useClass: IonicErrorHandler}]

})

export class AppModule {}

The important parts:

1
import { MyTabsComponent } from '../components/my-tabs/my-tabs';

and adding MyTabsComponent to “declarations” and “entryComponents”.

Update the landing page

Updating the landing page to be your MyTabs component:

So lets say you want the first page to be this tabs navigation that we are creating. In order for that to happen we need to edit app.component.ts in your /src/app folder:

You will replace Home with MyTabs.

Make sure it has the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { Component } from '@angular/core';

import { Platform } from 'ionic-angular';

import { StatusBar, Splashscreen } from 'ionic-native';

import { MyTabsComponent } from '../components/my-tabs/my-tabs';

@Component({

  templateUrl: 'app.html'

})

export class MyApp {

  rootPage = MyTabsComponent;

  constructor(platform: Platform) {

    platform.ready().then(() => {

      // Okay, so the platform is ready and our plugins are available.

      // Here you can do any higher level native things you might need.

      StatusBar.styleDefault();

      Splashscreen.hide();

    });

  }

}

The important parts:

1
import { MyTabs } from './components/my-tabs/my-tabs';

and

1
<span class="Apple-converted-space">  </span>rootPage = MyTabs;

If you get an error saying No component factory found for

You need to add the component declaration to app.module.ts as specified above.

Your application will now refresh and you will see just “Hello World”, we need to add our tabs structure to the MyTabs component. Or a black screen, it’s ok, don’t panic, we need to edit some more stuff, carry on below.

Add the tabs navigation

Have a look at the ionic 2 tabs documentation:

https://ionicframework.com/docs/v2/api/components/tabs/Tabs/

You will see you need this in your MyTabs class in my-tabs.ts:

1
2
3
4
5
<span class="Apple-converted-space">  </span>tab1Root = Page1;

<span class="Apple-converted-space">  </span>tab2Root = Page2;

<span class="Apple-converted-space">  </span>tab3Root = Page3;

And in your my-tabs.html:

1
2
3
4
5
  tab1Root = Page1;

  tab2Root = Page2;

  tab3Root = Page3;

Add those into the files mentioned above and as indicated in the documentation examples.

You will see errors appear, because Page1, Page2 and Page3 do not exist.

Lets just get it to work before adding pages.

Update your my-tabs.ts file to say:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import { Component } from '@angular/core';

import { HomePage } from '../../pages/home/home'

/*

  Generated class for the MyTabs component.

  See https://angular.io/docs/ts/latest/api/core/index/ComponentMetadata-class.html

  for more info on Angular 2 Components.

*/

@Component({

  selector: 'my-tabs',

  templateUrl: 'my-tabs.html'

})

export class MyTabsComponent {

  text: string;

  tab1Root = HomePage;

  constructor() {

  }

}

The important parts:

1
import { HomePage } from '../../pages/home/home'

and

1
  tab1Root = HomePage;

Also let’s update our html:

Makes sure your my-tabs.html has only the following:

1
2
3
4
5
<ion-tabs>

  <ion-tab [root]="tab1Root"></ion-tab>

</ion-tabs>

Now you should see your Home page appear again on the application.

The reason you see your home page is because the MyTabs tabs component is now responsible for displaying the pages not your app.component.ts.

So to repeat the concept behind Ionic 2 Tabs and page navigation

Your tabs component will be what you route to, this is what you display on screen, then the tabs hold the responsibility to pop and push the pages on and off the screen as the user clicks through them.

You do not attach the tabs to a page, but rather you attach the pages to the tabs.

Add more pages

We need to create the Page1, Page2 and Page3 pages. You can have any number of pages you want, just remember this is a mobile application and space is limited.

We already have a Home page which we can just edit. Let’s create an About page and a ContactUs page. Then we will replace Page1, Page2 and Page3 with those pages.

Use the useful ionic generator again

Remember to use Pascal case for you page name

1
2
ionic g page About
ionic g page ContactUs

Now you will see three new folders in your pages folder of your ionic project:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/src

/pages

/home

home.html

home.scss

home.ts

/about

about.html

about.scss

about.ts

/contact-us

contact-us.html

contact-us.scss

contact-us.ts

We want to import these new pages into the my-tabs.ts file.

Update your my-tabs.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import { Component } from '@angular/core';

import { HomePage } from '../../pages/home/home'

import { AboutPage } from '../../pages/about/about'

import { ContactUsPage } from '../../pages/contact-us/contact-us'

/*

  Generated class for the MyTabs component.

  See https://angular.io/docs/ts/latest/api/core/index/ComponentMetadata-class.html

  for more info on Angular 2 Components.

*/

@Component({

  selector: 'my-tabs',

  templateUrl: 'my-tabs.html'

})

export class MyTabsComponent {

  text: string;

  tab1Root = HomePage;

  tab2Root = AboutPage;

  tab3Root = ContactUsPage;

  constructor() {

  }

}

Update your my-tabs.html

1
2
3
4
5
6
7
8
9
<ion-tabs>

  <ion-tab [root]="tab1Root"></ion-tab>

  <ion-tab [root]="tab2Root"></ion-tab>

  <ion-tab [root]="tab3Root"></ion-tab>

</ion-tabs>

Important!

Add to /src/app/app.module.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import { NgModule, ErrorHandler } from '@angular/core';

import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';

import { MyApp } from './app.component';

import { HomePage } from '../pages/home/home';

import { AboutPage } from '../pages/about/about';

import { ContactUsPage } from '../pages/contact-us/contact-us';

import { MyTabsComponent } from '../components/my-tabs/my-tabs';

@NgModule({

  declarations: [

    MyApp,

    HomePage,

    AboutPage,

    ContactUsPage,

    MyTabsComponent

  ],

  imports: [

    IonicModule.forRoot(MyApp)

  ],

  bootstrap: [IonicApp],

  entryComponents: [

    MyApp,

    HomePage,

    AboutPage,

    ContactUsPage,

    MyTabsComponent

  ],

  providers: [{provide: ErrorHandler, useClass: IonicErrorHandler}]

})

export class AppModule {}

The important parts:

1
2
3
import { AboutPage } from '../pages/about/about';

import { ContactUsPage } from '../pages/contact-us/contact-us';

And adding AboutPage and ContactUsPage to the declarations and entryComponents sections.

Adding title and icons to the tabs

You now have 3 tabs at the bottom of the screen, except you can’t see them. If you click on the bottom grey bar you will navigate to the different pages you created, but you won’t know what you are clicking on.

Add tabTitle and tabIcon attributes:

1
2
3
4
5
6
7
8
9
<ion-tabs>

  <ion-tab [root]="tab1Root" tabTitle="Home" tabIcon="home"></ion-tab>

  <ion-tab [root]="tab2Root" tabTitle="About" tabIcon="help"></ion-tab>

  <ion-tab [root]="tab3Root" tabTitle="Contact Us" tabIcon="contact"></ion-tab>

</ion-tabs>

Now you will be able to clearly see your three tabs.

To find more ionic 2 icons search this page:

https://ionicframework.com/docs/v2/ionicons/

Bonus:

Making tabs dynamic and sneaking in some polymorphism.

My brain automatically wants to make things polymorphic. I see a pattern like “tab1Root”, “tab2Root” and “tab3Root” and I’m immediately thinking let’s make that a dynamic array that can easily be extended and changed without touching the html.

To do that is very simple.

All we need is an array of objects, and an *ngFor on the <ion-tab> tag.

Update the my-tabs.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import { Component } from '@angular/core';

import { HomePage } from '../../pages/home/home'

import { AboutPage } from '../../pages/about/about'

import { ContactUsPage } from '../../pages/contact-us/contact-us'

/*

  Generated class for the MyTabs component.

  See https://angular.io/docs/ts/latest/api/core/index/ComponentMetadata-class.html

  for more info on Angular 2 Components.

*/

@Component({

  selector: 'my-tabs',

  templateUrl: 'my-tabs.html'

})

export class MyTabsComponent {

  text: string;

  tab1Root = HomePage;

  tab2Root = AboutPage;

  tab3Root = ContactUsPage;

  tabs = [

    {

      root: HomePage,

      title: 'Home',

      icon: 'home'

    },

    {

      root: AboutPage,

      title: 'About',

      icon: 'help',

    },

    {

      root: ContactUsPage,

      title: 'Contact Us',

      icon: 'contact'

    }

  ];

  constructor() {

  }

}

Update the my-tabs.html

1
2
3
4
5
<ion-tabs>

  <ion-tab *ngFor="let tab of tabs" [root]="tab.root" tabTitle="{{tab.title}}" tabIcon="{{tab.icon}}"></ion-tab>

</ion-tabs>

Bonus bonus:

I did also push that a bit further and added callbacks to the tabs, so when you click on one that needs to triggers an action like a ActionSheetController instead of a redirect. If you are interested in this solution let me know and I’ll share it as well.

Ionic 2 Testing

How do we write tests for ionic 2 tabs?

We don’t talk about ionic 2 testing 😐

Kidding, I still have to wrap my head around ionic 2 testing, so as soon as I have that figured out I will create a follow up post on how to write the Ionic 2 unit tests.

Onwards! To mobile development success.