【问题标题】:Angular 2 - FormGroup ValueChanges UnsubscribeAngular 2 - FormGroup ValueChanges 退订
【发布时间】:2017-10-06 01:27:58
【问题描述】:

我有一个带有 ValueChanges 事件的 FormGroup,当用户从组件的路由移动到另一个组件然后他们返回到该组件时,该事件没有从内存中释放。

这意味着,如果用户离开组件然后返回组件 5 次,onFormChange 方法会触发 5 次,但其中只有 1 次调用是针对当前组件的。

我认为问题在于我需要取消订阅 NgDestroy 事件中的 valueChanges 事件,但 valueChanges 事件中没有可用的取消订阅方法。

我确定我必须取消订阅或释放内存,但我不确定是什么。

import * as _ from 'lodash';
import {Observable} from 'rxjs/Rx';

import {Component, Input, Output, EventEmitter, OnInit, OnDestroy} from '@angular/core';
import {FormGroup} from '@angular/forms';

import {formConfig} from './toolbar.form-config';
import {JobToolbarVm} from '../view-model/job-toolbar.vm';
import {BroadcastService} from '../../../services/broadcast/broadcast.service';

@Component({
    selector: 'wk-job-toolbar',
    template: require('./toolbar.html'),
})
export class JobToolbarComponent implements OnInit, OnDestroy {

  protected form: FormGroup;

  @Input()
  toolbar: JobToolbarVm;

  @Output()
  toolbarChanged = new EventEmitter<JobToolbarVm>();

  @Output()
  refresh = new EventEmitter<string>();

  constructor(private broadcast: BroadcastService) {
  }

  ngOnInit() {

    this.form = formConfig;
    this.form.setValue(this.toolbar, {onlySelf: true});

    // This ALWAYS RUNS when the form loads, ie. on the job route
    console.log('FORM VALUE');
    console.log(JSON.stringify(this.form.value, null, 2));

    this.form.valueChanges
      .debounceTime(2000)
      .subscribe(
        this.onFormChange.bind(this)
      );
  }

  ngOnDestroy() {
    //this.form.valueChanges.unsubscribe();
    //this.onChanges.unsubscribe();
    //this.toolbarChanged.unsubscribe();
    //this.form = null;
  }

  onFormChange(data: any) {
    // This runs whenever I go to a different route and then come back to this route
    // There is also a memory leak, because this method fires multiple times based on how
    // often I navigate away and come back to this route.
    // e.g. Navigate away and then back 5 times, then I see this log statement 5 times 
    console.log('FORM VALUE2 - THIS KEEPS FIRING FOR EACH INSTANCE OF MY COMPOMENT');
    console.log(JSON.stringify(this.form.value, null, 2));

    JobToolbarVm.fromJsonIntoInstance(data, this.toolbar);

    this.onChanges('data-changed');
  }

  onChanges($event: any) {
    console.log('onChanges: ' + $event);
    // console.log(this.toolbar);

    // Send the toolbar object back out to the parent
    this.toolbarChanged.emit(this.toolbar);

    // Broadcast an event that will be listened to by the list component so that it knows when to refresh the list
    this.broadcast.broadcast('job-admin-toolbar-changed', this.toolbar);
  }
}

