Reputation: 533
I wanted to stop navigation from one page to another page when user clicks on ion-back-button
. I have some validation/check to be perform based on which app will decide whether to allow back action or not.
Upvotes: 14
Views: 11103
Reputation: 3191
There is another alternative that may result in more natural
solution using Angular events:)
All we need to do create a Directive
that uses @Host
and IonBackButtonDelegate
to override the built-in ionic ion-back-button
directive itself, and fire a new backClicked: InterceptedEvent
if there are any observers listening for it.
Technically we don't need to fire a custom event/type, we could just fire the event itself, then check for event.defaultPrevented
before executing the click handler, but... the approach below supports async
event listeners via the callback pattern.
import { AfterViewInit, Directive, EventEmitter, Host, HostListener, Output } from '@angular/core';
import { IonBackButtonDelegate } from '@ionic/angular';
import { InterceptedEvent } from '../../model';
export interface InterceptedEvent {
native: Event;
proceed: () => void;
}
/**
* Overrides the default ion-back-button directive.
* @see https://github.com/ionic-team/ionic-framework/blob/main/angular/src/directives/navigation/ion-back-button.ts
*/
@Directive({
selector: 'ion-back-button',
})
export class IonBackButtonOverrideDirective implements AfterViewInit {
@Output()
backClicked = new EventEmitter<InterceptedEvent>();
private delegate: (ev: Event) => void;
constructor(
@Host() private host: IonBackButtonDelegate
) { }
ngAfterViewInit(): void {
if (this.backClicked.observed) {
// This is where we wrap/delegate the default behavior of the Ionic directive
this.delegate = this.host.onClick;
this.host.onClick = () => { };
}
}
@HostListener('click', ['$event'])
async onClick(event: Event) {
let executed = false;
this.backClicked.emit({
native: event,
proceed: () => {
if (!executed) {
executed = true;
this.delegate.apply(this.host, [event]);
}
}
});
}
}
And of course you need to make sure you properly register this in your Module.
@NgModule({
declarations: [IonBackButtonOverrideDirective],
...
Your consuming code will need to use the new (backClicked)
event:
<ion-back-button (backClicked)="onBackClicked($event)"></ion-back-button>
And you will need to call InterceptedEvent.proceed()
if you want the back button to do its original work:
handleBackClicked(event: InterceptedEvent) {
if (ignore event) {
... do your work here
} else {
event.proceed();
}
}
Done! Everything is type-safe and there's no need to use other ionic lifecycle events.
There are a lot of other ways to structure the event, like using event.preventDefault()
itself to avoid the need to call proceed()
, but the callback approach supported the most use-cases, such as async
methods, etc, that I needed.
I'm sure there's a cleaner way. Feedback appreciated!
Upvotes: 1
Reputation: 795
Use IonBackButtonDelegate
to override the functionality. Here's a simple example -
import { IonBackButtonDelegate } from '@ionic/angular';
...
export class TestPage {
@ViewChild(IonBackButtonDelegate, { static: false }) backButton: IonBackButtonDelegate;
...
// Registering
ionViewDidEnter() {
console.log('ionViewDidEnter');
this.setUIBackButtonAction();
}
setUIBackButtonAction() {
this.backButton.onClick = () => {
// handle custom action here
};
}
}
Upvotes: 28
Reputation: 71
I could not find a controller-only solution (ionViewCanLeave was not firing) - my current hack is as below. It conditionally prevents/overrides ion-back-button, while still animating the button on click.
HTML:
<ion-back-button defaultHref="/" routerDirection="back">
<div (click)="back($event)"></div>
</ion-back-button>
SCSS:
ion-back-button {
& > div {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 1;
}
}
TS:
public back($event) {
if (shouldPreventBack)
$event.stopPropagation();
}
Upvotes: 1
Reputation: 504
This can be accomplished by Ionic Life Cyle Hooks
ionViewDidLoad: Fired only when a view is stored in memory. This event is NOT fired on entering a view that is already cached. It’s a nice place for init related tasks. ionViewWillEnter: It’s fired when entering a page, before it becomes the active one. Use it for tasks you want to do every time you enter in the view (setting event listeners, updating a table, etc.).
ionViewDidEnter: Fired when entering a page, after it becomes the active page. Quite similar to the previous one.
ionViewWillLeave: Fired when you leave a page, before it stops being the active one. Use it for things you need to run every time you are leaving a page (deactivate event listeners, etc.).
ionViewDidLeave: Fired when you leave a page, after it stops being the active one. Similar to the previous one.
ionViewWillUnload: Fired when a view is going to be completely removed (after leaving a non-cached view).
As a bonus track, there are two other powerful methods related to those events: nav guards. Those methods are focused on view access control (with authentication purposes).
Nav Guards If you wanted to prevent a user from leaving a view:
export class MyClass{
constructor(
public navCtrl: NavController
){}
pushPage(){
this.navCtrl.push(DetailPage);
}
ionViewCanLeave(): boolean{
// here we can either return true or false
// depending on if we want to leave this view
if(isValid(randomValue)){
return true;
} else {
return false;
}
}
}
ionViewCanEnter: Fired before entering into a view, allows you to control whether the view can be accessed or not (returning true or false).
ionViewCanLeave: Fired before leaving a view, allows you to control whether the view can be left or not.
It is important to highlight that Nav Guards are executed before any other lifecycle event method.
Upvotes: 4
Reputation: 21681
Try this:
HTML:
<ion-buttons slot="left">
<ion-back-button (click)="BackButtonAction()">Back</ion-back-button>
</ion-buttons>
TS:
BackButtonAction(){
//action to be performed on back button
}
Upvotes: -2