Skip to content

Commit

Permalink
Adding Custom Header component implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Yahima Duarte committed Sep 9, 2018
1 parent d8c70d5 commit bb2c482
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 38 deletions.
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,45 @@ Action button can be of two types:
- without return value
Has no direct effect on dialog. Can be used to trigger some arbitrary functionality (e.g. copy values to clipboard)

Custom Header
1. Create a custom header component that implements `IModalHeaderDialog`.

```ts
import {Component} from '@angular/core';
import {IModalHeaderDialog} from '../../../../src/modal-dialog.interface';

@Component({
selector: 'app-custom-header-modal',
template: `
<p>This component is a custom header.</p>
<p><strong>Written By: </strong><b>{{data}}</b></p>
`
})
export class CustomHeaderModalComponent implements IModalHeaderDialog {
data: string;

setData(data: any) {
this.data = data;
}
}
```

2. Inject the `ModalDialogService` where you want to open the dialog passing the headerComponent as a new parameter instead of the title attribute:
```ts
constructor(modalService: ModalDialogService, viewRef: ViewContainerRef) { }

openNewDialog() {
this.modalDialogService.openDialog(this.viewContainer, {
headerComponent: CustomHeaderModalComponent,
childComponent: CustomModalComponent,
settings: {
closeButtonClass: 'close theme-icon-close'
},
data: 'Yahima Duarte <[email protected]>'
});
}
```

## API

