Angular 5 add event before the route change
Asked Answered
U

6

20

I'm want to add a alert dialog before the user click on the <a href="..."> link.

There are 2 types of <a> link

  1. Redirect within Angular scope <a routerLink="/path/to/dest">
  2. Redirect outside of Angular app <a href="http://www.somewhere.com" target="_blank">

I want to able to show an alert box when user try to go outside of Angular scope

Alert dialog

I want to apply to all <a> click event (kind like pre-hook)

Any way to achieve this?

Unaesthetic answered 27/12, 2017 at 4:21 Comment(6)
you want show alert before click or after click ?Pyroclastic
@ArunKumaresh I want to show when the user click, but before navigate outUnaesthetic
@JsLim Is this like an advertisement as part of your app and do you want to create a modal (pop-up) before the user click this link?Lisette
Did you consider implementing a CanDeactivate route guard?Oven
@Lisette is not advertisement part, just navigate out to 3rd party website. Ya want to create a modal after user click on the link, but before it navigate outUnaesthetic
@ConnorsFan I'm quite new in Angular, I will try your suggestion. ThanksUnaesthetic
U
0

I achieve it by creating a component for <a>, confirmation dialog component, and service for the dialog

The confirm dialog

I'm using Angular Material

import { Component, Inject, Output, EventEmitter } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';

@Component({
  selector: 'confirm-dialog',
  templateUrl: './confirm-dialog.component.html',
})
export class ConfirmDialogComponent {

  constructor(
    public translate:TranslateService,
    public dialogRef: MatDialogRef<ConfirmDialogComponent>,
    @Inject(MAT_DIALOG_DATA) public data: any
    ) {
  }
  onClick(result): void {
    this.dialogRef.close(result);
  }

}

the html file

<h1 mat-dialog-title>{{data.title}}</h1>
<div mat-dialog-content>
    <h4 class="card-title">{{ data.message }}</h4>
</div>
<div mat-dialog-actions class="pull-right">
    <a *ngIf="data.confirm_link" class="btn btn-primary" mat-button tabindex="-1" href="{{ data.confirm_link }}" target="_blank" (click)="onClick(true)">{{ data.confirm_button }}</a>
    <button *ngIf="!data.confirm_link" class="btn btn-primary" mat-button tabindex="-1" (click)="onClick(true)"> {{ data.confirm_button }} </button>
    <button class="btn btn-info" mat-button tabindex="-1" (click)="onClick(false)">Cancel</button>
</div>

The service

Once the component is created, I want to make it easy to call from anywhere, so create a service for it

import { Injectable, OnDestroy} from "@angular/core";
import { Subject } from 'rxjs/Subject';
import { MatDialog } from '@angular/material';
import { ConfirmDialogComponent } from 'path/to/confirm-dialog/confirm-dialog.component';
import * as _ from 'lodash';

@Injectable()
export class ConfirmService implements OnDestroy{
    private subject = new Subject<any>();
    private message = 1;
    info: any;
    constructor(private dialog: MatDialog){
    }
    show(data: any){
        let dialogRef = this.dialog.open(ConfirmDialogComponent, {
          width: '500px',
          data: data,
        });

        dialogRef.afterClosed().subscribe(result => {
          this.subject.next(result);
        });
        return this.subject;
    }
    ngOnDestroy() {

    }
}

The custom <a> element

To make it easier to use in .html file, I create a component for it

import { Component, OnInit, Input } from '@angular/core';
import { ConfirmService } from 'path/to/service/confirm.service';

@Component({
  selector: 'a-external',
  templateUrl: './a-external.component.html',
})
export class AExternalComponent implements OnInit {
  @Input('href') href: string;
  @Input('class') classes: string;
  @Input('content') content: string;

  constructor(
    private confirmService:ConfirmService,
  ) { }

  ngOnInit() {
  }

  onAClick() {
    var dialog = this.confirmService.show({
      'title': 'Warning',
      'message': 'This will open a new tab',
      'confirm_button': 'open',
      'confirm_link': this.href, // if pass in the uri, will open in new tab
    });
    var subscription = dialog.subscribe((result) => {
      // if the result is true, means Confirm button is clicked
      // if the result is false, means Cancel button is clicked
      subscription.unsubscribe();
    });
  }
}

The confirm_link is only applicable for open a new tab. Without the value, it will just trigger the dialog subscription result.

