モーダル ウィンドウに挿入された動的に作成されたコンポーネントを破棄しようとしています。コンポーネントコードは
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 経由の代わりに。