Ionic | App from Scratch – Adding Content

In this blog, we will discus the steps to add additional content (pages, components) to your existing app. We will start with the App From Scratch we create here.

Preparation

Configure Visual Studio Code

First, we will configure Visual Studio Code to show only the desired folders for this tutorial.

We will hide the folders node_modules, e2e and .env

To do this, select Code / Prefecences / Settings

Visual Studio Code will open the settings in a editor window. You will see the default user settings on the left, and the current user settings on the right.

Look for the lines unter “files.exclude” and add or edit the desired content.

Setting to true means exclude (remember the title of the group: files.exclude)

Open required windows

As we add some content, we want to see immediately the results.

So,  i opened three windows

  • Visual Studio Code
  • Browser Windows
  • Console, where we start the ionic server

Change directory structure

Now we can start: Lets add some pages we will use to contain Ionic Components (Buttons, Text, Switches, Lists, Grids, …)

Out app have 3 pages. You will find them in them explorer window

First, i want to change the directory structure.

For each component type, we will add, i want to a a separate folder.

So, first: create a folder pages for our pages

Next, move the pages folders home, list and about into the newly create folder

After that, go into the console, stop the running ionic serve command and restart it to force a new compilation of our app

$ ionic serve
> ng run app:serve --host=0.0.0.0 --port=8100

In the console window, we will see an error

[ng] ERROR in Could not resolve module ./home/home.module relative to .../app_from_scratch/src/app/app-routing.module.ts

This is, because we changes the directory path of the pages. We had to adjust the new path in out app: in app-routing.module.ts

const routes: Routes = [
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: 'home', loadChildren: './pages/home/home.module#HomePageModule' },
  { path: 'list', loadChildren: './pages/list/list.module#ListPageModule' },
  { path: 'about', loadChildren: './pages/about/about.module#AboutPageModule' }
];

Add page: ActionSheet

Ionic offers a huge amount of amazing components. So, we will demonstrate the use of this components step by step. We will take the inspiration from here.

Starting with Action Sheets.

  • Documentation is here
ionic generate page ActionSheet
> ng generate page ActionSheet
CREATE src/app/action-sheet/action-sheet.module.ts (569 bytes)
CREATE src/app/action-sheet/action-sheet.page.scss (0 bytes)
CREATE src/app/action-sheet/action-sheet.page.html (138 bytes)
CREATE src/app/action-sheet/action-sheet.page.spec.ts (727 bytes)
CREATE src/app/action-sheet/action-sheet.page.ts (279 bytes)
UPDATE src/app/app-routing.module.ts (736 bytes)
[OK] Generated page!

To add the new page to the sidemenu, change the AppPages array in app.component.ts

public appPages = [
  { title: 'Home', url: '/home', icon: 'home' },
  { title: 'List', url: '/list', icon: 'list' },
  { title: 'About', url: '/about', icon: '' },
  { title: 'ActionSheet', url: '/ActionSheet', icon: 'list' }
];

Notice the different spelling of the url (different case of letters). Whatever spelling you choose, it has to fit to the path value in the app-routing.module.ts

Next, move the page to the pages folder and change the path in app-routing.modules.ts

Add the sidemenu button to the actionSheet Page (in action-sheet.page.html)

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="start">
      <ion-menu-button></ion-menu-button>
    </ion-buttons>
    <ion-title>ActionSheet</ion-title>
  </ion-toolbar>
</ion-header>

Ok, well done

Next, add a button and the code for displaying the action sheet. The source is here.

