Обичам bootstrap. Тази компонентна и стилова рамка предлага система за създаване на добър интерфейс. MDBootstrap е инициатива, която ни позволява да използваме bootstrap в Angular. Въпреки това, Bootstrap е по-фокусиран върху самите компоненти и стилове. За да използваме тези компоненти и да ги използваме елегантно, имаме нужда от някаква опора между основата и компонентите.

Angular Material CDK може да се използва за преодоляване на празнината, той предлага много добра инфраструктура за изграждане на цялостен потребителски интерфейс на Angular. Днес разглеждам конкретен проблем, който имах: Да създам диалогов прозорец за потвърждение, без да се налага да вграждам mdbModal html всеки път, когато е необходимо обикновено потвърждение.

Това е необходимо за създаване на mdbModal в MDBootstrap.

<div mdbModal #frame="mdbModal" class="modal fade" id="frameModalTop" tabindex="-1" role="dialog"
  aria-labelledby="myModalLabel" aria-hidden="true" (open)="onOpen($event)">
  <div class="modal-dialog modal-notify modal-success" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <p class="heading lead">Modal Success</p>

        <button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="frame.hide()">
          <span aria-hidden="true" class="white-text">&times;</span>
        </button>
      </div>

      <div class="modal-body">
        I'm the modal body
      </div>

      <!--Footer-->
      <div class="modal-footer justify-content-center">
        <a type="button" mdbBtn color="primary" class="waves-light" mdbWavesEffect>Yes
          <i class="far fa-gem ml-1"></i>
        </a>
        <a type="button" mdbBtn color="primary" outline="true" class="waves-effect" mdbWavesEffect (click)="frame.hide()"
          data-dismiss="modal">No</a>
      </div>
    </div>
    <!--/.Content-->
  </div>
</div>

Какво чудовище! Честно казано, не мисля, че някой би искал да използва горното само за създаване на диалогов прозорец Да/Не.

Очакваният начин за създаване на диалогов прозорец за потвърждение е метод, който връща Observable‹boolean›. Нещо подобно на по-долу.

const obs$: Observable<boolean> = this.commonDialog
  .openConfirmDialog('Please confirm', 'My message');

Вземете 1, ъглов компонент

Започнах, като създадох нов компонент. Компонентът получава заглавие и съобщение като вход и излъчва булево като изход.

<app-confirm-dialog #confirm title="Please confirm" message="My message"></app-confirm-dialog>

Това може да работи, но не до степента, в която искам да бъде. Когато исках потвърждение, най-вероятно искам да се държа според изхода. В резултат на това кодът най-вероятно е необходим в метода Typescript, а не в html шаблона. За да се справя с този диалогов прозорец, ще трябва да използвам @ViewChild в класа на компонента и това определено не е идеално.

Вземете 2, A услуга

След това прочетох изходните кодове на MatDialog, опитвайки се да разбера как работят тези неща. Оказва се, че можем да използваме услуга.

Чакай, къде трябва да поставим модалното съдържание, ако използваме услуга?

Трябва ли да създадем елемент под тялото на документа и да го премахнем, когато диалоговият прозорец е затворен?

Вземете 3, наслагване

Материалното наслагване прави точно това, създайте елемент под тялото на документа и прикрепете вашия компонент към него. Можем да се абонираме за събитието за затваряне на mdbModal и да отделим компонента от Overlay.

Кодът на услугата изглежда така.

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

  constructor(private overlay: Overlay,
              private injector: Injector) {
  }

  openConfirmDialog(title: string, content: string | ComponentType<any> | TemplateRef<any>) {
    const overlayRef = this.overlay.create(new OverlayConfig({
      positionStrategy: this.overlay.position().global(),
      hasBackdrop: false, disposeOnNavigation: true
    }));
    const modalComponent = new ComponentPortal(ConfirmDialogComponent, null, this.injector);
    const ref = overlayRef.attach(modalComponent);
    return ref.instance.display(title, content).pipe(
      tap(() => {
        // destroy the ref and detach from the overlay
        ref.destroy();
        overlayRef.detach();
      })
    );
  }
}

Няколко пояснения.

  1. Не ни интересува позицията на наслагването. Действителната позиция се контролира от mdbModal (капсулиран в ConfirmDialogComponent). Това, което искаме от Overlay, е просто място, в което да поставим mdbModal.
  2. Не се нуждаем от фона на наслагването, mdbModal така или иначе има собствен фон.
  3. Методът ConfirmDialogComponent.display() е мястото, където свързвам заглавието и съдържанието на диалоговия прозорец и връщам тема. Субектът ще се излъчва, когато се щракне върху бутона „Да“ или „Не“. (Или ако диалоговият прозорец е затворен без отговор) Използвам също Material CDK Portal в този метод, така че съдържанието да може да приеме или друг компонент, или фрагмент от шаблон.

Имам

Беше необходима малка корекция, за да работи диалоговият прозорец. CDK Overlay има z-индекс 1000, което е много високо. Z-индексът на фона на mdbModal обаче е дори по-висок със стойност 1040. Ще трябва или да увеличите z-индекса на cdk наслагването, или да намалите z-индекса на фона mdbModal.

.cdk-overlay-container {
  z-index: 1050;
}

Крайни резултати

Ето как използвам диалоговия прозорец за потвърждение в крайна сметка.

constructor(private commonDialog: CommonDialog) {}
onAction(check: TemplateRef<any> | string) {
  this.commonDialog.openConfirmDialog('Please confirm', check).pipe(
    filter(identity),
    tap(() => do_something())
  ).subscribe();
}

Другите често използвани диалогови прозорци могат да бъдат добавени по подобен начин.

Научихте ли нещо ново? Ако е така, моля:

бутон за пляскане 👏 по-долу️, за да могат повече хора да видят това