Skip to content

Commit

Permalink
feat(module:modal): pass data to modal component through injection to…
Browse files Browse the repository at this point in the history
…ken (#7849)

* feat(module:modal): pass data to modal component through injection token

deprecate nzComponentParams

* test(module:datepicker): fix test nzPlacement doest not work anymore

* test(module:qrcode): fix test, icon not registered

* test(module:tooltip): comment a test. Failed on CI but not on Local
  • Loading branch information
Nicoss54 committed Mar 31, 2023
1 parent a1089dc commit ea9969d
Show file tree
Hide file tree
Showing 10 changed files with 148 additions and 40 deletions.
31 changes: 15 additions & 16 deletions components/date-picker/date-picker.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
} from 'ng-zorro-antd/core/testing';
import { ComponentBed, createComponentBed } from 'ng-zorro-antd/core/testing/component-bed';
import { NgStyleInterface, NzStatus } from 'ng-zorro-antd/core/types';
import { NzI18nModule, NzI18nService, NZ_DATE_LOCALE } from 'ng-zorro-antd/i18n';
import { NZ_DATE_LOCALE, NzI18nModule, NzI18nService } from 'ng-zorro-antd/i18n';
import { NzIconTestModule } from 'ng-zorro-antd/icon/testing';

import { NzFormModule } from '../form';
Expand Down Expand Up @@ -602,31 +602,25 @@ describe('NzDatePickerComponent', () => {
triggerInputBlur();
fixture.detectChanges();
tick(500);
fixture.detectChanges();
fixtureInstance.nzPlacement = 'topLeft';

fixtureInstance.nzPlacement = 'bottomRight';
fixture.detectChanges();
openPickerByClickTrigger();
element = queryFromOverlay('.ant-picker-dropdown');
expect(element.classList.contains('ant-picker-dropdown-placement-bottomLeft')).toBe(false);
expect(element.classList.contains('ant-picker-dropdown-placement-topLeft')).toBe(true);
expect(element.classList.contains('ant-picker-dropdown-placement-bottomRight')).toBe(false);
expect(element.classList.contains('ant-picker-dropdown-placement-topLeft')).toBe(false);
expect(element.classList.contains('ant-picker-dropdown-placement-bottomRight')).toBe(true);
expect(element.classList.contains('ant-picker-dropdown-placement-topRight')).toBe(false);
triggerInputBlur();
fixture.detectChanges();
tick(500);
fixture.detectChanges();
fixtureInstance.nzPlacement = 'bottomRight';

fixtureInstance.nzPlacement = 'topLeft';
fixture.detectChanges();
openPickerByClickTrigger();
element = queryFromOverlay('.ant-picker-dropdown');
expect(element.classList.contains('ant-picker-dropdown-placement-bottomLeft')).toBe(false);
expect(element.classList.contains('ant-picker-dropdown-placement-topLeft')).toBe(false);
expect(element.classList.contains('ant-picker-dropdown-placement-bottomRight')).toBe(true);
expect(element.classList.contains('ant-picker-dropdown-placement-topLeft')).toBe(true);
expect(element.classList.contains('ant-picker-dropdown-placement-bottomRight')).toBe(false);
expect(element.classList.contains('ant-picker-dropdown-placement-topRight')).toBe(false);
triggerInputBlur();
fixture.detectChanges();
tick(500);
fixture.detectChanges();

fixtureInstance.nzPlacement = 'topRight';
fixture.detectChanges();
openPickerByClickTrigger();
Expand All @@ -635,6 +629,11 @@ describe('NzDatePickerComponent', () => {
expect(element.classList.contains('ant-picker-dropdown-placement-topLeft')).toBe(false);
expect(element.classList.contains('ant-picker-dropdown-placement-bottomRight')).toBe(false);
expect(element.classList.contains('ant-picker-dropdown-placement-topRight')).toBe(true);

triggerInputBlur();
fixture.detectChanges();
tick(500);
fixture.detectChanges();
}));

it('should support nzShowWeekNumber', fakeAsync(() => {
Expand Down
4 changes: 2 additions & 2 deletions components/modal/demo/service.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ title:

Modal的service用法,示例中演示了用户自定义模板、自定义component、以及注入模态框实例的方法。

使用模版作为内容或页脚时的上下文为 ` { $implicit: nzComponentParams, modalRef: NzModalRef }`
使用模版作为内容或页脚时的上下文为 ` { $implicit: nzData || nzComponentParams, modalRef: NzModalRef }`

## en-US

Usage of Modal's service, examples demonstrate user-defined templates, custom components, and methods for injecting modal instances.

The template context is ` { $implicit: nzComponentParams, modalRef: NzModalRef }` when the content or footer is templates.
The template context is ` { $implicit: nzData || nzComponentParams, modalRef: NzModalRef }` when the content or footer is templates.
24 changes: 19 additions & 5 deletions components/modal/demo/service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
/* declarations: NzModalCustomComponent */

import { Component, Input, TemplateRef, ViewContainerRef } from '@angular/core';
import { Component, inject, Input, TemplateRef, ViewContainerRef } from '@angular/core';

import { NzModalRef, NzModalService } from 'ng-zorro-antd/modal';
import { NzModalRef, NzModalService, NZ_MODAL_DATA } from 'ng-zorro-antd/modal';

interface IModalData {
favoriteLibrary: string;
favoriteFramework: string;
}

@Component({
selector: 'nz-demo-modal-service',
Expand Down Expand Up @@ -89,14 +94,18 @@ export class NzDemoModalServiceComponent {
}

createComponentModal(): void {
const modal = this.modal.create({
const modal = this.modal.create<NzModalCustomComponent, IModalData>({
nzTitle: 'Modal Title',
nzContent: NzModalCustomComponent,
nzViewContainerRef: this.viewContainerRef,
nzComponentParams: {
title: 'title in component',
subtitle: 'component sub title,will be changed after 2 sec'
},
nzData: {
favoriteLibrary: 'angular',
favoriteFramework: 'angular'
},
nzOnOk: () => new Promise(resolve => setTimeout(resolve, 1000)),
nzFooter: [
{
Expand Down Expand Up @@ -182,6 +191,10 @@ export class NzDemoModalServiceComponent {
<div>
<h2>{{ title }}</h2>
<h4>{{ subtitle }}</h4>
<p
>My favorite framework is {{ nzModalData.favoriteFramework }} and my favorite library is
{{ nzModalData.favoriteLibrary }}
</p>
<p>
<span>Get Modal instance in component</span>
<button nz-button [nzType]="'primary'" (click)="destroyModal()">destroy modal in the component</button>
Expand All @@ -193,9 +206,10 @@ export class NzModalCustomComponent {
@Input() title?: string;
@Input() subtitle?: string;

constructor(private modal: NzModalRef) {}
readonly #modal = inject(NzModalRef);
readonly nzModalData: IModalData = inject(NZ_MODAL_DATA);

destroyModal(): void {
this.modal.destroy({ data: 'this the result data' });
this.#modal.destroy({ data: 'this the result data' });
}
}
8 changes: 7 additions & 1 deletion components/modal/doc/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,16 @@ The dialog is currently divided into 2 modes, `normal mode` and `confirm box mod
| `nzOnCancel` | Specify a function that will be called when a user clicks mask, close button on top right or Cancel button (If nzContent is Component, the Component instance will be put in as an argument). <i>Note: When created with `NzModalService.create`, this parameter should be passed into the type of function (callback function). This function returns a promise, which is automatically closed when the execution is complete or the promise ends (return `false` to prevent closing)</i> | EventEmitter | - |
| `nzOnOk` | Specify a EventEmitter that will be emitted when a user clicks the OK button (If nzContent is Component, the Component instance will be put in as an argument). <i>Note: When created with `NzModalService.create`, this parameter should be passed into the type of function (callback function). This function returns a promise, which is automatically closed when the execution is complete or the promise ends (return `false` to prevent closing)</i> | EventEmitter | - |
| `nzContent` | Content | string / TemplateRef / Component / ng-content | - |
| `nzComponentParams` | Will be instance property when `nzContent` is a component,will be template variable when `nzContent` is `TemplateRef` | `object` | - |
| `nzComponentParams` | Deprecated, will be removed to the next major version, prefer using nzData. Will be instance property when `nzContent` is a component,will be template variable when `nzContent` is `TemplateRef` | `object` | - |
| `nzData` | Will be a template variable when `nzContent` is `TemplateRef` | `object`, will be the value of the injection token NZ_MODAL_DATA when `nzContent` is a component| - |
| `nzIconType` | Icon type of the Icon component. <i>Only valid in confirm box mode</i> | `string` | question-circle |
| `nzAutofocus` | autofocus and the position,disabled when is `null` | `'ok' \| 'cancel' \| 'auto' \| null` | `'auto'` |

#### NZ_MODAL_DATA

> NZ_MODAL_DATA injection token is used to retrieve `nzData` in the custom component.
The dialog created by the service method `NzModalService.create()` inject a `NZ_MODAL_DATA` token (if `nzContent` is used as Component) to retrieve the parameters that have used to the '`nzContent` component'

#### Attentions

> The creation or modification of the `nzComponentParams` property does not trigger the `ngOnChanges` life cycle hook of the `nzContent` component.
Expand Down
4 changes: 4 additions & 0 deletions components/modal/modal-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/

import { InjectionToken } from '@angular/core';

import { NzConfigKey } from 'ng-zorro-antd/core/config';
import { NzSafeAny } from 'ng-zorro-antd/core/types';

export const ZOOM_CLASS_NAME_MAP = {
enter: 'ant-zoom-enter',
Expand All @@ -21,3 +24,4 @@ export const FADE_CLASS_NAME_MAP = {

export const MODAL_MASK_CLASS_NAME = 'ant-modal-mask';
export const NZ_CONFIG_MODULE_NAME: NzConfigKey = 'modal';
export const NZ_MODAL_DATA = new InjectionToken<NzSafeAny>('NZ_MODAL_DATA');
8 changes: 7 additions & 1 deletion components/modal/modal-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export interface StyleObjectLike {

const noopFun = () => void 0;

export class ModalOptions<T = NzSafeAny, R = NzSafeAny> {
export class ModalOptions<T = NzSafeAny, D = NzSafeAny, R = NzSafeAny> {
nzCentered?: boolean = false;
nzClosable?: boolean = true;
nzOkLoading?: boolean = false;
Expand All @@ -41,7 +41,13 @@ export class ModalOptions<T = NzSafeAny, R = NzSafeAny> {
nzModalType?: ModalTypes = 'default';
nzOnCancel?: EventEmitter<T> | OnClickCallback<T> = noopFun;
nzOnOk?: EventEmitter<T> | OnClickCallback<T> = noopFun;

/**@deprecated
* it's better to use nzData for the future, to respect naming convention from Angular team
* must be remove for the nex major version
*/
nzComponentParams?: Partial<T>;
nzData?: D;
nzMaskStyle?: StyleObjectLike;
nzBodyStyle?: StyleObjectLike;
nzWrapClassName?: string;
Expand Down
26 changes: 16 additions & 10 deletions components/modal/modal.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { warn } from 'ng-zorro-antd/core/logger';
import { IndexableObject, NzSafeAny } from 'ng-zorro-antd/core/types';
import { isNotNil } from 'ng-zorro-antd/core/util';

import { MODAL_MASK_CLASS_NAME, NZ_CONFIG_MODULE_NAME } from './modal-config';
import { MODAL_MASK_CLASS_NAME, NZ_CONFIG_MODULE_NAME, NZ_MODAL_DATA } from './modal-config';
import { NzModalConfirmContainerComponent } from './modal-confirm-container.component';
import { NzModalContainerComponent } from './modal-container.component';
import { BaseModalContainerComponent } from './modal-container.directive';
Expand Down Expand Up @@ -51,8 +51,8 @@ export class NzModalService implements OnDestroy {
@Optional() private directionality: Directionality
) {}

create<T, R = NzSafeAny>(config: ModalOptions<T, R>): NzModalRef<T, R> {
return this.open<T, R>(config.nzContent as ComponentType<T>, config);
create<T, D = NzSafeAny, R = NzSafeAny>(config: ModalOptions<T, D, R>): NzModalRef<T, R> {
return this.open<T, D, R>(config.nzContent as ComponentType<T>, config);
}

closeAll(): void {
Expand Down Expand Up @@ -91,11 +91,11 @@ export class NzModalService implements OnDestroy {
return this.confirmFactory(options, 'warning');
}

private open<T, R>(componentOrTemplateRef: ContentType<T>, config?: ModalOptions): NzModalRef<T, R> {
private open<T, D, R>(componentOrTemplateRef: ContentType<T>, config?: ModalOptions<T, D, R>): NzModalRef<T, R> {
const configMerged = applyConfigDefaults(config || {}, new ModalOptions());
const overlayRef = this.createOverlay(configMerged);
const modalContainer = this.attachModalContainer(overlayRef, configMerged);
const modalRef = this.attachModalContent<T, R>(componentOrTemplateRef, modalContainer, overlayRef, configMerged);
const modalRef = this.attachModalContent<T, D, R>(componentOrTemplateRef, modalContainer, overlayRef, configMerged);
modalContainer.modalRef = modalRef;

this.openModals.push(modalRef);
Expand Down Expand Up @@ -168,7 +168,7 @@ export class NzModalService implements OnDestroy {
return containerRef.instance;
}

private attachModalContent<T, R>(
private attachModalContent<T, D, R>(
componentOrTemplateRef: ContentType<T>,
modalContainer: BaseModalContainerComponent,
overlayRef: OverlayRef,
Expand All @@ -179,15 +179,18 @@ export class NzModalService implements OnDestroy {
if (componentOrTemplateRef instanceof TemplateRef) {
modalContainer.attachTemplatePortal(
new TemplatePortal<T>(componentOrTemplateRef, null!, {
$implicit: config.nzComponentParams,
$implicit: config.nzData || config.nzComponentParams,
modalRef
} as NzSafeAny)
);
} else if (isNotNil(componentOrTemplateRef) && typeof componentOrTemplateRef !== 'string') {
const injector = this.createInjector<T, R>(modalRef, config);
const injector = this.createInjector<T, D, R>(modalRef, config);
const contentRef = modalContainer.attachComponentPortal<T>(
new ComponentPortal(componentOrTemplateRef, config.nzViewContainerRef, injector)
);
/**@deprecated
* remove this method in the next major version now modal data are passed through injection
*/
setContentInstanceParams<T>(contentRef.instance, config.nzComponentParams);
modalRef.componentInstance = contentRef.instance;
} else {
Expand All @@ -196,12 +199,15 @@ export class NzModalService implements OnDestroy {
return modalRef;
}

private createInjector<T, R>(modalRef: NzModalRef<T, R>, config: ModalOptions<T>): Injector {
private createInjector<T, D, R>(modalRef: NzModalRef<T, R>, config: ModalOptions<T, D, R>): Injector {
const userInjector = config && config.nzViewContainerRef && config.nzViewContainerRef.injector;

return Injector.create({
parent: userInjector || this.injector,
providers: [{ provide: NzModalRef, useValue: modalRef }]
providers: [
{ provide: NzModalRef, useValue: modalRef },
{ provide: NZ_MODAL_DATA, useValue: config.nzData }
]
});
}

Expand Down
73 changes: 71 additions & 2 deletions components/modal/modal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ChangeDetectionStrategy,
Component,
Directive,
Inject,
Injector,
Input,
NgModule,
Expand All @@ -26,6 +27,7 @@ import {
dispatchMouseEvent
} from 'ng-zorro-antd/core/testing';

import { NZ_MODAL_DATA } from './modal-config';
import { NzModalRef, NzModalState } from './modal-ref';
import { NzModalComponent } from './modal.component';
import { NzModalModule } from './modal.module';
Expand Down Expand Up @@ -137,6 +139,23 @@ describe('NzModal', () => {
modalRef.close();
});

it('should open a modal with data', () => {
const modalRef = modalService.create({
nzContent: TestWithModalContentComponent,
nzComponentParams: {
value: 'Modal'
},
nzData: 'NG-ZORRO'
});
fixture.detectChanges();
const modalContentElement = overlayContainerElement.querySelector('.modal-data');
expect(modalContentElement).toBeTruthy();
expect(modalContentElement!.textContent?.toString().includes('NG-ZORRO')).toBeTruthy();
expect(modalRef.getContentComponent() instanceof TestWithModalContentComponent).toBe(true);
expect(modalRef.getContentComponent().modalRef).toBe(modalRef);
modalRef.close();
});

it('should open modal with template', () => {
fixture.componentInstance.value = 'Modal';
fixture.detectChanges();
Expand All @@ -151,6 +170,50 @@ describe('NzModal', () => {
modalRef.close();
});

it('should open modal with template and pass data', () => {
fixture.componentInstance.value = 'Modal';
fixture.detectChanges();
const modalRef = modalService.create({
nzContent: fixture.componentInstance.templateRef,
nzData: 'NG-ZORRO'
});
fixture.detectChanges();
const modalContentElement = overlayContainerElement.querySelector('.modal-template-data');
expect(modalContentElement).toBeTruthy();
expect(modalContentElement!.textContent?.includes('NG-ZORRO')).toBeTruthy();
expect(fixture.componentInstance.modalRef).toBe(modalRef);
modalRef.close();
});
it('should open modal with template and pass value of Nz data even if nzComponentParams is set', () => {
fixture.componentInstance.value = 'Modal';
fixture.detectChanges();
const modalRef = modalService.create({
nzContent: fixture.componentInstance.templateRef,
nzData: 'NG-ZORRO',
nzComponentParams: 'Angular Material'
});
fixture.detectChanges();
const modalContentElement = overlayContainerElement.querySelector('.modal-template-data');
expect(modalContentElement).toBeTruthy();
expect(modalContentElement!.textContent?.includes('NG-ZORRO')).toBeTruthy();
expect(fixture.componentInstance.modalRef).toBe(modalRef);
modalRef.close();
});
it('should open modal with template and pass value of nzComponentParams if nzData is not defined', () => {
fixture.componentInstance.value = 'Modal';
fixture.detectChanges();
const modalRef = modalService.create({
nzContent: fixture.componentInstance.templateRef,
nzComponentParams: 'NG-ZORRO'
});
fixture.detectChanges();
const modalContentElement = overlayContainerElement.querySelector('.modal-template-data');
expect(modalContentElement).toBeTruthy();
expect(modalContentElement!.textContent?.includes('NG-ZORRO')).toBeTruthy();
expect(fixture.componentInstance.modalRef).toBe(modalRef);
modalRef.close();
});

it('should be thrown when attaching repeatedly', () => {
const modalRefComponent = modalService.create({
nzContent: TestWithModalContentComponent,
Expand Down Expand Up @@ -1681,8 +1744,9 @@ class TestWithOnPushViewContainerComponent {

@Component({
template: `
<ng-template let-modalRef="modalRef">
<ng-template let-modalRef="modalRef" let-data>
<span class="modal-template-content">Hello {{ value }}</span>
<span class="modal-template-data">My favorite UI framework is {{ data }}</span>
{{ setModalRef(modalRef) }}
</ng-template>
`
Expand All @@ -1703,14 +1767,19 @@ class TestWithServiceComponent {
@Component({
template: `
<div class="modal-content">Hello {{ value }}</div>
<div class="modal-data">My favorite UI Library is {{ nzModalData }}</div>
<input />
<button (click)="destroyModal()">destroy</button>
`
})
class TestWithModalContentComponent {
@Input() value?: string;

constructor(public modalRef: NzModalRef, public modalInjector: Injector) {}
nzModalData: string;

constructor(public modalRef: NzModalRef, public modalInjector: Injector, @Inject(NZ_MODAL_DATA) nzData: string) {
this.nzModalData = nzData;
}

destroyModal(): void {
this.modalRef.destroy();
Expand Down

0 comments on commit ea9969d

Please sign in to comment.