Change your html page: action-sheet.page.html

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="start">
      <ion-menu-button></ion-menu-button>
    </ion-buttons>
    <ion-title>ActionSheet</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content padding fullscreen>
  <ion-button expand="block" id="basic" (click)="presentBasic()">Basic</ion-button>
  <ion-button expand="block" id="noBackdropDismiss" (click)="presentNoBackdropDismiss()">No Backdrop Dismiss</ion-button>
  <ion-button expand="block" id="alertFromActionSheet" (click)="presentAlert()">Alert from Action Sheet</ion-button>
  <ion-button expand="block" id="scrollableOptions" (click)="presentScroll()">Scrollable Options</ion-button>
  <ion-button expand="block" id="scrollWithoutCancel" (click)="presentScrollNoCancel()">Scroll Without Cancel</ion-button>
  <ion-button expand="block" id="cancelOnly" (click)="presentCancelOnly()">Cancel Only</ion-button>
  <ion-button expand="block" id="icons" (click)="presentIcons()">Icons</ion-button>
  <ion-button expand="block" id="cssClass" (click)="presentWithCssClass()">Custom CSS Class</ion-button>
</ion-content>

Change your Style: action-sheet.page-scss

app-action-sheet {
}

Change the Code: action-sheet.page.ts. Source is here

import { Component, OnInit } from "@angular/core";
import { Platform, ActionSheetController } from "@ionic/angular";

@Component({
  selector: "app-action-sheet",
  templateUrl: "./action-sheet.page.html",
  styleUrls: ["./action-sheet.page.scss"]
})
export class ActionSheetPage implements OnInit {
  constructor(
    public platform: Platform,
    public actionSheetController: ActionSheetController
  ) {}

  ngOnInit() {}


  public changeColor(ev) {

  }

  async presentActionSheet() {
    const actionSheet = await this.actionSheetController.create({
      header: "Albums",
      buttons: [{
        text: 'Delete',
        role: 'destructive',
        icon: 'trash',
        handler: () => {
          console.log('Delete clicked');
        }
      }, {
        text: 'Share',
        icon: 'share',
        handler: () => {
          console.log('Share clicked');
        }
      }, {
        text: 'Play (open modal)',
        icon: 'arrow-dropright-circle',
        handler: () => {
          console.log('Play clicked');
        }
      }, {
        text: 'Favorite',
        icon: 'heart',
        handler: () => {
          console.log('Favorite clicked');
        }
      }, {
        text: 'Cancel',
        icon: 'close',
        role: 'cancel',
        handler: () => {
          console.log('Cancel clicked');
        }
      }]
    });
    await actionSheet.present();
  }

  async presentBasic() {
    console.log('ActionSheetPage:presentBasic');

    const actionSheet = await this.actionSheetController.create({
      header: "Albums",
      buttons: [{
        text: 'Delete',
        role: 'destructive',
        icon: 'trash',
        handler: () => {
          console.log('Delete clicked');
        }
      }, {
        text: 'Share',
        icon: 'share',
        handler: () => {
          console.log('Share clicked');
        }
      }, {
        text: 'Play (open modal)',
        icon: 'arrow-dropright-circle',
        handler: () => {
          console.log('Play clicked');
        }
      }, {
        text: 'Favorite',
        icon: 'heart',
        handler: () => {
          console.log('Favorite clicked');
        }
      }, {
        text: 'Cancel',
        icon: 'close',
        role: 'cancel',
        handler: () => {
          console.log('Cancel clicked');
        }
      }]
    });
    await actionSheet.present();
  }
  
  async presentIcons() {
    const actionSheetController = document.querySelector('ion-action-sheet-controller');
    await actionSheetController.componentOnReady();
    const actionSheetElement = await actionSheetController.create({
      header: "Albums",
      buttons: [{
        text: 'Delete',
        role: 'destructive',
        icon: 'trash',
        handler: () => {
          console.log('Delete clicked');
        }
      }, {
        text: 'Share',
        icon: 'share',
        handler: () => {
          console.log('Share clicked');
        }
      }, {
        text: 'Play (open modal)',
        icon: 'arrow-dropright-circle',
        handler: () => {
          console.log('Play clicked');
        }
      }, {
        text: 'Favorite',
        icon: 'heart',
        role: 'selected',
        handler: () => {
          console.log('Favorite clicked');
        }
      }, {
        text: 'Cancel',
        role: 'cancel',
        icon: 'close',
        handler: () => {
          console.log('Cancel clicked');
        }
      }]
    })
    await actionSheetElement.present();
  }
  
