How to set state or dispatch actions from a navigator (ex : cypress testing) - cypress

A good practice given by Cypress (e2e testing) is to set the state of the app programmatically rather than using the UI. This of course makes sense.
On this video https://www.youtube.com/watch?v=5XQOK0v_YRE Brian Mann propose this solution to expose a Redux store :
Is there any possibility with NGXS to have access to the different state programmatically during testing ? An example is for the login process : dispatching directly a Login action or setting the store with the access token, to be logged in before any test, would be nice.

This cofnfiguration works for me:
in app folder in model:
export interface IWindowCypress {
Cypress: {
__store__: Store;
};
}
in app.module.ts:
import {BrowserModule} from '#angular/platform-browser';
import {NgModule} from '#angular/core';
import {NgxsModule, Store} from '#ngxs/store';
import {AppComponent, IWindowCypress} from './app.component';
import {ZooState} from './state/zoo.state';
import {NgxsReduxDevtoolsPluginModule} from '#ngxs/devtools-plugin';
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule, NgxsModule.forRoot([ZooState], {}),
NgxsReduxDevtoolsPluginModule.forRoot()
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {
constructor(protected store: Store) {
const windowSore: IWindowCypress = window as unknown as IWindowCypress;
if (windowSore.Cypress) {
console.log('ustawiƂem store');
windowSore.Cypress.__store__ = store;
}
}
}
using in app component:
import {Component} from '#angular/core';
import {Store} from '#ngxs/store';
import {FeedAnimals} from './state/zoo.state';
/// <reference types="Cypress" />
export interface IWindowCypress {
Cypress: {
__store__: Store;
};
}
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'cypress-ngxs';
constructor() {
const windowSore: IWindowCypress = window as unknown as IWindowCypress;
if (windowSore.Cypress) {
(windowSore.Cypress.__store__ as Store).dispatch(new FeedAnimals());
}
}
}
using in cypress spec:
/// <reference types="Cypress" />
import {Store} from '#ngxs/store';
import {IWindowCypress} from 'src/app/app.component';
import {FeedAnimals, ZooState} from '../../../src/app/state/zoo.state';
import {Observable} from 'rxjs';
describe('My Second Test Suite', () => {
it('My FirstTest case', () => {
cy.visit(' http://localhost:4200/ ');
cy.get('.content > :nth-child(2)').should(item => {
const windowSore: IWindowCypress = window as unknown as IWindowCypress;
if (windowSore.Cypress) {
// get store
const store: Store = windowSore.Cypress.__store__;
// declare observable
const myObs: Observable<boolean> = store.select(ZooState.zoo$);
// subscribe
myObs.pipe().subscribe((feed) => console.log('from subscribe: ', feed));
// make some dispatch
(windowSore.Cypress.__store__ as Store).dispatch(new FeedAnimals());
(windowSore.Cypress.__store__ as Store).dispatch(new FeedAnimals());
(windowSore.Cypress.__store__ as Store).dispatch(new FeedAnimals());
(windowSore.Cypress.__store__ as Store).dispatch(new FeedAnimals());
}
});
});
});
and zoo state:
import {Injectable} from '#angular/core';
import {Action, Selector, State, StateContext} from '#ngxs/store';
export class FeedAnimals {
static readonly type = '[Zoo] FeedAnimals';
}
export interface ZooStateModel {
feed: boolean;
}
#State<ZooStateModel>({
name: 'zoo',
defaults: {
feed: false
}
})
#Injectable()
export class ZooState {
#Selector()
static zoo$(state: ZooStateModel): boolean {
return state.feed;
}
#Action(FeedAnimals)
feedAnimals(ctx: StateContext<ZooStateModel>): void {
console.log('fedeeeeeed');
const state = ctx.getState();
ctx.setState({
...state,
feed: !state.feed
});
}
}

Related

How to mock ActivatedRoute in a jasmine test

