销毁动态创建的组件会引发更改检测错误

时间:2016-07-08 11:46:30

标签: typescript angular angular2-components angular2-changedetection

我正在尝试销毁插入到模态窗口中的动态创建的组件。组件代码是

import {Component, Input, Output, OnInit, EventEmitter, OnDestroy} from '@angular/core';
import {NgSwitch, NgSwitchCase, NgSwitchDefault} from '@angular/common';
import {FormBuilder, FormGroup, FormControl, Validators, REACTIVE_FORM_DIRECTIVES} from '@angular/forms';

import {ControlType, DateTimeValidationType} from '../Types/enums';
import {IFormBuilderField} from '../Types/interfaces';
import {ValidationFormBuilderService} from '../Services/validation-form-builder.service';
import {OptionsFormBuilderService} from '../Services/options-form-builder.service';
import {NumberControlComponent} from '../../NumberControl/Components/number-control.component';

import {BaseComponent} from '../../../App/Shared/Components/base.component';
import {isUnique} from '../../../App/Shared/Helpers/fieldValidators';
import {TagCreatorComponent} from '../../../App/Shared/Components/tag-creator.component';

@Component({
    selector: 'field-editor',
    templateUrl: 'templates/resources/formBuilder/field-editor.component.html',
    directives: [
        REACTIVE_FORM_DIRECTIVES,
        NgSwitch,
        NgSwitchCase,
        NgSwitchDefault,
        TagCreatorComponent,
        NumberControlComponent],
    providers: [ValidationFormBuilderService, OptionsFormBuilderService]
})

export class FieldEditorComponent extends BaseComponent implements OnInit, OnDestroy {
    constructor(
        private _formBuilder: FormBuilder,
        private _validationFormBuilder: ValidationFormBuilderService,
        private _optionsFormBuilder: OptionsFormBuilderService) {
        super();
    }
    DateTimeValidationType = DateTimeValidationType;
    ControlType = ControlType;
    @Input() field: IFormBuilderField;
    @Output() update: EventEmitter<IFormBuilderField> = new EventEmitter<IFormBuilderField>();
    @Input() labels: string[];
    form: FormGroup;
    label: FormControl;
    control: FormControl;

    validationForm: FormGroup;
    optionsForm: FormGroup;

    validationFormSubscription: any;
    optionsFormSubscription: any;
    controlSubscription: any;
    formSubscription: any;

    controlOptions = Object.keys(ControlType)
        .filter(x => typeof ControlType[x] === "number")
        .map(x => ({
            value: ControlType[x],
            label: x
        }));

    dateTimeValidationOptions = Object.keys(DateTimeValidationType)
        .filter(x => typeof DateTimeValidationType[x] === "number")
        .map(x => ({
            value: DateTimeValidationType[x],
            label: x
        }));

    ngOnInit() {
        this.form = this._formBuilder.group({
            label: [this.field.label, Validators.compose(
                [Validators.required, isUnique(this.labels)])],
            control: [this.field.control]
        });
        this.buildValidationForm();
        this.buildOptionsForm();
        this.label = this.form.controls['label'] as FormControl;
        this.control = this.form.controls['control'] as FormControl;

        this.controlSubscription = this.control.valueChanges.subscribe((control: ControlType) => {
            this.field.validation = {};
            this.buildValidationForm(+control);
            this.field.config = <any>{};
            this.buildOptionsForm(+control);
        });

        this.formSubscription = this.form.valueChanges.subscribe(form => {
            // Needs to be a number, but selects give a string value
            form.control = +form.control;
            Object.assign(this.field, form);
            this.update.emit(this.field);
        });
    }
    buildValidationForm(type: ControlType = this.field.control) {
        this.validationForm = this._validationFormBuilder.createForm(
            +type,
            this.field.validation);

        this.validationFormSubscription && this.validationFormSubscription.unsubscribe();

        this.validationFormSubscription = this.validationForm.valueChanges.subscribe(validationOptions => {
            this.field.validation = validationOptions;
            this.update.emit(this.field);
        });
    }
    buildOptionsForm(type: ControlType = this.field.control) {
        this.optionsForm = this._optionsFormBuilder.createForm(
            +type,
            this.field.config);

        this.optionsFormSubscription && this.optionsFormSubscription.unsubscribe();

        this.optionsFormSubscription = this.optionsForm.valueChanges.subscribe(options => {
            this.field.config = options;
            this.update.emit(this.field);
        });
    }
    getDateTimeValidationControl(firstLevel: string, name: string) {
        return (<FormGroup>this.validationForm.controls[firstLevel + "DateTime"]).controls[name];
    }
    ngOnDestroy() {
        this.controlSubscription.unsubscribe();
        this.optionsFormSubscription.unsubscribe();
        this.validationFormSubscription.unsubscribe();
        this.formSubscription.unsubscribe();
    }
}

