From e11b367196a266d784655bc431270550e2f2d853 Mon Sep 17 00:00:00 2001 From: Tiago Viegas Date: Fri, 18 Aug 2023 12:01:52 +0100 Subject: [PATCH] fix(angular): fix change detection on modal when using a TemplateRef (#700) --- packages/angular/src/modal/modal.config.ts | 3 +- .../angular/src/modal/modal.service.spec.ts | 15 ++++- packages/angular/src/modal/modal.service.ts | 59 ++++++++++--------- packages/documentation/docs/controls/modal.md | 4 +- 4 files changed, 50 insertions(+), 31 deletions(-) diff --git a/packages/angular/src/modal/modal.config.ts b/packages/angular/src/modal/modal.config.ts index 3954c410dff..fb704637198 100644 --- a/packages/angular/src/modal/modal.config.ts +++ b/packages/angular/src/modal/modal.config.ts @@ -7,9 +7,10 @@ * LICENSE file in the root directory of this source tree. */ +import { TemplateRef, Type } from '@angular/core'; import { ModalConfig as IxModalConfig } from '@siemens/ix'; export type ModalConfig = Omit & { - content: any; + content: TemplateRef | Type; data?: TDATA; }; diff --git a/packages/angular/src/modal/modal.service.spec.ts b/packages/angular/src/modal/modal.service.spec.ts index 0fce1d47091..8e6d57b9aa9 100644 --- a/packages/angular/src/modal/modal.service.spec.ts +++ b/packages/angular/src/modal/modal.service.spec.ts @@ -25,11 +25,18 @@ jest.mock('@siemens/ix', () => ({ })); test('should create modal by templateRef', () => { + const appRefMock = { + attachView: jest.fn(), + }; const createEmbeddedViewMock = jest.fn((_: { $implicit: any }) => ({ rootNodes: [{}], detectChanges: jest.fn(), })); - const modalService = new ModalService({} as any, {} as any, {} as any); + const modalService = new ModalService( + appRefMock as any, + {} as any, + {} as any + ); modalService.open({ content: { createEmbeddedView: createEmbeddedViewMock, @@ -46,6 +53,7 @@ test('should create modal by templateRef', () => { dismiss: expect.any(Function), }, }); + expect(appRefMock.attachView).toHaveBeenCalled(); }); test('should create modal by component typ', async () => { @@ -58,6 +66,11 @@ test('should create modal by component typ', async () => { rootNodes: [jest.fn()], detectChanges: jest.fn(), }, + injector: { + get: jest.fn(() => ({ + nativeElement: {} + })), + }, })), }; const componentFactoryMock = { diff --git a/packages/angular/src/modal/modal.service.ts b/packages/angular/src/modal/modal.service.ts index 84c547db6d3..88fd17fd604 100644 --- a/packages/angular/src/modal/modal.service.ts +++ b/packages/angular/src/modal/modal.service.ts @@ -10,10 +10,12 @@ import { ApplicationRef, ComponentFactoryResolver, - EmbeddedViewRef, + ElementRef, Injectable, Injector, + TemplateRef, Type, + ViewRef, } from '@angular/core'; import { closeModal, dismissModal, showModal } from '@siemens/ix'; import { InternalIxActiveModal, IxActiveModal } from './modal-ref'; @@ -43,10 +45,15 @@ export class ModalService { }; if (config.content instanceof Type) { - return this.createContentByComponentType(config, context); + return this.createContentByComponentType( + config.content, + config, + context + ); } const modalInstance = await this.createContentByTemplateRef( + config.content, config, context ); @@ -55,14 +62,14 @@ export class ModalService { } private async createContentByComponentType( + componentType: Type, config: ModalConfig, context: ModalContext ) { const activeModal = new InternalIxActiveModal(context.data); - const modalFactory = this.componentFactoryResolver.resolveComponentFactory( - config.content - ); + const modalFactory = + this.componentFactoryResolver.resolveComponentFactory(componentType); const modalInjector = Injector.create({ providers: [ @@ -77,10 +84,12 @@ export class ModalService { const instance = modalFactory.create(modalInjector); this.appRef.attachView(instance.hostView); - const embeddedView = instance.hostView as EmbeddedViewRef; + const element = instance.injector.get(ElementRef); + const modalInstance = await this.createModalInstance( context, - embeddedView, + element.nativeElement, + instance.hostView, config ); @@ -90,55 +99,51 @@ export class ModalService { } private async createContentByTemplateRef( + templateRef: TemplateRef, config: ModalConfig, - context: { - close: ((result: any) => void) | null; - dismiss: ((result?: any) => void) | null; - data?: TData | undefined; - } + context: ModalContext ) { - const embeddedView = config.content.createEmbeddedView({ + const embeddedView = templateRef.createEmbeddedView({ $implicit: context, }); + + this.appRef.attachView(embeddedView); + return await this.createModalInstance( context, + embeddedView.rootNodes[0], embeddedView, config ); } private async createModalInstance( - context: { - close: ((result: any) => void) | null; - dismiss: ((result?: any) => void) | null; - data?: TData | undefined; - }, - embeddedView: EmbeddedViewRef, + context: ModalContext, + htmlElement: HTMLElement, + viewRef: ViewRef, config: ModalConfig ) { - const node = embeddedView.rootNodes[0]; - context.close = (result: any) => { - closeModal(node, result); + closeModal(htmlElement, result); }; context.dismiss = (result?: any) => { - dismissModal(node, result); + dismissModal(htmlElement, result); }; - embeddedView.detectChanges(); + viewRef.detectChanges(); const modalInstance = await showModal({ ...config, - content: node, + content: htmlElement, }); modalInstance.onClose.once(() => { - embeddedView.destroy(); + viewRef.destroy(); }); modalInstance.onDismiss.once(() => { - embeddedView.destroy(); + viewRef.destroy(); }); return modalInstance; } diff --git a/packages/documentation/docs/controls/modal.md b/packages/documentation/docs/controls/modal.md index 87a9bc85093..44d90137e12 100644 --- a/packages/documentation/docs/controls/modal.md +++ b/packages/documentation/docs/controls/modal.md @@ -20,8 +20,8 @@ import ApiModalInstanceAngular from './\_modal/angular/modal-instance.html.md' import ApiModalConfigReact from './\_modal/react/modal-config.md' import ApiModalRefReact from './\_modal/react/modal-ref.html.md' -import ModalConfig from './../auto-generated/utils/core/ModalConfig.md' -import ModalInstance from './../auto-generated/utils/core/ModalInstance.md' +import ModalConfig from './../auto-generated/utils/ModalConfig.md' +import ModalInstance from './../auto-generated/utils/ModalInstance.md' import SourceReactLoading from './../auto-generated/previews/react/loading.md' import SourceReactMessage from './../auto-generated/previews/react/message.md'