  async presentNoBackdropDismiss() {
    const actionSheetController = document.querySelector('ion-action-sheet-controller');
    await actionSheetController.componentOnReady();
    const actionSheetElement = await actionSheetController.create({
      backdropDismiss: false,
      buttons: [{
        text: 'Archive',
        handler: () => {
          console.log('Archive clicked');
        }
      }, {
        text: 'Destructive',
        role: 'destructive',
        handler: () => {
          console.log('Destructive clicked');
        }
      }, {
        text: 'Cancel',
        role: 'cancel',
        handler: () => {
          console.log('Cancel clicked');
        }
      }]
    });
    await actionSheetElement.present();
  }
  
  async presentAlert() {
    const actionSheetController = document.querySelector('ion-action-sheet-controller');
    await actionSheetController.componentOnReady();
    const actionSheetElement = await actionSheetController.create({
      buttons: [{
        text: 'Open Alert',
        handler: () => {
          console.log('Open Alert clicked');
        }
      }, {
        text: 'Cancel',
        role: 'cancel',
        handler: () => {
          console.log('Cancel clicked');
        }
      }]
    });
    await actionSheetElement.present();
  }
  
  async presentScroll() {
    const actionSheetController = document.querySelector('ion-action-sheet-controller');
    await actionSheetController.componentOnReady();
    const actionSheetElement = await actionSheetController.create({
      buttons: [
        {
          text: 'Add Reaction',
          handler: () => {
            console.log('Add Reaction clicked');
          }
        }, {
          text: 'Copy Text',
          handler: () => {
            console.log('Copy Text clicked');
          }
        }, {
          text: 'Share Text',
          handler: () => {
            console.log('Share Text clicked');
          }
        }, {
          text: 'Copy Link to Message',
          handler: () => {
            console.log('Copy Link to Message clicked');
          }
        }, {
          text: 'Remind Me',
          handler: () => {
            console.log('Remind Me clicked');
          }
        }, {
          text: 'Pin File',
          handler: () => {
            console.log('Pin File clicked');
          }
        }, {
          text: 'Star File',
          handler: () => {
            console.log('Star File clicked');
          }
        }, {
          text: 'Mark Unread',
          handler: () => {
            console.log('Mark Unread clicked');
          }
        }, {
          text: 'Edit Title',
          handler: () => {
            console.log('Edit Title clicked');
          }
        }, {
          text: 'Save Image',
          handler: () => {
            console.log('Save Image clicked');
          }
        }, {
          text: 'Copy Image',
          handler: () => {
            console.log('Copy Image clicked');
          }
        }, {
          text: 'Delete File',
          role: 'destructive',
          handler: () => {
            console.log('Delete File clicked');
          }
        }, {
          text: 'Cancel',
          role: 'cancel', // will always sort to be on the bottom
          handler: () => {
            console.log('Cancel clicked');
          }
        }
      ]
    });
    await actionSheetElement.present();
  }
  
  async presentScrollNoCancel() {
    const actionSheetController = document.querySelector('ion-action-sheet-controller');
    await actionSheetController.componentOnReady();
    const actionSheetElement = await actionSheetController.create({
      buttons: [
        {
          text: 'Add Reaction',
          handler: () => {
            console.log('Add Reaction clicked');
          }
        }, {
          text: 'Copy Text',
          handler: () => {
            console.log('Copy Text clicked');
          }
        }, {
          text: 'Share Text',
          handler: () => {
            console.log('Share Text clicked');
          }
        }, {
          text: 'Copy Link to Message',
          handler: () => {
            console.log('Copy Link to Message clicked');
          }
        }, {
          text: 'Remind Me',
          handler: () => {
            console.log('Remind Me clicked');
          }
        }, {
          text: 'Pin File',
          handler: () => {
            console.log('Pin File clicked');
          }
        }, {
          text: 'Star File',
          handler: () => {
            console.log('Star File clicked');
          }
        }, {
          text: 'Mark Unread',
          handler: () => {
            console.log('Mark Unread clicked');
          }
        }, {
          text: 'Edit Title',
          handler: () => {
            console.log('Edit Title clicked');
          }
        }, {
          text: 'Save Image',
          handler: () => {
            console.log('Save Image clicked');
          }
        }, {
          text: 'Copy Image',
          handler: () => {
            console.log('Copy Image clicked');
          }
        }, {
          text: 'Delete File',
          role: 'destructive',
          handler: () => {
            console.log('Delete File clicked');
          }
        }
      ]
    });
    await actionSheetElement.present();
  }
  
