Angular 7的routerLink指令警告“Navigation triggered outside Angular zone”

24

我在使用Angular框架时遇到了一些问题,无法解决路由问题。我的顶层组件是AppComponentapp-routing.module.ts,通过我的自定义SlideMenuComponent进行导航管理。以下是AppComponent的简化HTML模板:

<app-slide-menu [buttons]="menuButtons"></app-slide-menu>
<router-outlet></router-outlet>

我的SlideMenuComponent的核心是以下HTML:

<nav><div *ngFor="let button of buttons">
    <a routerLink="{{button.routerLink}}"
    >{{button.name}}</a>
</div></nav>

一个用户可以通过这个滑动菜单导航到'/courses',由 CoursesComponent 监管,它对从服务器检索到的特定CourseComponent的链接进行分页。 这些组件位于其自己的courses.module.ts模块中,其中包含它们自己的courses-routing.module.ts。 但是,当我点击任何这些链接时,我会收到Navigation triggered outside Angular zone, did you forget to call 'ngZone.run()'?控制台警告,并且CourseCompontent在打开时没有调用ngOnInit(),而且在我点击页面上的任何按钮之前不会更新。 当我手动通过router.navigate()导航时,也遇到了此问题,解决方法是将此任务转发到NgZone.runTask(router.navigate()),但是为什么使用anchor标记和routerLink指令会发生这种情况呢?
以下是我的CoursesComponent代码摘录:
<nav><ul>
        <li *ngFor="let course of api.data | paginate: {
            currentPage: currentPage, itemsPerPage: limit, totalItems: api.total
        }">
           <a
               [routerLink]="course._id"
               [queryParams]="{ page: '1', limit: '5' }"
           >
            {{course.name}} ({{course.description}})
           </a>
        </li>
</ul></nav>

一个gif动画演示了这个问题:

enter image description here


你能试试这段代码吗?<nav><div *ngFor="let button of buttons"> <a [routerLink]="[button.routerLink]" >{{button.name</a>}}</div></nav> - Suresh Kumar Ariya
1
@Suresh 抱歉,可能有点误导,但这段代码运行良好,只是我的幻灯片菜单链接,虽然我尝试更改其插值类型,但没有有效的更改。当我点击 <a [routerLink]="course._id" [queryParams]="{ page: '1', limit: '5' }" > {{course.name}} ({{course.description}}) </a> 时出现问题。 - Veetaha
你可以尝试调用点击函数并使用navigateByURL作为其中一个选项,而不是使用routerlink。或者,你可以尝试给出绝对路径'/courses/id',而不是相对路径。 - Suresh Kumar Ariya
1
@Suresh 是的,我可以将其用作解决方法,但是为什么会发生这种情况呢? - Veetaha
奇怪的问题。你能展示一下分页管道以及你是如何解决 api.data 的吗? - Poul Kruijt
显示剩余6条评论
5个回答

40

看起来是一个 bug,请尝试这个解决方法

constructor(private ngZone: NgZone, private router: Router) {}

public navigate(commands: any[]): void {
    this.ngZone.run(() => this.router.navigate(commands)).then();
}

非常好用,谢谢。这个解决方案会有什么问题吗? - Sevyls
2
这让我出现了“ngZone未定义”的错误。 - Vincent Guyard
1
@VincentGuyard,将以下内容添加到你的导入中:import { Component,OnInit,...,NgZone } from '@angular/core'; - Vadim
1
应用程序中可能有多个组件,我需要在所有这些组件中都放置给定的代码吗? - vndpal
4
解决方法可行,但感觉像是一种hack,我无法弄清楚原因。 - Matjaz Hirsman
显示剩余2条评论

6
我们在浏览解决方案时遇到了这个错误。
在我们的侧边栏组件中,我们使用了on-push变化检测,并且在路由发生(参数改变)时有一个 this.ref.detectChanges(),这会导致路由出现问题(可能会将并行运行的解析器从ngZone中踢出或类似的情况)。 将其更改为常用的this.ref.markForCheck()后,此错误未再出现。 我很高兴我们不需要一个解决方法,尽管这种行为很奇怪..
希望这可以帮助任何遇到相同问题的人。

5

由于没有被接受的答案,而我在使用Angular 8时发现排名最高的答案并不完全有效,需要更多的解释。如果在*ngFor或其他地方注入组件,则可能会导致此问题。这与之前的答案非常接近,但是它声明了一个字符串参数到navigate函数中,然后调用navigateByUrl而不是navigate。

import { Component, NgZone } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  template: `<tag (click)="navigate(path)">clickable text</tag>`
})

constructor(private ngZone: NgZone, private router: Router) { }

public navigate(path: string): void {
  this.ngZone.run(() => this.router.navigateByUrl(path)).then();
}

您的情况可能会有所不同,但是对我来说这很完美地起作用了。

当我在我的Ionic 4(Angular)应用程序中尝试打开推送通知时,它没有任何结果,仍然抛出相同的警告并保留在主页面中,有任何想法吗? - Hamlet Minjares
@HamletMinjares - 没有想法。您确定您完全遵循了这个模式吗?(click)事件必须调用组件的导航函数,就像所示;它不能再使用routerLink。除此之外,在Ionic中,我认为这应该可以正常工作。我不知道为什么会有差异,但我在Ioninc方面没有太多经验。 - iGanja
抱歉,我之前使用了一个守卫类来在调用该方法之前进行验证,但没有注意到这个守卫没有使用 ng zone 进行重定向。在我纠正了这个问题之后,它就像魔法般地工作了,谢谢! - Hamlet Minjares
你的回答帮助了我解决了问题。尽管我使用的是 Angular 10 而不是 8,但它完美地工作了。 - tecnocrata
我也遇到了同样的问题,似乎与ag-grid呈现模板有关。我可以将其作为临时解决方案,但是我真的希望能够通过[routerLink]始终如一地工作,因为我希望它成为一个真正的链接,如果用户选择可以在另一个选项卡中打开。 - reads0520

1
对我来说,这个错误是因为代码的一部分在NgZone之外运行。之后我们运行了this.ref.detectChanges()以检测更改。检测到的更改会改变DOM并放置<a [routerLink]="..."></a>。这会破坏路由。 解决方案:我们使用this.ngZone.run将代码部分在NgZone之外但内部运行。这样,我们就不必使用ref.detectChanged(),因为更改(RxJS Subject订阅)在NgZone中。这解决了路由问题。 希望这能帮助任何遇到相同问题的人。

谢谢!这节省了我很多调试时间。 - Oleg Shchegolev

0

工作示例-请参考此处

 public backButtonSubscription;
      ngAfterViewInit(): void {
        let self = this;
        this.backButtonSubscription = this.platform.backButton.subscribe(() => {
          console.log("back preseed \n \n");
    
          self._ngZone.runOutsideAngular(() => {
            setTimeout(() => {
              self.Router.navigateByUrl("/stock-management");
            }, 100);
          });
        });
      }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接