Routing and Navigation
Basic Angular Router configuration
Create a routing module that is ‘visible’ to all components in your app
ng generate module app-routing
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
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' }
];
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