Angular 5 在路线改变前添加事件

Angular 5 add event before the route change

我想在用户点击 <a href="..."> link 之前添加一个警告对话框。

有两种<a>link

  1. 在 Angular 范围内重定向 <a routerLink="/path/to/dest">
  2. 在 Angular 应用之外重定向 <a href="http://www.somewhere.com" target="_blank">

我希望能够在用户尝试超出 Angular 范围时显示警告框

我想应用到所有<a>点击事件(有点像pre-hook)

有什么方法可以实现吗?

试试这个

在html

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

在你的 ts

yourfunc(){

   alert('navigate')

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

  // your code to navigate

  }

所以 Angular 提供了 canActivate 来确定是否要根据特定条件激活路由。你可以

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

您的 canActivate 服务

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;
  }

}

在 canActivate 中你可以显示一个通用模型来询问他是否要路由到 URL,并基于此你可以控制哪些 link 可以拥有它,哪些不可以。 您甚至可以为所有路由编写逻辑,无论它来自锚标记还是其他任何东西。

要链接到 Angular 应用程序的其他视图,您可以为 "Home" 页面实施 CanDeactivate route guard. You will find an example in this stackblitz

在应用程序外部导航的链接应触发绑定到 window:beforeunload 的事件处理程序(在下面的 HomeViewComponent 中显示)。但是,它在 Firefox(显示确认框)和 Chrome(不显示确认框)中的行为似乎有所不同。据我所知,该事件无法使用 stackblitz 进行测试。


在app.module中:

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

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

在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,
  ],
  ... 
})

在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();
  }
}

在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?");
  }

  ...
}

您可以实施路由守卫来检查您的情况,然后根据您的选择决定是否重定向到已点击的 url。

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
            }
    }
}

在 .ts 文件中

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

我通过为 <a> 创建组件、确认对话框组件和对话框服务来实现它

确认对话框

我正在使用 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);
  }

}

html 文件

<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>

服务

创建组件后,我想方便地从任何地方调用它,因此为它创建一个服务

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() {

    }
}

自定义<a>元素

为了方便在.html文件中使用,我为其创建了一个组件

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();
    });
  }
}

confirm_link仅适用于打开新标签页。没有该值,只会触发对话订阅结果。

而且html文件非常简单

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

使用它

<a-external [href]="http://www.foobar.com" [class]="'btn btn-info'" [content]="'The content inside a element'"></a-external>