I'm trying to mock the ActivatedRoute object is a test to simulate the passing of a parameter.
However every time the jasmine tests runs I get the following error:
Error: Can't resolve all parameters for ActivatedRoute: (?, ?, ?, ?, ?, ?, ?, ?).
at syntaxError (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/compiler/esm5/compiler.js:485:22)
at CompileMetadataResolver.webpackJsonp../node_modules/#angular/compiler/esm5/compiler.js.CompileMetadataResolver._getDependenciesMetadata (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/compiler/esm5/compiler.js:15700:1)
at CompileMetadataResolver.webpackJsonp../node_modules/#angular/compiler/esm5/compiler.js.CompileMetadataResolver._getTypeMetadata (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/compiler/esm5/compiler.js:15535:1)
at CompileMetadataResolver.webpackJsonp../node_modules/#angular/compiler/esm5/compiler.js.CompileMetadataResolver._getInjectableMetadata (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/compiler/esm5/compiler.js:15515:1)
at CompileMetadataResolver.webpackJsonp../node_modules/#angular/compiler/esm5/compiler.js.CompileMetadataResolver.getProviderMetadata (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/compiler/esm5/compiler.js:15875:1)
at http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/compiler/esm5/compiler.js:15786:1
at Array.forEach (<anonymous>)
at CompileMetadataResolver.webpackJsonp../node_modules/#angular/compiler/esm5/compiler.js.CompileMetadataResolver._getProvidersMetadata (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/compiler/esm5/compiler.js:15746:1)
at CompileMetadataResolver.webpackJsonp../node_modules/#angular/compiler/esm5/compiler.js.CompileMetadataResolver.getNonNormalizedDirectiveMetadata (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/compiler/esm5/compiler.js:15007:1)
at CompileMetadataResolver.webpackJsonp../node_modules/#angular/compiler/esm5/compiler.js.CompileMetadataResolver._getEntryComponentMetadata (http://localhost:9876/_karma_webpack_/webpack:/node_modules/#angular/compiler/esm5/compiler.js:15848:26)
The Test looks like as follows:
import { TestBed } from '#angular/core/testing';
import { ReviewComponent } from './review.component';
import { ActivatedRoute, Router, Params } from '#angular/router';
import {AppModule} from "../app.module";
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
class MockRouter {
navigate = jasmine.createSpy('navigate');
}
class MockActivatedRoute extends ActivatedRoute {
params: Observable<Params>;
constructor(parameters?: { [key: string]: any; }) {
super();
this.params = Observable.of(parameters);
}
}
describe('ReviewComponent', () => {
let mockActivatedRoute: MockActivatedRoute;
let mockRouter: MockRouter;
beforeEach(() => {
mockActivatedRoute = new MockActivatedRoute({'id': 1});
mockRouter = new MockRouter();
TestBed.configureTestingModule({
declarations: [ReviewComponent],
providers: [
{provide: ActivatedRoute, useValue: mockActivatedRoute},
{provide: Router, useValue: mockRouter}
],
imports: [AppModule]
}).compileComponents();
});
// beforeEach(async(() => {
//
// TestBed.configureTestingModule({
// declarations: [ ReviewComponent ],
// providers: [{provide: ActivatedRoute, useValue: activeRoute},
// {provide: Router, useValue: mockRouter}]
// })
// .compileComponents();
// }));
it('should create', () => {
const fixture = TestBed.createComponent(ReviewComponent);
fixture.detectChanges();
const editComponent = fixture.debugElement.componentInstance;
expect(editComponent).toBeTruthy();
});
// it('should have a review rating', () => {
// const compiled = fixture.debugElement;
// const title = compiled.query(By.css('.reviewRating'));
// expect(title).not.toBeNull();
// });
//
// it('should have a review product name', () => {
// const compiled = fixture.debugElement;
// const productName = compiled.query(By.css('.review-title'));
// expect(productName).not.toBeNull()
// });
//
// it('should have a header image', () => {
// const compiled = fixture.debugElement;
// const title = compiled.query(By.css('.reviewImageMain'));
// expect(title).not.toBeNull();
// });
});
I've tried a number of things to mock the object however as you can see from the log, for some reason the TestBed is not using the mock and is instead using import.
Can anyone help? I've lost so many hours today to this problem.
I almost forgot, the component looks like this:
import { Component, OnInit } from '#angular/core';
import {ActivatedRoute, Router} from "#angular/router";
import {OnDestroy} from "#angular/core/src/metadata/lifecycle_hooks";
#Component({
selector: 'app-review',
templateUrl: './review.component.html',
providers: [ActivatedRoute],
styleUrls: ['./review.component.css']
})
export class ReviewComponent implements OnInit, OnDestroy {
id: string;
private sub: any;
constructor(private route: ActivatedRoute, private router: Router){
}
cancel() {
this.router.navigate(['/search']);
}
ngOnInit() {
this.sub = this.route.params.subscribe(params => {
this.id = params['id'];
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
}
Got it :)
Wow, that was inconspicuous.
in review ReviewComponent in the providers I had included
providers: [ActivatedRoute]
This caused the the exception to happen, once I removed this line the test began to pass.
6 hours lost but bug found :)
import { Component, OnInit } from '#angular/core';
import {ActivatedRoute, Router} from "#angular/router";
import {OnDestroy} from "#angular/core/src/metadata/lifecycle_hooks";
#Component({
selector: 'app-review',
templateUrl: './review.component.html',
providers: [],
styleUrls: ['./review.component.css']
})
export class ReviewComponent implements OnInit, OnDestroy {
id: string;
private sub: any;
constructor(private route: ActivatedRoute, private router: Router){
}
cancel() {
this.router.navigate(['/search']);
}
ngOnInit() {
this.sub = this.route.params.subscribe(params => {
this.id = params['id'];
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
}

How Do I use Angular 4 Dialog Directives?

I am developing an Angular web application using:
Angular 4.1.2
Angular Material 2.0.0-beta.11
I am trying to create a simple modal dialog and on reading the Angular guide documents (Dialog | Angular Material) I see that there are several directives available to make it easier to structure the dialog content.
I cannot work out how to implement md-dialog-title, <md-dialog-content>, <md-dialog-actions> or md-dialog-close. The attributes, when applied to an element, appear to make no difference at all and the <md-dialog-content> and <md-dialog-actions> create errors like this:
Unhandled Promise rejection: Template parse errors: 'md-dialog-content' is not a known element:
Any guidance would be very welcome please. Here are some further details of my project:
For my initial development I have created an Angular module, named AngularMaterialModule to manage my Angular Materials. Here is a summary of it:
import { NgModule } from '#angular/core';
import {
MdAutocompleteModule,
MdButtonModule,
....
MdStepperModule,
StyleModule,
} from '#angular/material';
import { CdkTableModule } from '#angular/cdk/table';
import { A11yModule } from '#angular/cdk/a11y';
import { BidiModule } from '#angular/cdk/bidi';
import { OverlayModule } from '#angular/cdk/overlay';
import { PlatformModule } from '#angular/cdk/platform';
import { ObserversModule } from '#angular/cdk/observers';
import { PortalModule } from '#angular/cdk/portal';
#NgModule({
exports: [
// Material Modules
MdAutocompleteModule,
MdButtonModule,
....
PlatformModule,
PortalModule
],
declarations: []
})
export class AngularMaterialModule { }
My Visual Studio Solution was created using dotnet new angular taking advantage of the Microsoft ASP.Net SPA Templates.
In my app.module.client.ts file I import the AngularMaterialModule, described above, like this:
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { BrowserAnimationsModule } from '#angular/platform-browser/animations';
import { FormsModule } from '#angular/forms';
import { HttpModule } from '#angular/http';
import { AngularMaterialModule } from './core/angular-material.module';
import { sharedConfig } from './app.module.shared';
#NgModule({
bootstrap: sharedConfig.bootstrap,
declarations: sharedConfig.declarations,
imports: [
BrowserModule,
BrowserAnimationsModule,
AngularMaterialModule,
FormsModule,
HttpModule,
...sharedConfig.imports
],
providers: [
{ provide: 'ORIGIN_URL', useValue: location.origin }
]
})
export class AppModule {
}
You need to import the actual dialog module into the module you want to use it in.
import { MdDialogModule } from '#angular/material';
#NgModule({
imports: [
MdDialogModule
],
})
After that, it's a straight forward and follow the example in their docs
import {Component, Inject} from '#angular/core';
import {MdDialog, MdDialogRef, MD_DIALOG_DATA} from '#angular/material';
/**
* #title Dialog Overview
*/
#Component({
selector: 'dialog-overview-example',
templateUrl: 'dialog-overview-example.html'
})
export class DialogOverviewExample {
animal: string;
name: string;
constructor(public dialog: MdDialog) {}
openDialog(): void {
let dialogRef = this.dialog.open(DialogOverviewExampleDialog, {
width: '250px',
data: { name: this.name, animal: this.animal }
});
dialogRef.afterClosed().subscribe(result => {
console.log('The dialog was closed');
this.animal = result;
});
}
}
#Component({
selector: 'dialog-overview-example-dialog',
templateUrl: 'dialog-overview-example-dialog.html',
})
export class DialogOverviewExampleDialog {
constructor(
public dialogRef: MdDialogRef<DialogOverviewExampleDialog>,
#Inject(MD_DIALOG_DATA) public data: any) { }
onNoClick(): void {
this.dialogRef.close();
}
}
If you ever want to create a custom dialog component, you will have to add it into your entryComponents in the module.
import { MdDialogModule } from '#angular/material';
#NgModule({
entryComponents: [
AddressDialogComponent,
],
imports: [
MdDialogModule,
],
exports: [
AddressDialogComponent,
],
})