And the html file is very simple

<a href="javascript:" class="{{ classes }}" (click)="onAClick()">{{ content }}</a>

To use it

<a-external [href]="http://www.foobar.com" [class]="'btn btn-info'" [content]="'The content inside a element'"></a-external>
Unaesthetic answered 28/12, 2017 at 2:7 Comment(0)
O
15

For links to other views of your Angular application, you can implement a CanDeactivate route guard. You will find an example in this stackblitz, for the "Home" page.

The links that navigate outside of the application should trigger the event handler bound to window:beforeunload (shown in HomeViewComponent below). However, its behavior seems to be different in Firefox (a confirmation box is shown) and in Chrome (no confirmation box shown). That event cannot be tested with stackblitz, as far as I can see.


In app.module:

...
import { AppRoutingModule } from './app.routing.module';
import { DeactivateGuard } from './views/home/deactivate-guard';

@NgModule({
  imports: [ 
    AppRoutingModule, 
    ... 
  ],
  providers: [
    DeactivateGuard
  ],
  ...
})

In app.routing.module:

...
import { RouterModule } from '@angular/router';
import { DeactivateGuard } from './views/home/deactivate-guard';

@NgModule({
  imports: [
    RouterModule.forRoot([
      ...
      {
        path: 'home',
        component: HomeViewComponent,
        canDeactivate: [DeactivateGuard]
      },
      ...
    ])
  ],
  exports: [
    RouterModule,
  ],
  ... 
})

In home/deactivate-guard:

import { CanDeactivate } from '@angular/router';
import { HomeViewComponent } from './home.component';

export class DeactivateGuard implements CanDeactivate<HomeViewComponent> {

  canDeactivate(component: HomeViewComponent) {
    return component.canDeactivate();
  }
}

In home.component:

import { Component, HostListener } from '@angular/core';
...

@Component({
  ...
})
export class HomeViewComponent {

  @HostListener("window:beforeunload", ["$event"]) unloadHandler(event: Event) {
      event.returnValue = false;
  }

  canDeactivate() {
    return confirm("Do you want to leave?");
  }

  ...
}
Oven answered 27/12, 2017 at 6:21 Comment(0)
F
3

so Angular provides canActivate to make sure if you want to activate the route or not based on certain condition. You can

const routes: Routes = [
    {path: '/some-path', canActivate:[AuthGuard]}
];

Your canActivate service

import { Injectable } from '@angular/core';
import { CanActivate, CanActivateChild } from '@angular/router';

@Injectable()
export class AuthGuard implements CanActivate, CanActivateChild {

  canActivate() {
    //ask if he really wants to route.
    console.log('i am checking to see if you are logged ')
    return true;
  }

  canActivateChild() {
    console.log('checking child route access');
    return true;
  }

}

In the canActivate you can display a generic model to ask whether he wants to route to URL or not, and based on that you can control which link can have it and which not. You can even write logic for all the routing whether it be coming from anchor tag or anything else.

Firstling answered 27/12, 2017 at 5:15 Comment(0)
I
3

You can implement route guard which checks for your condition and then decide whether to redirect to clicked url or not depending upon your choice.

If you are following angular cli then you can simply install route guard by running :

ng g guard my-new-guard

Import guard file in app.module.ts and add it into providers array. In routing file add route guard to the paths on which you want to check for the condition. Like :

const appRoutes: Routes = [
    {path: '/your-path', canActivate: [route-guard]}
];

