From 7ae4141f676a80fb90f49e404980101345aa60e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Georg=20M=C3=BCller?= Date: Tue, 19 Aug 2025 14:18:47 +0200 Subject: [PATCH 1/2] make modals type-safe it is now possible to create a modal and return a correctly typed ComponentRef. The BaseModal class is now also typed, allowing a correctly typed output value. --- src/modal/base-modal.class.ts | 8 ++++---- src/modal/base-modal.service.ts | 7 +++++-- src/placeholder/placeholder.service.ts | 13 +++++++------ 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/modal/base-modal.class.ts b/src/modal/base-modal.class.ts index 5595e97ba8..86bda6c28b 100644 --- a/src/modal/base-modal.class.ts +++ b/src/modal/base-modal.class.ts @@ -13,11 +13,11 @@ import { @Directive({ selector: "[cdsBaseModal], [ibmBaseModal]" }) -export class BaseModal { +export class BaseModal { /** * Base event emitter to propagate close events */ - @Output() close = new EventEmitter(); + @Output() close = new EventEmitter(); /** * Controls the open state of the modal @@ -27,7 +27,7 @@ export class BaseModal { /** * Default method to handle closing the modal */ - closeModal(): void { - this.close.emit(); + closeModal(value?: R): void { + this.close.emit(value); } } diff --git a/src/modal/base-modal.service.ts b/src/modal/base-modal.service.ts index 1e4950da97..8fdcf988a1 100644 --- a/src/modal/base-modal.service.ts +++ b/src/modal/base-modal.service.ts @@ -3,10 +3,12 @@ import { Injector, Injectable, inject, - EnvironmentInjector + EnvironmentInjector, + Type } from "@angular/core"; import { PlaceholderService } from "carbon-components-angular/placeholder"; import { tap, delay } from "rxjs/operators"; +import {BaseModal} from "./base-modal.class"; /** @@ -44,7 +46,8 @@ export class BaseModalService { * Creates and renders the modal component that is passed in. * `inputs` is an optional parameter of `data` that can be passed to the `Modal` component. */ - create(data: { component: any, inputs?: any }): ComponentRef { + create = Record>( + data: { component: Type, inputs?: I }): ComponentRef { let defaults = { inputs: {} }; data = Object.assign({}, defaults, data); diff --git a/src/placeholder/placeholder.service.ts b/src/placeholder/placeholder.service.ts index a1ec4655a8..7a4a83ab30 100644 --- a/src/placeholder/placeholder.service.ts +++ b/src/placeholder/placeholder.service.ts @@ -3,7 +3,8 @@ import { ViewContainerRef, Injector, EnvironmentInjector, - inject + inject, + Type } from "@angular/core"; import { Injectable } from "@angular/core"; @@ -35,24 +36,24 @@ export class PlaceholderService { /** * Creates and returns component in the view. */ - createComponent( - component: ComponentRef, + createComponent( + component: Type, injector: Injector, id?: any, environment: EnvironmentInjector = undefined - ): ComponentRef { + ): ComponentRef { if (id) { if (!this.viewContainerMap.has(id)) { console.error(`No view container with id ${id} found`); return; } - return this.viewContainerMap.get(id).createComponent(component as any, { index: this.viewContainerMap.size, injector }); + return this.viewContainerMap.get(id).createComponent(component, { index: this.viewContainerMap.size, injector }); } if (!this.viewContainerRef) { console.error("No view container defined! Likely due to a missing `cds-placeholder`"); return; } - return this.viewContainerRef.createComponent(component as any, + return this.viewContainerRef.createComponent(component, { index: this.viewContainerRef.length, injector, From fc5593e75adf34893bb1c65b9eb2309ffb3600f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Georg=20M=C3=BCller?= Date: Tue, 19 Aug 2025 14:29:45 +0200 Subject: [PATCH 2/2] make DataPassingModal story type-safe --- src/modal/stories/data-passing.component.ts | 18 ++++++++++-------- src/modal/stories/input-modal.component.ts | 10 ++++++---- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/modal/stories/data-passing.component.ts b/src/modal/stories/data-passing.component.ts index a309cb32f2..ca45a2da2a 100644 --- a/src/modal/stories/data-passing.component.ts +++ b/src/modal/stories/data-passing.component.ts @@ -12,31 +12,33 @@ import { InputModal } from "./input-modal.component"; selector: "app-data-passing-modal", template: ` -

Data passed from input modal on input change:

{{ modalInputValue }} +

Data passed from input modal on close:

{{ modalInputValue }} ` }) -export class DataPassingModal implements AfterContentInit { +export class DataPassingModal { @Input() modalText = "Hello, World"; @Input() size = "md"; protected modalInputValue = ""; - protected data: Observable = new Subject(); constructor(protected modalService: ModalService) { } openModal() { - this.modalService.create({ + const componentRef = this.modalService.create({ component: InputModal, inputs: { modalText: this.modalText, inputValue: this.modalInputValue, size: this.size, - data: this.data } }); - } - ngAfterContentInit() { - this.data.subscribe(value => this.modalInputValue = value); + componentRef.instance.close.subscribe((result: string) => { + if (result === undefined) { + console.log("Modal closed without value"); + } else { + this.modalInputValue = result; + } + }); } } diff --git a/src/modal/stories/input-modal.component.ts b/src/modal/stories/input-modal.component.ts index 882f72821c..dc85a08400 100644 --- a/src/modal/stories/input-modal.component.ts +++ b/src/modal/stories/input-modal.component.ts @@ -20,22 +20,24 @@ import { ModalService, BaseModal } from "../"; - + ` }) -export class InputModal extends BaseModal { +export class InputModal extends BaseModal { + + protected outputValue = this.inputValue; + constructor( @Inject("modalText") public modalText, @Inject("size") public size, - @Inject("data") public data, @Inject("inputValue") public inputValue, protected modalService: ModalService) { super(); } onChange(event) { - this.data.next(event.target.value); + this.outputValue = event.target.value; } }