  async presentCancelOnly() {
    const actionSheetController = document.querySelector('ion-action-sheet-controller');
    await actionSheetController.componentOnReady();
    const actionSheetElement = await actionSheetController.create({
      buttons: [
        {
          text: 'Cancel',
          role: 'cancel', // will always sort to be on the bottom
          handler: () => {
            console.log('Cancel clicked');
          }
        }
      ]
    });
    await actionSheetElement.present();
  }
  
  async presentWithCssClass() {
    const actionSheetController = document.querySelector('ion-action-sheet-controller');
    await actionSheetController.componentOnReady();
    const actionSheetElement = await actionSheetController.create({
      header: "Custom Css Class",
      cssClass: "my-class my-custom-class",
      buttons: [
        {
          text: 'Test',
          role: 'test',
          cssClass: 'my-cancel-button my-custom-button customClass',
          handler: () => {
            console.log('Cancel clicked');
          }
        }
      ]
    });
    await actionSheetElement.present();
  }
}

Done. Now look at the result

Add page: Buttons

Add page: Toolbar Buttons

Starting with Capacitor

Introduction

Git Repository is here

$ npm -g install ionic
$ ionic start SampleApp super  --no-link
$ cd SampleApp
$ npm install --save @capacitor/core @capacitor/cli

$ npx cap init sample-app com.example.sampleapp
$ npm run build
$ npx cap add ios
$ npx cap copy
$ npx cap copy ios
$ npx cap open ios

Installation

Requirements

Working with Ionic and Capacitor needs additional software like node and python.

To separate our development environment from other parts of our system, we will use virtual environments for both parts:

To automatically setup this environment when entering the directory, additionally we will use direnv. Read this post to get more detailled information about direnv.

Setup under Mac OS

Install components

Use Homebrew to install the required components

$ brew install direnv nvm pyenv

Configure direnv

Configure your shell to use direnv. Add the following into your .profile or .bashrc.

$ eval "$(direnv hook bash)"

Change to your development directory and setup direnv

$ cd <your development dir>

Use this file as as starter for direnv. Copy the file into the directory with the name .envrc

After saving or creating the file .direnv, you will see the following direnv message:

direnv: error .envrc is blocked. Run `direnv allow` to approve its content.

Just type direnv allow, to use this file

$ direnv allow
direnv: loading .envrc
setup               : nvm        (<your development directory>/.env/nvm)
set default         : nvm        (default)
N/A: version "default -> N/A" is not yet installed.

You need to run "nvm install default" to install it before using it.
set default         : python     (2.7.14)
pyenv: version `2.7.14' not installed
Enabled             : nvm        (0.33.8)
Enabled             : node       (9.7.1)
Enabled             : npm        (5.6.0)
pyenv: version `2.7.14' is not installed (set by <your development directory>/.python-version)
Enabled    
direnv: export +NVM_RC_VERSION ~NVM_DIR         : python     ()

Install additional software components

See the error messages in line 7 and 13? You need to install node and python:

$ nvm install 10.8.0
$ pyenv install 2.7.15

The desired versions are activated by direnv because of this lines in you .envrc

# Activate Node
$ nvm use default --silent

# Activate Python
$ pyenv local $PYTHON_VERSION

Final check

Leave the directory and reenter again, or type direnv reload

direnv: loading .envrc
setup               : nvm        (<your development directory>/.env/nvm)
set default         : nvm        (default)
set default         : python     (2.7.14)
Enabled             : nvm        (0.33.8)
Enabled             : node       (9.8.0)
Enabled             : npm        (5.6.0)
Enabled             : python     (Python 2.7.14)
Enabled             : python2    (Python 2.7.14)
direnv: export ~NVM_DIR

