Angular 2+ modal окно c помощью ng-template

Когда несколько месяцев назад передо мной встала задача — как реализовать модальное окно на новом Angular, то в поисках ответа я перерыла интернет и пришла к нескольким мыслям:

  1. Существующие в виде пакетов решения слишком громоздки, так что нужно свое, реализующие только то, что необходимо.
  2. Кажется все будет не так уж просто в реализации, хотя на словах все не так страшно.

Что такое модальное/диалоговое окно или проще говоря popup ?

Это некий html код, который появляется на странице. У него есть определенные стили, представляющие его всплывающим окном.
В рамках нового Angular это компонент. То есть нам надо вставить в наше дерево компонентов новый компонент.

Так же нам необходим механизм закрытия/скрытия модального окна.

Таким образом, нужны:

  • компонент, который будет реализовывать функционал вставки
  • компонент, который мы будет вставлять
  • сервис, с помощью которого все наши потенциальные компоненты на вставку/открытие будут общаться.

PopupService

Начнем с сервиса.

Я его реализовала с помощью ReplaySubject.  ReplaySubject похож на BehavierSubject (про который писала в посте  Angular 2 сервис с observable).

Но он может хранить более одного переданного значения. В данном случае нам это дает возможность ‘поймать’ не только последний открытый/закрытый попап, но и большее количество. При желании.

Метод asObservable() делает наш объект только читаемым, таким образом мы защищаем нашу систему от нежелательного вмешательства в стороннем коде.

Теперь через объект popupDialog$ нельзя будет испускать (передавать всем подписчикам) значение, которое нам не нужно. Следовательно, подписчики могут получить либо ‘open’, либо ‘close’. Значения, которые popupDialog испускает в методах сервиса open и close.

 

import {Injectable} from '@angular/core';
import { Subject }    from 'rxjs/Subject';
import { Observable }    from 'rxjs/Observable';
import {ReplaySubject} from 'rxjs/Rx';

@Injectable()

export class PopupService {

    private popupDialog = new ReplaySubject<{popupEvent: string, component?: any, options?: {}}>();

    public popupDialog$ = this.popupDialog.asObservable();

    open(component: any, options?: any) {
        this.popupDialog.next({popupEvent: 'open', component: component, options: options});
    }

    close() {
        this.popupDialog.next({popupEvent: 'close'});
    }


}

Компонент, вставляющий модальное окно

В шаблоне данного компонента мы будет использовать директиву — popup-host, размещенную в компоненте ng-template, который нам как раз и позволяет вставлять один компонент в другой. А пометка-директива popup-host дает понять куда именно вставлять.

import {
    Component,
    AfterViewInit,
    ComponentFactoryResolver,
    OnDestroy,
    ViewContainerRef,
    ViewChild
} from '@angular/core';

import {Subscription} from 'rxjs/Subscription';


import {PopupService} from './popup.service';
import {PopupDirective} from './popup.directive';

export interface PopupComponent {
    data: any;
    close: any;
}

@Component({
    selector: 'xp-popup',
    template: `
        <div class="modal-block">
            <ng-template popup-host></ng-template>
        </div>
`
})

export class XpPopupComponent implements AfterViewInit, OnDestroy {

    subscription: Subscription;
    /* инжектим child PopupDirective как свойство popupHost в этом родительском контейнере */
    @ViewChild(PopupDirective) popupHost: PopupDirective;

    constructor(private popupService: PopupService, private componentFactoryResolver: ComponentFactoryResolver, private viewContainerRef: ViewContainerRef) {
    }

    ngAfterViewInit() {

        this.subscription = this.popupService.popupDialog$.subscribe((data) => {
            if (!!data && data.popupEvent === 'open') {
                this.open(data);
            } else if (data &&
                ( data.popupEvent === 'close')
            ) {
                this.close();
            }
        });
    }

    ngOnDestroy() {
        this.subscription.unsubscribe();
    }

    open(data) {

        let componentFactory = this.componentFactoryResolver.resolveComponentFactory(data.component);
        /* получаем доступ к контейнеру, куда будем вставлять компонент */
        this.viewContainerRef = this.popupHost.viewContainerRef;
        // Удаляем любые уже открытые диалоговые окна
        this.viewContainerRef.clear();

        /* инжектим компонент в контейнер*/
        let componentRef = this.viewContainerRef.createComponent(componentFactory);
        // передаем опции в вставляемый компонент, 
        // которые будут доступны в его шаблоне (например заголовок попапа)
        (<PopupComponent>componentRef.instance).data = data.options;

    }

    close() {
        this.viewContainerRef.detach(0);
    }
}

 

Сама директива очень простая

import { Directive, ViewContainerRef, Inject } from '@angular/core';

@Directive({
  selector: '[popup-host]'
})

export class PopupDirective {

  constructor(public viewContainerRef: ViewContainerRef) {
  }

}

А вот пример компонента на показ в диалоговом окне


import { Component, Input }  from '@angular/core';
import { PopupService } from  './../popup.service';
export interface PopupComponent {
    data: any;
}

@Component({
    selector: 'success-popup',
    template: `
         <div class="ui-modal">
             <h1>{{ data.title }}</h1>
             <div class="modal-message">{{ data.message }}</div>
             <button ng-click="close()" class="btn btn__success"></button>
         </div>
`
})

export class SuccessModalComponent implements PopupComponent {

    @Input() data: {};

    constructor( private _popupService: PopupService) {}

    close() {
        this._popupService.close();
    }
}

А попроще?

Спустя время после реализации попапного модуля (со всем вышеперечисленным) я пришла к значительно более простому решению. О нем в посте Angular 2+ модальное окно с помощью router-outlet

Хотите быть в курсе новых статей?

  1. Здравствуйте Front Nika. Я взял — за основу именно это модальное окно, но у меня никак не получается его довести до ума. SuccessModalComponent вставляется в компонент XpPopupComponent- но само модальное окно не показывается. Есть ли ссылка на полный пример?

  2. Григорий, здравствуйте! Полный пример показать не могу, но если код компонента вставился, но не отображается, то стоит смотреть что с css стилями. Если же не вставился, то можно проверить все ли подключено в app.module.