Reusability is very important when programming, and anything we can do to reduce duplication of code is going to help us out.
I have to use Modal popups to display information to users in many places in my Angular 2 project. I am using ng-bootstrap and all of these Modals have the same Header and Footer but body changes in many cases. Sometimes the body just wanted to replace a single place holder, at other times it has some complexity to prepare the dynamic content. And these are triggered or managed by different components.
ng-bootstrap allow us to pass content into a Modal in two ways.
- as a Template. here wrap the whole Modal html in 
<ng-template></ng-template> - as a Component
 
With the first approach, I have to write the header, body and footer repeatedly per modal.
With the second approach, I can wrap the HTML inside a component but need to put placeholders to make it dynamic. so then I can pass values as follows
  open() {
    const modalRef = this.modalService.open(NgbdModalContent);
    modalRef.componentInstance.name = 'World';
  } 
But the flexibility is still limited.
What I am trying to achieve is to make a reusing modal component with Content Projection [Transclusion]
So, in my Common Modal's body looks as below. I placed <ng-content></ng-content> as a slot for the Modal's body.
  @Component({
  selector: 'common-modal',
  template: `
  <!-- Modal -->
  <div class="modal fade" id="common-modal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
  <div class="modal-dialog modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="exampleModalLabel">{{title}}</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">×</span>
        </button>
      </div>
      <div class="modal-body">
        <ng-content></ng-content>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">Close</button>
      </div>
    </div>
  </div>
  </div>
  `,
Now I wish I could use it as bellow.
<common-modal title="First Modal">
  <span>Welcome Jasnan!</span>
</common-modal>
And at some other place
<common-modal title="Second Modal">   
     //......
      <tr *ngFor="let student of pagedStudents">
        <td>
          {{student.name}}
        </td>
        <td>
          {{student.grade}}
        </td>
      </tr>
     //......
</common-modal>
How can I make this? Is there a way to do this in ng-bootstrap? Thanks for helping me to figure this out.
The best solution so far is to  create a custom reusable modal component and modal service  in a shared module (where the component and service is exported), so that it can be used in any other module where shared module is imported
shared.module.ts
@NgModule({
  imports: [
    CommonModule
  ],
  declarations: [
    ModalComponent,
  ],
  providers:[ 
    ModalService
   ],
  exports:[
    ModalComponent
  ]
})
modal.component.html
<div class="custom-modal">
  <div class="model-close-btn">
      <img class="close-image" src="assets/icon/png/close.png" alt="">
  </div>
  <ng-content></ng-content>
</div>
modal.component.ts
import { Component, OnInit, OnDestroy, ElementRef, Input } from '@angular/core';
import { ModalService } from '../services/modal.service';
import { element } from '@angular/core/src/render3';
@Component({
  selector: 'custom-modal',
  templateUrl: './modal.component.html',
  styleUrls: ['./modal.component.scss']
})
export class ModalComponent implements OnInit, OnDestroy {
  @Input() id: string;
  private element: any;
  constructor(private modalService: ModalService, private el: ElementRef) {
      this.element = el.nativeElement;
  }
  ngOnInit(): void {
      let modal = this;
      // ensure id attribute exists
      if (!this.id) {
          console.error('modal must have an id');
          return;
      }
      // move element to bottom of page (just before </body>) so it can be displayed above everything else
      document.body.appendChild(this.element);
      // close modal on background click
      this.element.addEventListener('click', function (e: any) {
          if (e.target.className === 'modal__overlay modal__overlay--toggle') {
              modal.close();
          }
      });
      this.element.addEventListener('click', function (e: any) {
        if (e.target.className === 'model-close-btn' || e.target.className === 'close-image' ) {
            modal.close();
        }
    });
      // add self (this modal instance) to the modal service so it's accessible from controllers
      this.modalService.add(this);
  }
  // remove self from modal service when directive is destroyed
  ngOnDestroy(): void {
      this.modalService.remove(this.id);
      this.element.remove();
  }
  // open modal
  open(): void {
    //console.log(this.element);
      this.element.style.display = 'block';
  }
  // close modal
  close(): void {
      this.element.style.display = 'none';
  }
}
modal.component.scss
/* MODAL STYLES
-------------------------------*/
:host(custom-modal) {
  /* modals are hidden by default */
  display: none;
}
.custom-modal-open {
  /* body overflow is hidden to hide main scrollbar when modal window is open */
  display: block !important;
}
.model-close-btn {
  position: fixed;
  width: 18px;
  height: 18px;
  right: 50px;
  top: 50px;
  z-index: 9999;
  background-color: #fff;
  border-radius: 50px;
  padding: 10px;
  cursor: pointer;
  img {
    width: 18px;
  }
}
modal.service.ts
import { Injectable } from '@angular/core';
@Injectable({
  providedIn: 'root'
})
export class ModalService {
  private modals: any[] = [];
  constructor() { }
  add(modal: any) {
    // add modal to array of active modals
    this.modals.push(modal);
  }
  remove(id: string) {
    // remove modal from array of active modals
    this.modals = this.modals.filter(x => x.id !== id);
  }
  open(id: string) {
    // open modal specified by id
    let modal: any = this.modals.filter(x => x.id === id)[0];
    modal.open();
  }
  close(id: string) {
    // close modal specified by id
    let modal: any = this.modals.filter(x => x.id === id)[0];
    modal.close();
  }
}
Now if you want to use this component in a sample component in another module , do the following:
Step 1: Import the shared module to the sample module where you want to use the custom-modal
sample.module.ts
@NgModule({
  declarations: [
    SampleComponent,
  ],
  imports: [
    SharedModule
  ],
})
Step 2:
sample.component.ts
  import { ModalService } from 'src/shared/services/modal.service';
  constructor(private modalService: ModalService){}
  //  call this function to open modal by passing modal id
  openModal(id: string) {
    this.modalService.open(id);
  }
  // just call this function to close modal by passing modal id
  closeModal(id: string) {
      this.modalService.close(id); 
  }
sample.component.html
<!-- modal popup started -->
  <custom-modal id="custom-modal-one">
   // add your any custom modal template code here and logic in sample.component.ts
  </custom-modal>
<!-- modalp popup ends -->
    来源:https://stackoverflow.com/questions/44360691/how-to-make-a-modal-reusable-in-angular-2