通过此服务显示模态窗口

import {
    Injectable,
    ComponentRef,
    Inject,
    ComponentResolver,
    Injector,
    ViewChild,
    ViewContainerRef} from '@angular/core';

import {IModalInformation, IButton} from '../Types/interfaces';

@Injectable()
export class ModalService {
    constructor(
        @Inject(ComponentResolver) private _componentResolver: ComponentResolver) { }
    visible: boolean = false;
    title: string = null;
    body: any = null;
    isString: boolean;
    isArray: boolean;
    isComponent: boolean;
    componentRef: ComponentRef<any>;
    buttons: IButton[] = [];
    onClose: () => void = () => undefined;
    modalBody: ViewContainerRef;    //Assigned in the modal.component code correctly
    private renderComponent(component: any): Promise<ComponentRef<any>> {
        return this._componentResolver.resolveComponent(component)
            .then(componentFactory => {
                const ref = this.modalBody.createComponent(componentFactory);
                this.componentRef = ref;
                return Promise.resolve(ref);
            });
    }
    public show(modalInformation: IModalInformation) {
        this.isString = typeof this.body === "string";
        this.isArray = Array.isArray(this.body);
        this.isComponent = !this.isString && !this.isArray;

        this.visible = true;
        this.title = modalInformation.title;
        this.body = modalInformation.body;

        if (this.isComponent) {
            setTimeout(() => this.renderComponent(this.body.component).then(this.body.then), 0);
        }

        this.buttons = modalInformation.buttons;
        this.onClose = modalInformation.onClose;
    }
    public close() {
        if (!this.onClose()) {
            return;
        }
        if (this.isComponent) {
            this.componentRef.destroy();
            this.componentRef = null;
        }
        this.isComponent = false;
        this.visible = false;
        this.isArray = false;
        this.isString = false;
        this.title = null;
        this.body = null;
        this.onClose = () => undefined;
        this.buttons = [];
    }
}

该服务由父组件调用,该组件指定要使用的组件类型以及将输入和输出属性附加到组件引用(body proeprty)的回调。

我遇到的问题是,当我试图销毁动态创建的组件(FieldEditorComponent)时,我收到错误

Error: Attempt to use a destroyed view: detectChanges
at ViewDestroyedException.BaseException [as constructor] (http://localhost:5000/js/app-es6.js:6500:23)
at new ViewDestroyedException (http://localhost:5000/js/app-es6.js:35885:16)
at DebugAppView.AppView.throwDestroyedError (http://localhost:5000/js/app-es6.js:36433:72)
at DebugAppView.AppView.detectChanges (http://localhost:5000/js/app-es6.js:36380:18)
at DebugAppView.detectChanges (http://localhost:5000/js/app-es6.js:36487:44)
at ViewRef_.detectChanges (http://localhost:5000/js/app-es6.js:36839:65)
at http://localhost:5000/js/app-es6.js:30325:84
at Array.forEach (native)
at ApplicationRef_.tick (http://localhost:5000/js/app-es6.js:30325:38)
at http://localhost:5000/js/app-es6.js:30229:125

正如您所看到的,我已经尝试清除FieldEditor中的订阅,并首先分离正在销毁的ComponentRef上可用的ChangeDetectorRef。将FieldDeditor的ChangeDetectionStrategy更改为OnPush也没有帮助。我还试图破坏组件,将引用设置为null,并删除模态的可见性。

模态组件本身只是用于保存html并获取modalBody ViewContainerRef,而modalService上的visible属性通过'display:none;'工作。而不是通过* ngIf。

0 个答案:

没有答案