Prepare for capacitor

Detailed description can be found here

Start with a Ionic template

Ionic
$ npm -g install ionic
Create sample app from template and change to the SampleApp directory
$ ionic start SampleApp super --no-link --bundle-id com.example.sampleapp
$ cd SampleApp

Important: Be sure to say NO no when asked for integration of Cordova. We will use Capacitor.

Build your app, we will need the www directory with the web files
 $ npm run build
 Add capacitor support
$ npm install --save @capacitor/core @capacitor/cli
$ npx cap init sample-app com.example.myapp
$ npm install -save @capacitor/core
$ npx cap add ios
Build your app
$ npx cap copy ios
$ npx cap open ios

The last command starts Xcode:

Now, you can build and run your iOS App

Working with moments.js

Introduction

With moment.js, you can

Parse, validate, manipulate, and display dates and times in JavaScript.

With a npm-Module, you can integrate this functionality into your Ionic App

Preparation

Create your empty app

$ ionic start working-with-moments blank --no-git --no-link

Install npm Module

$ cd  working-with-moments
$ npm install moment
+ moment@2.20.1
added 1 package in 3.001s

Start Editor and serve your app

$ vscode .
$ ionic serve

Add moments.js functionality

Change pages/home/home.ts

Add the moments.js reference to pages/home/home.ts

import { Component } from '@angular/core'; 
import { NavController } from 'ionic-angular';
import * as moment from 'moment';

Create a var for the current date/time

export class HomePage {
  public date: any;

  constructor(public navCtrl: NavController) {
    this.date = moment();
  }
}

Change pages/home/home.html

<ion-content padding>
  The current date/time is {{ date }}
</ion-content>

Summary

A lot more examples could be found in my repository. Just create a starter app with this template and begin to play

$ ionic start app https://github.com/ionic4-toolbox/Working-with-moment.js

Ionic | App from Scratch – Two Sidemenus

Prepare your starter App

ionic start App sidemenu --type angular --no-git

Change into the newly create folder and start the Ionic App

cd App
ionic serve

You will see the default Ionic Sidemenu App

The entries of the sSidemenu are defined in app.components.ts:

export class AppComponent {
	public appPages = [
		{ title: 'Home', url: '/home', icon: 'home' },
		{ title: 'List', url: '/list', icon: 'list' }
	];

The sidemenu itself and his apperance are defined in app.component.html:

<ion-app>
  <ion-split-pane contentId="main-content">
    <ion-menu contentId="main-content" type="overlay">
      <ion-header>
        <ion-toolbar>
          <ion-title>Menu</ion-title>
        </ion-toolbar>
      </ion-header>
      <ion-content>
        <ion-list>
          <ion-menu-toggle auto-hide="false" *ngFor="let p of appPages">
            <ion-item [routerDirection]="'root'" [routerLink]="[p.url]">
              <ion-icon slot="start" [name]="p.icon"></ion-icon>
              <ion-label>{{p.title}}</ion-label>
            </ion-item>
          </ion-menu-toggle>
        </ion-list>
      </ion-content>
    </ion-menu>
    <ion-router-outlet id="main-content"></ion-router-outlet>
  </ion-split-pane>
</ion-app>

The Sidemenu entries and the Icon are added in line 13 and 14 to the sidemenu.

Define two sidemenus in app.html

<ion-menu [content]="contentLeft"> ....</ion-menu>
<ion-menu side="right" [content]="contentRight">....</ion-menu>
 ....

<ion-nav [root]="rootPage" #contentLeft #contentRight swipeBackEnabled="false"></ion-nav>

Add buttons to page

<ion-header>
   <ion-navbar>
      <button ion-button start menuToggle>
         <ion-icon name="menu"></ion-icon>
      </button>
      <ion-title>Demo of two Sidemenus</ion-title>
      <button ion-button end menuToggle="right">
         <ion-iconname="menu"></ion-icon>
      </button>
   </ion-navbar>
</ion-header>
<ion-content padding>
</ion-content>
Result
Github Repository

You can find the code here