【问题讨论】:

    标签: angular rxjs angular2-forms


    【解决方案1】:

    您可以执行以下操作:

    // private variable to hold all your subscriptions for the component
    private subscriptions: Subscription[] = [];
    
    // when you subscribe to an observable,
    // you can push all subscription this.subscriptions
    
    this.subscriptions.push(
           this.form.valueChanges.pipe(
                .debounceTime(2000)) 
                .subscribe(val => this.onFormChange.bind(this)),
    
           this.observe2$.subscribe(val => this.somemethod(val))
        );
    
    // in ngondestroy
        ngOnDestroy(): void {
            if (this.subscriptions && this.subscriptions.length > 0) {
                this.subscriptions.forEach(s => s.unsubscribe());
            }
        }
    
    

    【讨论】:

      【解决方案2】:

      我不确定这是否是一个好主意,但这很容易实现,并且非常适合我的学校项目。

      var sub = this.items.subscribe(snapshots => {
          console.log(snapshots);
          sub.unsubscribe();
      });
      

      来源:https://github.com/angular/angularfire2/issues/377

      【讨论】:

        【解决方案3】:

        我已经创建了以下函数

        export function AutoUnsubscribe(exclude = []) {
        
            return function (constructor) {
        
                const original = constructor.prototype.ngOnDestroy;
        
                constructor.prototype.ngOnDestroy = function () {
                    for (let prop in this) {
                        const property = this[prop];
                        if (!exclude.includes(prop)) {
                            if (property && (typeof property.unsubscribe === "function")) {
                                property.unsubscribe();
                            }
                        }
                    }
                    original && typeof original === 'function' && original.apply(this, arguments);
                };
            }
        
        }
        

        实际上您可以使用它来自动取消订阅所有观察者,但您必须将它们存储在公共属性中,以便该函数可以拦截它并在其上调用取消订阅。您如何使用它如下所述:-

        @AutoUnsubscribe()
        @Component({
            selector: 'account-login',
            templateUrl: './login.component.html',
            styleUrls: ['./login.component.scss']
        })
        export class LoginComponent implements OnInit {
        
        
            public submitWatcher: Subscription;
        
             submit() {
                this.submitWatcher = this.authService.login(this.loginForm.getRawValue())
                    .subscribe(res => {
                        if (this.returnUrl) {
                            this.router.navigate([this.returnUrl]);
                        }
                        else {
                            this.router.navigate(['/special']);
                        }
                    }, (error) => {
                        alert(JSON.stringify(error.data));
                    });
            }
        
        }
        

        有关如何使用装饰器的更多信息,请阅读这个博客,这是我的灵感来源,它非常酷

        Blog

        【讨论】:

        • Atul.. 我喜欢自定义装饰器的想法......但在这种情况下,使用 take、takeWhile 和 takeUntil 运算符更有意义。使用上述装饰器并不是退订的有效方式……你怎么看?
        【解决方案4】:

        subscribe() 调用返回一个Subscription,这是您用来取消订阅的:

        class JobToolbarComponent
        
          private subscr:Subscription;
        
          ngOnInit() {
            ...
            this.subscr = this.form.valueChanges ...
            ...
          }
        
          ngOnDestroy() {
            this.subscr.unsubscribe();
          }
        }
        

        【讨论】:

        • 您好!非常感谢您的回答。我不断地阅读它们。订阅了你的 github。但是你能解释一下为什么我们需要取消订阅,如果组件和表单自己销毁,垃圾收集器不应该从内存中删除订阅吗?
        • 谢谢 :-) 如果您强制订阅,通常最好强制取消订阅。也可能是垃圾收集不会立即处理组件实例,然后它可能仍会收到一些事件,直到它最终消失了。订阅回调可能会在组件不再处于活动状态时导致错误,或者可能导致在释放组件时不再需要的昂贵操作(网络请求)。
        • @GünterZöchbauer 好的。谢谢!;)
        • 当组件中创建了this.form,在组件销毁后,哪个事件可以触发ValueChange?我无法想象需要取消订阅自己创建的表单的情况。如果unsubscribe 可以在debounceTime 之后停止流,那也会很有趣(参见上面的示例)。顺便说一句,您的回答是正确的 - 我的评论更多是关于“我们必须退订吗?”。
        • 一般规则是,如果您订阅(显式锁定资源),则显式释放它。当然,在某些情况下这可能不是必需的,但通常稍后会以需要发布的方式修改代码,但随后它很容易被忽视并且可能导致难以发现错误。这只是避免错误的防御性编程。
        猜你喜欢
        • 1970-01-01
        • 2023-01-04
        • 2019-06-08
        • 1970-01-01
        • 2017-05-12
        • 2017-12-27
        • 1970-01-01
        • 1970-01-01
        • 2017-06-12
        相关资源
        最近更新 更多