Ionic | Cookbook
Routing and Navigation
Basic Angular Router configuration
Create a routing module that is ‘visible’ to all components in your app
With Angular CLI
ng generate module app-routing
With Ionic
ionic start my-app blank --type=angular
Match URL paths to Pages/Components
app-routing.modules.ts
const routes: Routes = [ { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: '**', redirectTo: '/home', pathMatch: 'full' }, { path: 'home', loadChildren: './home/home.module#HomePageModule' }, { path: 'list', loadChildren: './list/list.module#ListPageModule' }, { path: 'about', loadChildren: './about/about.module#AboutPageModule' } ]
Update routing module imports and exports
app-routing.modules.ts
imports { RouterModule, Routes } from '@angular/routes';
@NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] })
Do not forget to import the routing module to you main app module
app.module
import { AppRoutingModule } from './app-routing.module';
imports:[ AppRoutingModule ]
Add a router-outlet to indicate where the pages will be rendered
app.component.html
<router-outlet></router-outlet>
Basics
src/app/app-routing.module.ts
const routes: Routes = [ { path: 'hello', component: HelloPage } ];
app.component.html
<router-outlet></router-outlet>
// Regular Route { path: 'eager', component: MyComponent }, // Lazy Loaded Route (Page) { path: 'lazy', loadChildren: './lazy/lazy.module#LazyPageModule' }, // Redirect { path: 'here', redirectTo: 'there', pathMatch: 'full' } ];
<ion-button href="/hello">Hello</ion-button> <a routerLink="/hello">Hello</a>
<a [routerLink]="['/product',product.id]"></a>
Navigate Programmatically
import { Component } from '@angular/core'; import { Router } from '@angular/router'; @Component({ ... }) export class HomePage { constructor(private router: Router) {} go() { this.router.navigateByUrl('/animals'); } }
Navigate to Dynamic URLS
const routes: Routes = [ // Regular Route { path: 'items/:id', component: MyComponent }, ];
<ion-button href="/items/abc">ABC</ion-button> <ion-button href="/items/xyz">XYZ</ion-button>
Passing parameter
Passing parameter with state service
export class ComponentA { constructor(private stateService: StateService) {} goToComponentB(): void { this.stateService.data = {...}; this.router.navigate(['/b']); } } export class ComponentB implements OnInit { constructor(private stateService: StateService) {} ngOnInit(): void { this.data = this.stateService.data; this.stateService.data = undefined; } }
Passing parameter in link
export class ComponentA { constructor(private router: Router) {} goToComponentB(): void { this.router.navigate(['/b'], {state: {data: {...}}}); } }
go() { this.router.navigate(['../list'], { relativeTo: this.route }); }
Passing parameter in routerlink directive
<a [routerLink]=”/b” [state]=”{ data: {...}}”>Go to B</a>
Extracting the data
The state property was added to Navigation
which is available through Router.getCurrentNavigation().extras.state
.
Problem is that getCurrentNavigation
returns Navigation
only during the navigation and returns null
after the navigation ended. So the Navigation
is no longer available in Component’s B onInit
lifecycle hook. We need to read the data from browser’s history object:
history.state.data
Extract Data from Routes with ActivatedRoute
When working with dynamic data, you need to extract the params from the URL.
For example, you might want to read from the database when the user navigates to /items/:id
, using the ID from the route to make a query.
Angular has an ActivatedRoute service that allows us to grab information from the current route as a plain object or Observable.
import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; @Component({ ... }) export class ProfileComponent implements OnInit { id: string; constructor(private route: ActivatedRoute) {} ngOnInit() { this.id = this.route.snapshot.paramMap.get('id'); } }
Or if we need to react to changes, we can subscribe to an Observable.
ngOnInit() { this.route.paramMap.subscribe(params => { this.products.forEach((p: Product) => { if (p.id == params.id) { this.product = p; } }); }); }
ngOnInit() { this.products.forEach((p: Product) => { if (p.id == this.route.snapshot.params.id) { this.product = p; } }); }
Migrate from Ionic X to Ionic 4 Routing
Set Root
<ion-button href="/support" routerDirection="root">
or in class
this.navCtrl.navigateRoot('/support');
Push
<ion-button href="/products/12" routerDirection="forward">
this.navCtrl.navigateForward('/products/12');
Pop
<ion-button href="/products" routerDirection="backward">
<ion-back-button defaultHref="/products"></ion-back-button>
Navigate backwards programatically:
this.navCtrl.navigateBack('/products');
Routing in Tabs
{ path: 'contact', outlet: 'modal', component: ContactModal }
http://.../(modal:contact)
Lazy Loading
Code Snippets
// home.module.ts @NgModule({ imports: [ IonicModule, RouterModule.forChild([{ path: '', component: HomePage }]) ], declarations: [HomePage] }) export class HomePageModule {}
// app.module.ts @NgModule({ declarations: [AppComponent], imports: [ BrowserModule, IonicModule.forRoot(), RouterModule.forRoot([ { path: 'home', loadChildren: './pages/home/home.module#HomePageModule' }, { path: '', redirectTo: 'home', pathMatch: 'full' } ]) ], bootstrap: [AppComponent] }) export class AppModule {}
Code Snippet
app-routing.module.ts
const routes: Routes = [ { path: 'about', loadChildren: './about/about.module#AboutPageModule' }, ];
about/about.module.ts
const routes: Routes = [ { path: '', component: AboutPage }, ];
Using Guards
@Injectable({ providedIn: 'root' }) export class AuthGuard implements CanActivate { constructor(private router: Router) {} canActivate( next: ActivatedRouteSnapshot, state: RouterStateSnapshot ): boolean { const loggedIn = false; // replace with actual user auth checking logic if (!loggedIn) { this.router.navigate(['/']); } return loggedIn; } }
const routes: Routes = [ { path: 'special', component: SpecialPage, canActivate: [AuthGuard] }, ];
Troubleshooting of Routing
Enable tracing
@NgModule({ imports: [ RouterModule.forRoot(routes, { enableTracing: true } )], exports: [RouterModule], }) export class AppRoutingModule {}
Common Errors and mistakes
Placing global routing patterns at the front ot routing array
const routes: Routes = [ { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: '**', redirectTo: '/home', pathMatch: 'full' }, { path: 'home', component: HomePageComponent },
Finding the right path is a sequential process in search all entries in route[] and select the first with a matting path. So, in our wrong example, every path matches the common pattern ‘**’.
Solution: Put this matting pattenr at the end of the routes[] array
const routes: Routes = [ { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: 'home', component: HomePageComponent }, { path: '**', redirectTo: '/home', pathMatch: 'full' },
Storage
Configuration
import { IonicStorageModule } from '@ionic/storage'; @NgModule({ declarations: [ // ... ], imports: [ BrowserModule, IonicModule.forRoot(MyApp), IonicStorageModule.forRoot() ], bootstrap: [IonicApp], entryComponents: [ // ... ], providers: [ // ... ] }) export class AppModule {}
Finally, inject it into any of your components or pages: import { Storage } from '@ionic/storage'; export class MyApp { constructor(private storage: Storage) { storage.set('name', 'Max'); storage.get('age').then((val) => { console.log('Your age is', val); }); } }
Code sample
class MyClass { constructor(public storage: Storage) {} async setData(key, value) { const res = await this.storage.set(key, value); console.log(res); } async getData(key) { const keyVal = await this.storage.get(key); console.log('Key is', keyVal); } }
Storage with Capacitor
import { Plugins } from '@capacitor/core'; const { Storage } = Plugins; async setObject() { await Storage.set({ key: 'user', value: JSON.stringify({ id: 1, name: 'Max' }) }); } async getObject() { const ret = await Storage.get({ key: 'user' }); const user = JSON.parse(ret.value); } async setItem() { await Storage.set({ key: 'name', value: 'Max' }); } async getItem() { const value = await Storage.get({ key: 'name' }); console.log('Got item: ', value); } async removeItem() { await Storage.remove({ key: 'name' }); } async keys() { const keys = await Storage.keys(); console.log('Got keys: ', keys); } async clear() { await Storage.clear(); }
Components
Alerts
Code Snippets
showAlert() { this.alertCtrl.create({ message: "Hello There", subHeader: "I'm a subheader" }).then(alert => alert.present()); } // Or using async/await async showAlert() { const alert = await this.alertCtrl.create({ message: "Hello There", subHeader: "I'm a subheader" }); await alert.present(); }
Local Notifications
import { Plugins } from '@capacitor/core'; const { LocalNotifications } = Plugins; LocalNotifications.schedule({ notifications: [ { title: "Title", body: "Body", id: 1, schedule: { at: new Date(Date.now() + 1000 * 5) }, sound: null, attachments: null, actionTypeId: "", extra: null } ] });
Custom Components
Create custom component
$ ionic generate component components/Sample > ng generate component components/Sample CREATE src/app/components/sample/sample.component.scss (0 bytes) CREATE src/app/components/sample/sample.component.html (25 bytes) CREATE src/app/components/sample/sample.component.spec.ts (628 bytes) CREATE src/app/components/sample/sample.component.ts (270 bytes) UPDATE src/app/components/components.module.ts (621 bytes) [OK] Generated component!
$ ionic generate module components/Components --flat > ng generate module components/Components --flat CREATE src/app/components/components.module.ts (194 bytes) [OK] Generated module!
Modify selector for component in app/components/sample/sample.component.ts
@Component({ selector: 'c-sample', templateUrl: './c-sample.component.html', styleUrls: [ './c-sample.component.scss' ] })
Rename files for component
cd src/app/components/sample mv sample.component.html c-sample.component.scss mv sample.component.html c-sample.component.html mv sample.component.html c-sample.component.spec.ts mv sample.component.html c-sample.component.ts
Export created component in app/components/components.module.ts
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { IonicModule } from '@ionic/angular'; import { SampleComponent } from './sample/sample.component'; @NgModule({ imports: [ CommonModule, IonicModule.forRoot(), ], declarations: [ SampleComponent ], exports: [ SampleComponent ], entryComponents: [], }) export class ComponentsModule { }
Add page to display the component
$ ionic generate page pages/Sample > ng generate page pages/Sample CREATE src/app/pages/sample/sample.module.ts (543 bytes) CREATE src/app/pages/sample/sample.page.scss (0 bytes) CREATE src/app/pages/sample/sample.page.html (133 bytes) CREATE src/app/pages/sample/sample.page.spec.ts (691 bytes) CREATE src/app/pages/sample/sample.page.ts (256 bytes) UPDATE src/app/app-routing.module.ts (539 bytes) [OK] Generated page!
Add custom component to new page sample.page.html
<ion-content padding> <c-sample></c-sample> </ion-content>
Register components module in sample.module.ts
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { Routes, RouterModule } from '@angular/router'; import { IonicModule } from '@ionic/angular'; import { ComponentsModule } from 'src/app/components/components.module'; import { SamplePage } from './sample.page'; const routes: Routes = [ { path: '', component: SamplePage } ]; @NgModule({ declarations: [SamplePage], imports: [ CommonModule, IonicModule, RouterModule.forChild(routes), ComponentsModule ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) export class SamplePageModule {}
Check Routing in app-routing.modules.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: 'sample', loadChildren: './pages/sample/sample.module#SamplePageModule' } ];
Add new page to sidemenu in app.components.ts
public appPages = [ { title: 'Home', url: '/home', icon: 'home' }, { title: 'List', url: '/list', icon: 'list' }, { title: 'Sample Component', url: '/sample', icon: 'list' } ];
Directives
Pipes
HTML Elements
Access HTML Element from Page Class
<div #box></div>
@ViewChild('box', {static: false}) el_box:ElementRef; box: any; constructor() { this.box = this.el_box.nativeElement; }
Grabbing Ionic Components with ViewChild
Let’s imagine we have a HomePage
component that looks like this and we want to close the menu when an item is clicked.
<ion-menu> </ion-menu>
Our goal is to access the ion-menu
from the TypeScript code so we can call its API methods, like open()
and close()
.
import { Component, ViewChild } from '@angular/core'; import { Menu } from '@ionic/angular'; @Component(...) export class HomePage { @ViewChild(Menu, {static: false})) menu: Menu; onDrag() { this.menu.close(); } }
Explanation:
{ static: false }
If you set static false, the component ALWAYS gets initialized after the view initialization in time for the ngAfterViewInit/ngAfterContentInit
callback functions.
{ static: true}
If you set static true, the initialization will take place at the view initialization at ngOnInit
Shortcut: Use Template Variables
There’s actually a very convenient shortcut to using ViewChild in a component. We never have to leave the HTML by setting a template variable in Angular. In this example we reference the menu component with a hashtag and variable name #mymenu
.
<ion-menu #mymenu> <ion-item (click)="mymenu.close()"></ion-item>
Grabbing Multiple Components with ViewChildren
You might also run into a situation where there are multiple components of the same type on the page, such as multiple FABs:
<ion-fab></ion-fab> <ion-fab></ion-fab> <ion-fab></ion-fab>
ViewChildren
is almost the same, but it will grab all elements that match this component and return them as an Array.
import { Component, ViewChildren } from '@angular/core'; import { Fab } from '@ionic/angular'; @Component(...) export class HomePage { @ViewChildren(Fab, {static: false})) fabs: Fab[]; closeFirst() { this.fabs[0].close(); } }
Now that you know about ViewChild
, you should have no problem accessing the API methods found on Ionic’s web components.
Loops in HTML Elements
<ul> <li *ngFor="let number of [0,1,2,3,4]"> {{number}} </li> </ul>
<ul> <li *ngFor='#key of [1,2]'> {{key}} </li> </ul>
<ul> <li *ngFor='#val of "0123".split("")'>{{val}}</li> </ul>
<ul> <li *ngFor='#val of counter(5) ;#i= index'>{{i}}</li> </ul> export class AppComponent { demoNumber = 5 ; counter = Array; numberReturn(length){ return new Array(length); } }
Display Array
<ion-grid class="board"> <ion-row *ngFor="let r of [0,1,2]"> <ion-col col-4 class="cell" *ngFor="let c of [0,1,2]" (click)="handle(c+r*3)"> {{squares[c+r*3]}} </ion-col> </ion-row> </ion-grid>
Add function to Button click
<ion-item (click)="onClick($event)">
onClick(ev: any){ this.log('onClick', 'event=' + ev); }
Change CSS class on click
Add handler to html element
<a class="btn" (click)='toggleClass($event)'> <ion-icon class="icon" name="bluetooth"></ion-icon> </a>
Import Render2 in page.ts
import { Component, OnInit, Renderer2 } from '@angular/core'; ... constructor(private renderer: Renderer2) { }
Write handler to toggle class
toggleClass(event: any) { const classname = 'active'; if (event.target.classList.contains(classname)) { this.renderer.removeClass(event.target, classname); } else { this.renderer.addClass(event.target, classname); } }
Migrating to Ionic 4
Replace Http Module with HttpClient
Changes in app.module.ts
import { HttpClientModule } from '@angular/common/http';
@NgModule({ declarations: [AppComponent], entryComponents: [], imports: [ ... HttpClientModule ... ],
Changes in service.ts
import { Http } from '@angular/http';
import { HttpClient } from '@angular/common/http';
constructor(public http: Http) { }
constructor(public httpClient: HttpClient) { }
Troubleshooting
ERROR: Unexpected end of JSON input while parsing near
npm cache clean --force npm install -g @angular/cli