Angular区域外触发了导航,您是否忘记调用'ngZone.run()'?

125

我正在使用Angular 7并遇到一个问题 => 登录后API GET调用成功,组件也接收到了数据,但UI没有显示那些数据。

当我打开浏览器控制台时,立即在UI上填充数据,并显示一个警告。

"core.js:15686:导航触发Angular区域外部,您是否忘记调用'ngZone.run()'?"

我已经搜索了这个警告并找到了一些解决方法,例如this.ngZone.run(),并在其中调用我的API。

但问题是,我正在使用超过40个组件并在每个组件中调用许多API。因此,我必须在每个API调用上调用ngZone.run(),这似乎很难做到。

如何更好地解决这个问题?

app.component.ts

getEmployees(): void {
    this.employeeService.getEmployees().subscribe(e => {
        this.employees = e;
    });
}

app.service.ts

@Injectable()
export class EmployeeService {
    constructor(private httpClient: HttpClient) { }

    getEmployees() {
        return this.httpClient.get<EmployeeModel[]>('employees');
    }

请分享您相关的组件/服务代码。 - Ravinder Payal
它没有达到预期的解决目的。请分享 getEmployees 的引导代码。你从哪里调用 getEmployees(): void - Ravinder Payal
在组件的ngOnInit()方法中调用它。 - Er Vipin Sharma
我也遇到了这个错误信息。我会开另一个帖子,看看我的情况是否更清楚。 - AlanObject
代码片段中没有展示路由的使用,这是导致提供的消息出现问题的部分。单独的 HTTP 调用不会引起此问题。当在 app.component.ts 中设置 employees 时是否发生了任何有趣的事情? - André Werlang
显示剩余3条评论
7个回答

123

通常这种情况发生在您将angular调用包装在一些外部js回调函数内时,这些函数来自于与angular代码无关的外部JavaScript。

例如app.component.ts

callMyCustomJsLibrary() {
  googleSdk.getLocations(location => this.getEmployees());
}

getEmployees(): void {
    this.employeeService.getEmployees().subscribe(e => {
        this.employees = e;
    });
}
在这种情况下,您需要将调用包含在NgZone中,例如:this.ngZone.run(() => this.getEmployees());。然后app.component.ts看起来将如下所示:
callMyCustomJsLibrary() {
  googleSdk.getLocations(location => this.ngZone.run(() => this.getEmployees()));
}

getEmployees(): void {
    this.employeeService.getEmployees().subscribe(e => {
        this.employees = e;
    });
}

1
在我的情况下,这发生在由Angular组件创建的rxjs订阅中。 - m1ld
2
非常感谢,我一直在和 Razorpay 集成回调遇到问题,什么都没用。 - Prince Kumar Sharma
4
感谢您的留言!需要在构造函数中添加“private zone: NgZone”,但很好,谢谢! - Nek
@PrinceKumarSharma,你解决了razorpay的问题吗?我也遇到了同样的问题,我发现这可能是因为当弹出窗口打开时覆盖整个屏幕的背景div在手动关闭弹出窗口时被留下了。你是如何去掉它的? - Moiz Tankiwala

64

通过将路由导航命令包装在ngZone中,我的问题得到了解决。请查看下面的代码

在构造函数中添加 "private zone: NgZone"。

private zone: NgZone

this.zone.run(() => {
                    this.router.navigate(['/login']);
                });

16
如果我从上面说起,那么这个警告的意思是事件在区域外触发。Angular使用“zones”,也称为“执行上下文”来进行“变更检测和UI渲染”。因此,在“导航”时,Angular期望重新渲染UI,但这里并没有发生。这就是你所面临的问题。因此,当执行的代码位于Angular zone内部时,它会抛出此警告,因为Angular正在处理变更检测和UI渲染。
但实际上,当你试图在“subscribe”方法内调用“路由导航”时,就会出现这个警告。
如果你将“路由导航”调用放在“subscribe”方法之外,那么它就不会再次抛出这个警告,并且在导航后你将看到预期的UI。
有关“zones”的更多信息,请阅读和观看下面提到的文章和视频:

https://blog.thoughtram.io/angular/2017/02/21/using-zones-in-angular-for-better-performance.html#running-outside-angulars-zone

https://www.youtube.com/watch?v=3IqtmUscE_U&t=150


7
我在subscribe()方法块内部调用了router.navigate()。当你说将其放到外面时,确切的位置是哪里? - Stephane
1
对我来说,升级到Angular 8后,在错误处理程序内部调用现有路由器会触发“在Angular区域外触发导航”错误。我不得不在自定义错误处理程序块errorService.log(error).subscribe(errorWithContextInfo => {中删除router.navigate()调用。 - Stephane

13

我有同样的问题。这个对我有用,你有两种方式:

第一种形式:


(I had the same problem. This worked for me, you have two ways:First form:)
document.location.href=`/component/${param}/...`

问题在于这会重新加载整个应用程序,从而使其变得更慢。

第二种方法:

导入这个。

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

构造函数:

 constructor(private _ngZone: NgZone, private _router: Router) { }


  goTo(comp, param){
    this._ngZone.run(()=>{
    this._router.navigate([comp, param])
    });
  }

所以当您想要访问您的组件时,只需:

this.goTo("/myComponent", "my-param-value");

我希望这对你有效


精确而简单的解决方案。非常感谢。 - Aneeez

6
在我的情况下,问题是由一个测试触发的,该测试调用路由器以确保被测试的组件对路由更改做出正确反应。我的测试代码大致如下:
const router: Router = TestBed.inject(Router);
await router.navigateByUrl('some-route');

注入NgZone并包装路由器调用是解决方案。
const router: Router = TestBed.inject(Router);
const ngZone: NgZone = TestBed.inject(NgZone);
await ngZone.run(async () => router.navigateByUrl('some-route'));

如果您直接测试触发导航的组件方法,则也需要这样做:

const ngZone: NgZone = ngZone = TestBed.inject(NgZone);
ngZone.run(() => component.someMethod()); // someMethod() triggers a router navigation

0

可行的解决方案 - 修复

 ngAfterViewInit(): void {
    let self = this;
      this.platform.backButton.subscribe(() => {
      console.log("back preseed \n \n");

      self._ngZone.runOutsideAngular(() => {
        setTimeout(() => {
          self.Router.navigateByUrl("/stock-management");
        }, 100);
      });
    });
  }

-8

如果您尝试注入自定义服务,则会发生这种情况:

constructor(private myService: MyService) {
}

当你忘记在模块配置中提供它时:

@NgModule({
  providers: [
    MyService
  ]

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