Finley Williamson V
Finley Williamson V

Reputation: 1

How can I write a custom type declaration for NgbModal that correctly infers the type of modalInstance?

NgbModal's open method takes in a content parameter typed as any:

// @ng-bootstrap/ng-bootstrap/modal/modal.d.ts

export declare class NgbModal {
    open(content: any, options?: NgbModalOptions): NgbModalRef;
}

That returns an NgbModalRef with a function 'get componentInstance()' that also returns any:

// @ng-bootstrap/ng-bootstrap/modal/modal-ref.d.ts

export declare class NgbModalRef {
   get componentInstance(): any;
}

I want to create a custom type definition that allows the initial open function to infer the type of the component instance.

Here's my types.d.ts file:

import {
  NgbModal as _NgbModal,
  NgbModalOptions,
  NgbModalRef as _NgbModalRef
} from '@ng-bootstrap/ng-bootstrap';

declare module '@ng-bootstrap/ng-bootstrap' {
  export interface NgbModal extends _NgbModal {
    open<T>(content: T, options?: NgbModalOptions): NgbModalRef<T>;
  }

  export interface NgbModalRef<T> extends _NgbModalRef {
    get componentInstance(): InstanceType<T>;
  }
}

Here's my modal class:

export type ModalOption = {
  title: string;
  buttonText: string;
};

@Component({
  selector: 'app-modal',
  templateUrl: './modal.component.html',
  styleUrls: ['./modal.component.scss']
})
export class ModalComponent {
  @Input() data: ModalOption = {};
}

Here's where I'm using it:

// Open the modal
const changeConfirmationModal = modalService.open(ModalComponent, {
  centered: true
});

// Set the modal data
changeConfirmationModal.componentInstance.data = {
  title: 'Some Title',
  buttonText: 'Some Button Text'
};

If I don't import and extend the original NgbModal / NgbModalRef in the types.d.ts file, componentInstance.data is typed correctly, but it breaks my app module as no other exports from @ng-bootstrap can be found. If I import and extend those two in my types.d.ts file, the inference breaks and componentInstance.data returns to being typed as any.

Any help would be greatly appreciated.

Upvotes: 0

Views: 89

Answers (1)

MateusRosario
MateusRosario

Reputation: 21

In cases like this, I create my version of the Service, instead of redeclaring the imported Module. Then I can use my type-safe service throughout the application instead of relying directly on the third-party module. See the example below:

type Constructor<T> = new (...args: any[]) => T;

export interface MyModalOptions extends NgbModalOptions {}

export interface MyModalRef<T> extends NgbModalRef {
    get componentInstance(): Constructor<T>;
}

@Injectable({
   providedIn: 'root'
})
export class MyModalService {

    constructor(private ngbModalService: NgbModal) {}

    open<T>(content: T, options?: MyModalOptions): MyModalRef<T> { 
        ... opens the modal ... 
    }

}

this approach creates an interface between the application and the external module where I can modify the modal's service behavior.

Upvotes: 0

Related Questions