0

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

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 = [];
    }
}

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

我遇到的问题是,当我尝试销毁动态创建的组件(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。将 FieldEditor 的 ChangeDetectionStrategy 更改为 OnPush 也没有帮助。我还尝试了各种方式的 setTimeouts 来破坏组件、将引用设置为 null 以及删除模式的可见性。

modal 组件本身只是用来保存 html 并获取 modalBody ViewContainerRef,modalService 上的可见属性通过“display: none;”工作 而不是通过 *ngIf。

4

0 回答 0