Jasmine testcase for angular2 router.navigate()

I am having a app.component.html file with button to navigate to another component called HomeComponent
<button (click)="nav()" id="nav">Navigate</button>
<router-outlet></router-outlet>
My app.component.ts file
import { Component } from '#angular/core';
import { RouterModule,Router, Routes } from '#angular/router';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app';
constructor(private router: Router){
}
nav(){
this.router.navigate(['/home']);
}
}
and my app.module.ts
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { RouterModule, Routes,Router } from '#angular/router';
#NgModule({
declarations: [
AppComponent,
HomeComponent
],
imports: [
BrowserModule,
RouterModule.forRoot([{ path: "", component: AppComponent}]),
RouterModule.forChild([{ path: "home", component: HomeComponent}])
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Here is my spec file app.component.spec.ts
import { TestBed, async, ComponentFixture, fakeAsync, tick,inject } from '#angular/core/testing';
import { By, BrowserModule } from '#angular/platform-browser';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { DebugElement } from '#angular/core';
import { RouterModule, Routes } from '#angular/router';
import { Router, RouterOutlet } from "#angular/router";
import { FormsModule } from "#angular/forms";
import { RouterTestingModule } from '#angular/router/testing';
import * as br from '#angular/platform-browser';
describe('Component:AppComponent', () => {
let location, router;
let mockRouter
beforeEach(() => {
mockRouter = {
navigate: jasmine.createSpy('navigate')
};
TestBed.configureTestingModule({
imports: [RouterTestingModule.withRoutes([
{ path: 'home', component: HomeComponent }
])],
declarations: [AppComponent, HomeComponent]
});
});
it('should go home', async(() => {
let fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
console.log(mockRouter.navigate);
let component = TestBed.createComponent(AppComponent).componentInstance;
component.nav();
spyOn(component, 'nav');
fixture.detectChanges();
expect(mockRouter.navigate).toHaveBeenCalledWith(['/home']);
}));
});
I am getting the result as
In order to create unit test case for router.navigate you can do something like this:
import { TestBed, async, ComponentFixture, fakeAsync, tick,inject } from '#angular/core/testing';
import { By, BrowserModule } from '#angular/platform-browser';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { DebugElement } from '#angular/core';
import { RouterModule, Routes } from '#angular/router';
import { Router, RouterOutlet } from "#angular/router";
import { FormsModule } from "#angular/forms";
import { RouterTestingModule } from '#angular/router/testing';
import * as br from '#angular/platform-browser';
describe('Component:AppComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule],
declarations: [AppComponent, HomeComponent]
});
});
it('should go home', async(inject([Router], (router) => {
spyOn(router, 'navigate'); //added a spy on router
let fixture = TestBed.createComponent(AppComponent);
let component = TestBed.createComponent(AppComponent).componentInstance;
component.nav();
expect(router.navigate).toHaveBeenCalled();
expect(router.navigate).toHaveBeenCalledWith(['/home']);
}));
});
You have spied on router but you haven't provided it to the testBed environment. Try adding
providers: [
{ provide: Router, useValue: mockRouter},
],
after declarations.
Also you need not include RouterTestingModule since you are not clicking any links.
References:
https://stackoverflow.com/a/46342813/8558515
Angular 2 Unit Testing - Cannot read property 'root' of undefined