### ModalDialogService
Expand All @@ -122,6 +161,7 @@ Modal heading text
```ts
interface IModalDialogOptions<T> {
title: string;
headerComponent: IModalHeaderDialog;
childComponent: IModalDialog;
onClose: ModalDialogOnAction;
actionButtons: IModalDialogButton[];
Expand All @@ -136,6 +176,9 @@ This is generic interface, where `T` is arbitrary type of `data` section.
- title: `string`
Modal heading text

- headerComponent: `any`
Component type that will be rendered as a header of modal dialog. Component must implement `IModalHeaderDialog` interface.

- childComponent: `any`
Component type that will be rendered as a content of modal dialog. Component must implement `IModalDialog` interface.

Expand Down
2 changes: 1 addition & 1 deletion demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"karma-coverage-istanbul-reporter": "~2.0.0",
"karma-jasmine": "~1.1.1",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "~5.3.0",
"protractor": "^5.4.0",
"ts-node": "~5.0.1",
"tslint": "~5.9.1",
"typescript": "~2.7.2"
Expand Down
5 changes: 5 additions & 0 deletions demo/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
<button class="btn btn-success" type="button" (click)="openCustomModal()">Dialog with custom child component</button>
</div>
</div>
<div class="row mb-2">
<div class="col">
<button class="btn btn-success" type="button" (click)="openCustomHeaderModal()">Dialog with custom header component</button>
</div>
</div>
<div class="row mb-2">
<div class="col">
<button class="btn btn-danger" type="button" (click)="openMultipleModal()">Multiple modal dialogs on top of each other</button>
Expand Down
44 changes: 31 additions & 13 deletions demo/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { Component, ViewContainerRef } from '@angular/core';
import { ModalDialogService, SimpleModalComponent } from 'ngx-modal-dialog';
import { CustomModalComponent } from './dialogs/custom-modal.component';
import { DynamicModalComponent } from './dialogs/dynamic-modal.component';
import {Component, ViewContainerRef} from '@angular/core';
import {ModalDialogService, SimpleModalComponent} from 'ngx-modal-dialog';
import {CustomModalComponent} from './dialogs/custom-modal.component';
import {DynamicModalComponent} from './dialogs/dynamic-modal.component';
import {CustomHeaderModalComponent} from './dialogs/custom-header-modal.component';

@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
constructor(private modalDialogService: ModalDialogService, private viewContainer: ViewContainerRef) {}
constructor(private modalDialogService: ModalDialogService, private viewContainer: ViewContainerRef) {
}

openSimpleModal() {
this.modalDialogService.openDialog(this.viewContainer, {
Expand Down Expand Up @@ -92,6 +94,18 @@ export class AppComponent {
});
}

openCustomHeaderModal() {
this.modalDialogService.openDialog(this.viewContainer, {
title: 'Custom child component',
headerComponent: CustomHeaderModalComponent,
childComponent: CustomModalComponent,
settings: {
closeButtonClass: 'close theme-icon-close'
},
data: 'Yahima Duarte <[email protected]>'
});
}

openDynamicModal() {
this.modalDialogService.openDialog(this.viewContainer, {
title: 'Dynamic child component',
Expand All @@ -106,30 +120,34 @@ export class AppComponent {
this.modalDialogService.openDialog(this.viewContainer, {
title: 'Dialog 1',
childComponent: SimpleModalComponent,
settings: { closeButtonClass: 'close theme-icon-close' },
settings: {closeButtonClass: 'close theme-icon-close'},
placeOnTop: true,
data: { text: `
data: {
text: `
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.` }
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`
}
});
this.modalDialogService.openDialog(this.viewContainer, {
title: 'Dialog 2',
childComponent: SimpleModalComponent,
settings: { closeButtonClass: 'close theme-icon-close' },
settings: {closeButtonClass: 'close theme-icon-close'},
placeOnTop: true,
data: { text: `
data: {
text: `
Lorem ipsum is placeholder text commonly used in the graphic, print,
and publishing industries for previewing layouts and visual mockups.` }
and publishing industries for previewing layouts and visual mockups.`
}
});
this.modalDialogService.openDialog(this.viewContainer, {
title: 'Dialog 3',
childComponent: SimpleModalComponent,
settings: { closeButtonClass: 'close theme-icon-close' },
settings: {closeButtonClass: 'close theme-icon-close'},
placeOnTop: true,
data: { text: 'Some text content' }
data: {text: 'Some text content'}
});
}
}
20 changes: 11 additions & 9 deletions demo/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';

import { AppComponent } from './app.component';
import { CustomModalComponent } from './dialogs/custom-modal.component';
import { ModalDialogModule } from 'ngx-modal-dialog';
import { DynamicModalComponent } from './dialogs/dynamic-modal.component';
import {AppComponent} from './app.component';
import {CustomModalComponent} from './dialogs/custom-modal.component';
import {ModalDialogModule} from 'ngx-modal-dialog';
import {DynamicModalComponent} from './dialogs/dynamic-modal.component';
import {CustomHeaderModalComponent} from './dialogs/custom-header-modal.component';

@NgModule({
declarations: [AppComponent, CustomModalComponent, DynamicModalComponent],
declarations: [AppComponent, CustomModalComponent, DynamicModalComponent, CustomHeaderModalComponent],
imports: [
BrowserModule,
ModalDialogModule.forRoot()
],
entryComponents: [CustomModalComponent, DynamicModalComponent],
entryComponents: [CustomModalComponent, DynamicModalComponent, CustomHeaderModalComponent],
bootstrap: [AppComponent]
})
export class AppModule { }
export class AppModule {
}
17 changes: 17 additions & 0 deletions demo/src/app/dialogs/custom-header-modal.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {Component} from '@angular/core';
import {IModalHeaderDialog} from '../../../../src/modal-dialog.interface';

@Component({
selector: 'app-custom-header-modal',
template: `
<p>This component is a custom header.</p>
<p>Written By: <b>{{data}}</b></p>
`
})
export class CustomHeaderModalComponent implements IModalHeaderDialog {
data: string;

setData(data: any) {
this.data = data;
}
}
26 changes: 22 additions & 4 deletions src/modal-dialog.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import {
IModalDialog,
IModalDialogOptions,
IModalDialogButton,
IModalDialogSettings, ModalDialogOnAction
IModalDialogSettings, ModalDialogOnAction,
IModalHeaderDialog
} from './modal-dialog.interface';
import { Observable, Subject, from } from 'rxjs';
import { AdHeaderDirective } from './modal-dialog.ad-header.directive';

/**
* Modal dialog component
Expand Down Expand Up @@ -55,14 +57,20 @@ import { Observable, Subject, from } from 'rxjs';
-moz-animation-name: shake;
animation-name: shake;
}
.modal-title {
display: inline-flex;
}
`],
template: `
<div *ngIf="settings.overlayClass && showOverlay" [ngClass]="[settings.overlayClass, animateOverlayClass]"></div>
<div [ngClass]="[settings.modalClass, animateModalClass]" #dialog>
<div [ngClass]="settings.modalDialogClass">
<div [ngClass]="[ showAlert ? settings.alertClass : '', settings.contentClass]">
<div [ngClass]="settings.headerClass">
<h4 [ngClass]="settings.headerTitleClass">{{title}}</h4>
<div [ngClass]="settings.headerTitleClass">
<ng-template ad-header></ng-template>
<h4 *ngIf="title">{{title}}</h4>
</div>
<button (click)="close()" *ngIf="!actionButtons || !actionButtons.length" type="button"
[title]="settings.closeButtonTitle"
[ngClass]="settings.closeButtonClass">
Expand All @@ -83,6 +91,7 @@ import { Observable, Subject, from } from 'rxjs';
})
export class ModalDialogComponent implements IModalDialog, OnDestroy, OnInit {
@ViewChild('modalDialogBody', { read: ViewContainerRef }) public dynamicComponentTarget: ViewContainerRef;
@ViewChild(AdHeaderDirective) adHeader: AdHeaderDirective;
@ViewChild('dialog') private dialogElement: ElementRef;
public reference: ComponentRef<IModalDialog>;

Expand Down Expand Up @@ -147,8 +156,8 @@ export class ModalDialogComponent implements IModalDialog, OnDestroy, OnInit {

// inject component
if (options.childComponent) {
let factory = this.componentFactoryResolver.resolveComponentFactory(options.childComponent);
let componentRef = this.dynamicComponentTarget.createComponent(factory) as ComponentRef<IModalDialog>;
const factory = this.componentFactoryResolver.resolveComponentFactory(options.childComponent);
const componentRef = this.dynamicComponentTarget.createComponent(factory) as ComponentRef<IModalDialog>;
this._childInstance = componentRef.instance as IModalDialog;

this._closeDialog$ = new Subject<void>();
Expand All @@ -163,6 +172,15 @@ export class ModalDialogComponent implements IModalDialog, OnDestroy, OnInit {
(document.activeElement as HTMLElement).blur() :
(document.body as HTMLElement).blur();
}
if (options.headerComponent) {
const factory = this.componentFactoryResolver.resolveComponentFactory(options.headerComponent);

const viewContainerRef = this.adHeader.viewContainerRef;
viewContainerRef.clear();
const componentRef = viewContainerRef.createComponent(factory);
(<IModalHeaderDialog>componentRef.instance).setData(options.data);
}

// set options
this._setOptions(options);
}
Expand Down
5 changes: 5 additions & 0 deletions src/modal-dialog.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ export interface IModalDialog {
dialogInit: (reference: ComponentRef<IModalDialog>, options: Partial<IModalDialogOptions<any>>) => void;
}

export interface IModalHeaderDialog {
setData(data: any): void;
}

export interface IModalDialogOptions<T> {
title: string;
headerComponent: any;
childComponent: any;
onClose: () => Promise<any> | Observable<any> | boolean;
actionButtons: IModalDialogButton[];
Expand Down
3 changes: 2 additions & 1 deletion src/modal-dialog.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ModalDialogInstanceService } from './modal-dialog-instance.service';
// modules
import { CommonModule } from '@angular/common';
import { NgModule, ModuleWithProviders, InjectionToken, SkipSelf, Optional } from '@angular/core';
import { AdHeaderDirective } from './modal-dialog.ad-header.directive';

/**
* Guard to make sure we have single initialization of forRoot
Expand All @@ -15,7 +16,7 @@ export const MODAL_DIALOG_FORROOT_GUARD = new InjectionToken<ModalDialogModule>(

@NgModule({
imports: [CommonModule],
declarations: [ModalDialogComponent, SimpleModalComponent],
declarations: [ModalDialogComponent, SimpleModalComponent, AdHeaderDirective],
entryComponents: [ModalDialogComponent, SimpleModalComponent],
exports: [ModalDialogComponent, SimpleModalComponent],
providers: [ModalDialogService]
Expand Down
9 changes: 3 additions & 6 deletions src/simple-modal.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, ComponentRef } from '@angular/core';
import { Component, ComponentRef, HostBinding } from '@angular/core';
import { IModalDialog, IModalDialogOptions } from './modal-dialog.interface';

export interface ISimpleModalDataOptions {
Expand All @@ -8,13 +8,10 @@ export interface ISimpleModalDataOptions {
@Component({
selector: 'simple-modal-dialog',
template: ``,
styles: [':host { display: block; }'],
host: {
'[innerHTML]': 'text'
}
styles: [':host { display: block; }']
})
export class SimpleModalComponent implements IModalDialog {
text: string;
@HostBinding('innerHTML') text: string;

/**
* Initialize dialog with simple HTML content
Expand Down
Loading

0 comments on commit bb2c482

Please sign in to comment.