【问题标题】:Angular 4 unsubscribe causes an error when navigating导航时 Angular 4 取消订阅会导致错误
【发布时间】:2019-03-24 19:48:32
【问题描述】:

我最近学会了避免内存泄漏,我需要在我的组件中取消订阅。为此,我实现了OnDestroy,但这在导航时引起了问题。

错误错误:未捕获(承诺):TypeError:无法读取未定义的属性“取消订阅” TypeError:无法读取未定义的属性“取消订阅”

如果我不退订导航成功完成。

我的印象是 ngOnDestroy 只有在其他所有事情都完成后才会发生,显然情况并非如此。

我显然做错了什么,但我不知道导航离开页面时如何取消订阅?

export class PropertyFormMapperComponent implements OnInit, OnDestroy {
    private _routeSubscription: any;
    private _getPropertySubscription: any;

    constructor(
        private _route: ActivatedRoute,
        private _propertyFormFileService: PropertyFormFileService,
        private _router: Router
    ) { }

    ngOnInit(): void {
        this._routeSubscription = this._route.params.subscribe(params => {
            this._id = params['id'];
            this._getPropertySubscription = this._propertyService.getProperty(this._id).subscribe(property => {
                ...do stuff...
            });
        });
    }

    ngOnDestroy(): void {
        this._routeSubscription.unsubscribe();
        this._getPropertySubscription.unsubscribe();
    }

    private onCreate(event: any): void {    
        this._propertyService.postProperty(property, 1).subscribe(
            response => {
                ...do stuff...
                this._router.navigate(['home']);
            },
            error => {
                ...other stuff...
            }
        );
    }

}

【问题讨论】:

  • 您能否将值记录到ngOnDestroy 中的控制台,以查找两个订阅中的哪一个未定义?
  • 嵌套的未定义 - _getPropertySubscription。在另一个订阅中订阅是一种糟糕的 rxjs 实践,也是初学者常犯的错误。您应该使用更高阶的 rxjs 方法组合流来组合流
  • 您可以在调用 unsubscribe... 之前检查是否定义了 subscription
  • @codeepic 谢谢你的建议,combine the streams using higher order rxjs methods 是什么意思,按照你的建议,我现在正在审查提到concatsyntaxsuccess.com/viewarticle/…,这是你的意思吗?跨度>
  • 是的,concatMap 更具体。您使用哪个版本的rxjs?我希望后一个,所以我的答案中的语法对你有用->较新版本的管道运算符函数而不是.do()使用.tap(),因为do是JavaScript中的保留字。

标签: angular rxjs angular4-router


【解决方案1】:

在这里,这个应该适合你。我使用concatMap 和投影函数组合了路由参数流和getProperty 流。您可以在 rxjs 文档中了解更多信息:rxjs concatMap

我还使用takeUntil,它在其他流发出时取消订阅它通过管道传输的流。 rxjs takeUntil

所以当componentDestroyed$ observable 发出时,因为takeUntil 将其作为参数,takeUntil 将取消订阅this._route.paramsthis._propertyService.getProperty 流。

我还添加了takeUntilonCreate 方法,因为你也在那里订阅。

takeUntil 是跟踪所有Subuscriptions 并在ngOnDestroy 中手动取消订阅它们的替代方法。使用takeUntil,我们可以实现相同的目标,但不必让组件与订阅属性混为一谈。

您可以更进一步,拥有一个所有组件类都继承自的基类 --> 然后您不必在每个组件中都保留componentDestroyed$,也可以跳过ngOnDestroy() 部分:

export abstract class BaseComponent implements OnDestroy {
    protected componentDestroyed$: Subject<void> = new Subject<void>();

    ngOnDestroy() {
        this.componentDestroyed$.next();
        this.componentDestroyed$.complete();
    }
}

您的组件现在将从BaseComponent 继承:

export class PropertyFormMapperComponent extends BaseComponent implements OnInit, OnDestroy

您的代码已修复:

import {Subscription} from 'rxjs';
import {tap, map, concatMap, takeUntil} from 'rxjs/operators';

export class PropertyFormMapperComponent implements OnInit, OnDestroy {
    componentDestroyed$: Subject<void> = new Subject<void>();

    constructor(
        private _route: ActivatedRoute,
        private _propertyFormFileService: PropertyFormFileService,
        private _router: Router
    ) { }

    ngOnInit() {
        this._route.params
            .pipe(
                map(params => params['id']),
                tap(id => this._id = id),
                concatMap(id => this._propertyService.getProperty(id))
                takeUntil(this.componentDestroyed$),
                tap(property => {
                   ...do stuff...
                   ... side effects are handled in tap functions     
                })
            )
            .subscribe();        
    }

    private onCreate(event) {    
        this._propertyService.postProperty(property, 1)
            .pipe(
                takeUntil(this.componentDestroyed$)
            )
            .subscribe(
                response => {
                    ...do stuff...
                    this._router.navigate(['home']);
                },
                error => {
                    ...other stuff...
                }
            );
    }

    ngOnDestroy() {
        this.componentDestroyed$.next();
        this.componentDestroyed$.complete();
    }
}

【讨论】:

  • 非常感谢您的帮助。我只是想根据一些非常人为的例子编写一个解决方案。我不确定我要花多长时间。在 rx 上有很多东西要学。再次感谢
  • 应该导入import {Subscription} from 'rxjs/Subscription'; 我的印象是导入'rxjs' 只会导入完整的库吗?
  • 是的,RxJS 确实很酷,但是自学很难。文档很难理解,旧文档完全不可读,所以它仍然是一个改进。 ;-D 观看有关该主题或 frontend-masters.com 的一些傻瓜视频。 angular-university.io 也有很好的视频和博客文章。
  • 如果你从rxjs 导入你会得到所有的,但最好只导入需要的东西。
  • 由于流的异步特性,我发现 ol' console.log 是最好的帮助。因此,在您开始映射并执行其他操作之前,请在 map(params =&gt; params['id']), 上方插入 tap(params =&gt; console.log('params are: ', params)), 以查看整个 params 对象。
猜你喜欢
  • 1970-01-01
  • 2015-01-29
  • 1970-01-01
  • 2022-11-04
  • 2018-08-23
  • 1970-01-01
  • 2019-02-11
  • 1970-01-01
  • 2019-06-12
相关资源
最近更新 更多