In your route-guard file you can implement your logic like this :

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot} from '@angular/router';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class AuthGuardGuard implements CanActivate {
    canActivate(
        next: ActivatedRouteSnapshot,
        state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {

            if(!state.url.startsWith('/')){
               // I have check here for starting single slash for all your angular local routes. You can also check for http or https whichever you want according to your need
               // here you can trigger your modal pop-up on its 'OK' button return true to redirect to the url
               return true;   // or return false on 'Cancel' button of modal pop-up for cancelling route if condition doesn't fullfill
            }
    }
}
Imogen answered 27/12, 2017 at 6:45 Comment(0)
P
0

Try this

In html

<a role="button" (click)="yourfunc()">

In your ts

yourfunc(){

   alert('navigate')

  window.location.href='http://www.somewhere.com';

  // your code to navigate

  }
Pyroclastic answered 27/12, 2017 at 4:44 Comment(1)
Thanks, but this only applicable to 1 link, I want to intercept all link before it navigate outUnaesthetic
I
0

In .ts File

ngAfterViewInit() {
    var aElements = this._elementRef.nativeElement.querySelectorAll('a');
    var aElementsLen = aElements.length;
    console.log('aElements==========>', aElements);
    for(let i=0; i< aElementsLen; i++){
        console.log('aElements[i]==========>', aElements[i]);
        aElements[i].addEventListener('click',  function(e){
            e.preventDefault();
            //return true; // If Redirect inside of Angular app
            return false; // Redirect outside of Angular app and show popup
        });
    }
}

Plnkr link

Illbehaved answered 27/12, 2017 at 6:51 Comment(0)
U
0

I achieve it by creating a component for <a>, confirmation dialog component, and service for the dialog

The confirm dialog

I'm using Angular Material

import { Component, Inject, Output, EventEmitter } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';

@Component({
  selector: 'confirm-dialog',
  templateUrl: './confirm-dialog.component.html',
})
export class ConfirmDialogComponent {

  constructor(
    public translate:TranslateService,
    public dialogRef: MatDialogRef<ConfirmDialogComponent>,
    @Inject(MAT_DIALOG_DATA) public data: any
    ) {
  }
  onClick(result): void {
    this.dialogRef.close(result);
  }

}

the html file

<h1 mat-dialog-title>{{data.title}}</h1>
<div mat-dialog-content>
    <h4 class="card-title">{{ data.message }}</h4>
</div>
<div mat-dialog-actions class="pull-right">
    <a *ngIf="data.confirm_link" class="btn btn-primary" mat-button tabindex="-1" href="{{ data.confirm_link }}" target="_blank" (click)="onClick(true)">{{ data.confirm_button }}</a>
    <button *ngIf="!data.confirm_link" class="btn btn-primary" mat-button tabindex="-1" (click)="onClick(true)"> {{ data.confirm_button }} </button>
    <button class="btn btn-info" mat-button tabindex="-1" (click)="onClick(false)">Cancel</button>
</div>

The service

Once the component is created, I want to make it easy to call from anywhere, so create a service for it

import { Injectable, OnDestroy} from "@angular/core";
import { Subject } from 'rxjs/Subject';
import { MatDialog } from '@angular/material';
import { ConfirmDialogComponent } from 'path/to/confirm-dialog/confirm-dialog.component';
import * as _ from 'lodash';

@Injectable()
export class ConfirmService implements OnDestroy{
    private subject = new Subject<any>();
    private message = 1;
    info: any;
    constructor(private dialog: MatDialog){
    }
    show(data: any){
        let dialogRef = this.dialog.open(ConfirmDialogComponent, {
          width: '500px',
          data: data,
        });

        dialogRef.afterClosed().subscribe(result => {
          this.subject.next(result);
        });
        return this.subject;
    }
    ngOnDestroy() {

    }
}

The custom <a> element

To make it easier to use in .html file, I create a component for it

import { Component, OnInit, Input } from '@angular/core';
import { ConfirmService } from 'path/to/service/confirm.service';

@Component({
  selector: 'a-external',
  templateUrl: './a-external.component.html',
})
export class AExternalComponent implements OnInit {
  @Input('href') href: string;
  @Input('class') classes: string;
  @Input('content') content: string;

  constructor(
    private confirmService:ConfirmService,
  ) { }

  ngOnInit() {
  }

  onAClick() {
    var dialog = this.confirmService.show({
      'title': 'Warning',
      'message': 'This will open a new tab',
      'confirm_button': 'open',
      'confirm_link': this.href, // if pass in the uri, will open in new tab
    });
    var subscription = dialog.subscribe((result) => {
      // if the result is true, means Confirm button is clicked
      // if the result is false, means Cancel button is clicked
      subscription.unsubscribe();
    });
  }
}

The confirm_link is only applicable for open a new tab. Without the value, it will just trigger the dialog subscription result.

And the html file is very simple

<a href="javascript:" class="{{ classes }}" (click)="onAClick()">{{ content }}</a>

To use it

<a-external [href]="http://www.foobar.com" [class]="'btn btn-info'" [content]="'The content inside a element'"></a-external>
Unaesthetic answered 28/12, 2017 at 2:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.