error using isBrowser function in angular universal

I'm trying to use the isBrowser function in angular universal but I keep getting the same error when I build. I did install the npm package of angular-universal
npm i angular2-universal --save
'ng build -prod -aot'
ERROR in Illegal state: symbol without members expected, but got {"filePath":"C:/dir/universal/website/node_modules/#angular/platform-browser/platform-browser.d.ts","name":"__platform_browser_private__","members":["BROWSER_SANITIZATION_PROVIDERS"]}.
ERROR in ./src/main.ts
Module not found: Error: Can't resolve './$$_gendir/app/app.module.ngfactory' in 'C:/dir/universal/website\src'
# ./src/main.ts 4:0-74
# multi ./src/main.ts
this is my app.module.ts:
//modules
import { HomeModule } from './home/home.module';
import { IntakeFormulierModule } from './intake-formulier/intake-formulier.module';
import { BrowserModule } from '#angular/platform-browser';
import { UniversalModule } from 'angular2-universal/browser';
import { NgModule } from '#angular/core';
//routing
import { routing } from "./app.routing";
//pages
import { AppComponent } from './app.component';
//isbrowser
import { isBrowser } from 'angular2-universal';
#NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule.withServerTransition({
appId: 'website-u - (starter)'
}),
HomeModule,
IntakeFormulierModule,
routing,
],
providers: [
{ provide: 'isBrowser', useValue: isBrowser }
],
bootstrap: [AppComponent]
})
export class AppModule { }
this is my app/home/landing/landing.ts
import { Component, Inject } from '#angular/core';
import { Router } from '#angular/router';
#Component({
...
})
export class LandingComponent {
constructor(//public updateTop: TopImageUpdateService,
public router: Router,
#Inject('isBrowser') public isBrowser: boolean) {}
navigateToResults(name) {
if (this.isBrowser) {
let scrollToTop = window.setInterval(() => {
let pos = window.pageYOffset;
if (pos > 0) {
window.scrollTo(0, pos - 10); // how far to scroll on each step
} else {
window.clearInterval(scrollToTop);
this.router.navigate(['home/page', name]);
}
}, 9)
}
}
}
It looks like they are using isPlatformBrowser() instead of isBrowser() in the angular universal example.
https://github.com/angular/universal#universal-gotchas
import { Component, Inject, PLATFORM_ID, OnInit } from '#angular/core';
import { isPlatformBrowser, isPlatformServer } from '#angular/common';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements {
constructor(
#Inject(PLATFORM_ID) private platformId: Object
){
}
ngOnInit(){
if (isPlatformBrowser(this.platformId)) {
//Client only code.
} else {
//Server only code.
}
}
}

NativeScript handling back button event

I am trying to handle the hardware back button in a NativeScript app. I am using NativeScript version 2.3.0 with Angular.
Here is what I have in main.ts file
// this import should be first in order to load some required settings (like globals and reflect-metadata)
import { platformNativeScriptDynamic, NativeScriptModule } from "nativescript-angular/platform";
import { NgModule,Component,enableProdMode } from "#angular/core";
import { AppComponent } from "./app.component";
import { NativeScriptRouterModule } from "nativescript-angular/router";
import { routes, navigatableComponents } from "./app.routing";
import { secondComponent } from "./second.component";
import {AndroidApplication} from "application";
#Component({
selector: 'page-navigation-test',
template: `<page-router-outlet></page-router-outlet>`
})
export class PageNavigationApp {
}
#NgModule({
declarations: [AppComponent,PageNavigationApp,secondComponent
// ...navigatableComponents
],
bootstrap: [PageNavigationApp],
providers:[AndroidApplication],
imports: [NativeScriptModule,
NativeScriptRouterModule,
NativeScriptRouterModule.forRoot(routes)
],
})
class AppComponentModule {
constructor(private androidapplication:AndroidApplication){
this.androidapplication.on("activityBackPressed",()=>{
console.log("back pressed");
})
}
}
enableProdMode();
platformNativeScriptDynamic().bootstrapModule(AppComponentModule);
I am importing application with
import {AndroidApplication} from "application";
Then in the constrouctor of appComponentModule I am registering the event for activityBackPressed and just doing a console.log.
This does not work.
What am I missing here?
I'm using NativeScript with Angular as well and this seems to work quite nicely for me:
import { RouterExtensions } from "nativescript-angular";
import * as application from "tns-core-modules/application";
import { AndroidApplication, AndroidActivityBackPressedEventData } from "tns-core-modules/application";
export class HomeComponent implements OnInit {
constructor(private router: Router) {}
ngOnInit() {
if (application.android) {
application.android.on(AndroidApplication.activityBackPressedEvent, (data: AndroidActivityBackPressedEventData) => {
if (this.router.isActive("/articles", false)) {
data.cancel = true; // prevents default back button behavior
this.logout();
}
});
}
}
}
Note that hooking into the backPressedEvent is a global thingy so you'll need to check the page you're on and act accordingly, per the example above.
import { Component, OnInit } from "#angular/core";
import * as Toast from 'nativescript-toast';
import { Router } from "#angular/router";
import * as application from 'application';
#Component({
moduleId: module.id,
selector: 'app-main',
templateUrl: './main.component.html',
styleUrls: ['./main.component.css']
})
export class MainComponent {
tries: number = 0;
constructor(
private router: Router
) {
if (application.android) {
application.android.on(application.AndroidApplication.activityBackPressedEvent, (args: any) => {
if (this.router.url == '/main') {
args.cancel = (this.tries++ > 0) ? false : true;
if (args.cancel) Toast.makeText("Press again to exit", "long").show();
setTimeout(() => {
this.tries = 0;
}, 2000);
}
});
}
}
}
Normally you should have an android activity and declare the backpress function on that activity. Using AndroidApplication only is not enough. Try this code:
import {topmost} from "ui/frame";
import {AndroidApplication} from "application";
let activity = AndroidApplication.startActivity ||
AndroidApplication.foregroundActivity ||
topmost().android.currentActivity ||
topmost().android.activity;
activity.onBackPressed = function() {
// Your implementation
}
You can also take a look at this snippet for example
As far as I know, NativeScript has a built-in support for this but it's not documented at all.
Using onBackPressed callback, you can handle back button behaviour for View components (e.g. Frame, Page, BottomNavigation).
Example:
function pageLoaded(args) {
var page = args.object;
page.onBackPressed = function () {
console.log("Returning true will block back button default behaviour.");
return true;
};
page.bindingContext = homeViewModel;
}
exports.pageLoaded = pageLoaded;
What's tricky here is to find out which view handles back button press in your app. In my case, I used a TabView that contained pages but the TabView itself handled the event instead of current page.

Resources