Angular 2 - FormGroup ValueChanges Unsubscribe

I have a FormGroup with ValueChanges that is not freed from memory when the user navigates from a component's route to another component and then returns to the component.

This means that if the user navigates from the component and then back to the component 5 times, the onFormChange method will fire 5 times, but only one of those calls is for the current component.

I figured out that the problem was that I needed to unsubscribe from the valueChanges event in the NgDestroy event, but there is no unsubscribe method in the valueChanges event.

I'm sure I need to unsubscribe or free up memory for something, but I'm not sure what.

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

      

+7


source to share


3 answers


The call subscribe()

returns Subscription

, and this is what you use to unsubscribe:



class JobToolbarComponent

  private subscr:Subscription;

  ngOnInit() {
    ...
    this.subscr = this.form.valueChanges ...
    ...
  }

  ngOnDestroy() {
    this.subscr.unsubscribe();
  }
}

      

+8


source


I created this following function

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

}

      

which you can actually use to automatically unsubscribe all observers, but you have to store them in public properties so that this function can intercept it and cause it to unsubscribe. The way you use it is stated below: -



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

}

      

For more information on how to use the decorator please read this blog where I got the idea and it's pretty cool.

Blog

+3


source


I'm not sure if this is a good idea, but it is very simple to implement and works well for my school project.

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

      

Source: https://github.com/angular/angularfire2/issues/377

0


source







All Articles