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 に変更しても効果はありませんでした。また、コンポーネントの破棄、null への参照の設定、およびモーダルの可視性の削除について、あらゆる種類の setTimeouts を試しました。

モーダル コンポーネント自体は、html を保持して modalBody ViewContainerRef を取得するためだけに存在し、modalService の visible プロパティは「display: none;」を介して機能します。*ngIf 経由の代わりに。

4

0 に答える 0