Существует несколько способов приблизиться к реализации модалов в приложении AngularJS, включая популярный сервис angular-dialog и официальный модал Angular-UI Bootstrap . В этой статье я расскажу, как мне нравится обрабатывать модалы в Angular, используя другой сервис Angular-UI, ui-router .
Мышление в Штатах
Основная идея этого подхода заключается в том, что модалы фактически являются уникальными состояниями вашего приложения. Рассмотрим сайт электронной коммерции. Когда вы нажимаете кнопку «Добавить в корзину», появляется модал с просьбой войти в систему. Вы вводите свои данные и нажимаете «Продолжить», что показывает вам другой модал с деталями вашей корзины.
Вы только что прошли через ряд состояний, которые просто оказались в модальности. Если мы рассматриваем модалы как состояния, то имеет смысл использовать инструмент управления состояниями для перехода в различные модальные состояния и из них.
Начало работы с UI Router
Давайте начнем с простого. Сначала мы установим ui-router и добавим его в наше приложение Angular.
1. Создайте простую HTML-страницу
Начнем с создания файла index.html
со следующим содержимым:
<!doctype html> <html ng-app="modalApp"> <head> <title>Modals with Angular and Ui-Router</title> <link rel="stylesheet" href="css/app.css" /> <script type="text/javascript" src="js/jquery.min.js"></script> <script type="text/javascript" src="js/angular.min.js"></script> <script type="text/javascript" src="js/angular-ui-router.min.js"></script> <script type="text/javascript" src="js/app.js"></script> </head> <body> </body> </html>
JQuery был включен для некоторых работ DOM позже.
Помимо самой последней версии Angular, я включил Angular UI Router, CSS-файл (в настоящее время пустой) и, конечно, основной файл JavaScript нашего приложения. Давайте посмотрим на это дальше.
2. Создайте свое угловое приложение
На app.js
файл app.js
невероятно прост. Мы просто создаем модуль для нашего modalApp
а затем добавляем ui.router
в качестве зависимости:
angular.module("modalApp", ["ui.router"])
3. Улучшение интерфейса
Прежде чем мы сможем открыть модальное окно, нам нужен пользовательский интерфейс для взаимодействия с пользователем. В этом случае я упростил задачу с помощью кнопки «Добавить в корзину» в index.html
:
<!doctype html> <html ng-app="modalApp"> <head> <title>Modals with Angular and Ui-Router</title> <link rel="stylesheet" href="css/app.css" /> <script type="text/javascript" src="js/jquery.min.js"></script> <script type="text/javascript" src="js/angular.min.js"></script> <script type="text/javascript" src="js/angular-ui-router.min.js"></script> <script type="text/javascript" src="js/app.js"></script> </head> <body> <h3>Pusheen Hoodie</h3> <p>Now you too can look like Pusheen!</p> <button>Add to cart</button> </body> </html>
4. Настройте начальные состояния
Мы собираемся определить несколько состояний для каждого из наших модальных типов, но сначала нужно выполнить некоторые настройки. Поскольку вполне вероятно, что мы захотим поделиться поведением между нашими различными модалами, давайте создадим родительское Modal
состояние, от которого затем могут наследоваться наши отдельные модалы. Следующий код принадлежит js/app.js
:
angular.module("modalApp", ["ui.router"]).config(function($stateProvider) { $stateProvider.state("Modal", { views:{ "modal": { templateUrl: "modal.html" } }, abstract: true }); });
Мы определили состояние под названием «Модальное». Это абстрактное состояние, означающее, что на него нельзя напрямую перейти. Он служит только родителем для своих дочерних состояний, предлагая общую функциональность. Мы также определили шаблон для именованного представления (также называемого модальным). Называя свои представления, рекомендуется избегать загрузки неправильных состояний в неправильные места в вашем приложении, особенно если вы используете ui-router для других состояний в вашем приложении.
5. Определите директиву ui-view для загрузки нашего модального режима в
Ui-router использует директиву ui-view
чтобы определить, куда должен быть загружен шаблон состояния. Мы добавим директиву ui-view
на нашу страницу index.html
:
<!doctype html> <html ng-app="modalApp"> <head> <title>Modals with Angular and Ui-Router</title> <link rel="stylesheet" href="css/app.css" /> <script type="text/javascript" src="js/jquery.min.js"></script> <script type="text/javascript" src="js/angular.min.js"></script> <script type="text/javascript" src="js/angular-ui-router.min.js"></script> <script type="text/javascript" src="js/app.js"></script> </head> <body> <h3>Pusheen Hoodie</h3> <p>Now you too can look like Pusheen!</p> <button>Add to cart</button> <div ui-view="modal" autoscroll="false"></div> </body> </html>
Поскольку мы назвали наше представление «модальным», мы должны передать это и директиве. Параметр autoscroll="false"
позволяет ui-router пытаться прокрутить модальное изображение.
6. Создание нашего первого актуального мода
Давайте определимся с нашим первым актуальным модалом. Мы создадим всплывающее окно, которое спросит пользователя, уверены ли они, что хотят добавить продукт в свою корзину.
angular.module("modalApp", ["ui.router"]).config(function($stateProvider) { $stateProvider.state("Modal", { views:{ "modal": { templateUrl: "modal.html" } }, abstract: true }); $stateProvider.state("Modal.confirmAddToCart", { views:{ "modal": { templateUrl: "modals/confirm.html" } } }); });
Просто как тот. Мы определили новое состояние confirmAddToCart
как дочерний элемент Modal
используя точечную нотацию маршрутизатора. Мы также указали шаблон для нашего нового модала.
Так как confirmAddToCart
является дочерним элементом состояния Modal
, ui-router будет искать директиву ui-view
в шаблоне состояния Modal
( modal.html
) для визуализации шаблона подтверждения. Давайте создадим оба этих шаблона дальше.
7. Создайте шаблон базового модального состояния
Шаблон Modal
состояния отвечает за создание прозрачного фона для покрытия приложения, а также за своего рода держателя, куда мы будем загружать шаблоны нашего другого дочернего состояния.
<div class="Modal-backdrop"></div> <div class="Modal-holder" ui-view="modal" autoscroll="false"></div>
Вот несколько основных стилей в css/app.css
:
* { box-sizing: border-box; } .Modal-backdrop { position: fixed; top: 0px; left: 0px; height:100%; width:100%; background: #000; z-index: 1; opacity: 0.5; } .Modal-holder { position: fixed; top: 0px; left: 0px; height:100%; width:100%; background: transparent; z-index: 2; padding: 30px 15px; }
8. Создайте шаблон подтверждения модала
Этот шаблон предназначен для самого модального окна с сообщением подтверждения. Следующий код идет в modals/confirm.html
:
<div class="Modal-box"> Are you sure you want to do that? <button>Yes</button> </div>
Стайлинг для Modal-box
:
.Modal-box { margin: 0 auto; width: 100%; background: #fff; padding: 15px; border-radius: 4px; box-shadow: 1px 2px 5px rgba(0,0,0,0.3); position: relative; } @media screen and (min-width: 992px) { .Modal-box { width: 50%; padding: 30px; } }
9. Подключите все это
Теперь, когда у нас есть родительское модальное состояние с дочерним состоянием и оба их шаблона, все, что осталось сделать, — это запустить модальное состояние. Ui-router предоставляет нам директиву ui-sref
которая ведет себя подобно href
тега привязки. Это по существу свяжет нас с названием штата, которое мы предоставляем.
<!doctype html> <html ng-app="modalApp"> <head> <title>Modals with Angular and Ui-Router</title> <link rel="stylesheet" href="css/app.css" /> <script type="text/javascript" src="js/jquery.min.js"></script> <script type="text/javascript" src="js/angular.min.js"></script> <script type="text/javascript" src="js/angular-ui-router.min.js"></script> <script type="text/javascript" src="js/app.js"></script> </head> <body> <h3>Pusheen Hoodie</h3> <p>Now you too can look like Pusheen!</p> <button ui-sref="Modal.confirmAddToCart">Add to cart</button> <div ui-view="modal" autoscroll="false"></div> </body> </html>
Теперь, когда мы нажимаем кнопку, маршрутизатор переместит нас в состояние Modal.confirmAddToCart
. Шаблон Modal
состояния сначала загрузится в директиву ui-view
в index.html
. Это покажет наш фон и подготовит владельца. Затем белый диалог подтверждения загрузится в директиву ui-view
в родительском модальном шаблоне, и эй presto! У нас есть модал!
10. Закрытие Модального
Первое, что вы можете заметить, это то, что вы не можете закрыть модал. Давайте это исправим.
AngularUI Router предоставляет нам onEnter
вызовы onEnter
и onExit
которые onEnter
при входе и выходе из состояний. Мы будем использовать onEnter
вызов onEnter
для настройки некоторых прослушивателей событий, которые будут закрывать модальное окно. Но возникает вопрос: как мы на самом деле закрываем это? Закрытие модального режима на основе состояния — это просто вопрос перехода в другое состояние. Независимо от того, было ли это состояние модальным или просто каким-то другим, состояние без операции зависит от вас. А пока давайте создадим пустое состояние по умолчанию и используем его для перехода от нашего модального состояния.
angular.module("modalApp", ["ui.router"]).config(function($stateProvider) { $stateProvider.state("Default", {}); $stateProvider.state("Modal", { views:{ "modal": { templateUrl: "modal.html" } }, onEnter: ["$state", function($state) { $(document).on("keyup", function(e) { if(e.keyCode == 27) { $(document).off("keyup"); $state.go("Default"); } }); $(document).on("click", ".Modal-backdrop, .Modal-holder", function() { $state.go("Default"); }); $(document).on("click", ".Modal-box, .Modal-box *", function(e) { e.stopPropagation(); }); }], abstract: true }); $stateProvider.state("Modal.confirmAddToCart", { views: { "modal": { templateUrl: "modals/confirm.html" } } }); });
Поangular.module("modalApp", ["ui.router"]).config(function($stateProvider) { $stateProvider.state("Default", {}); $stateProvider.state("Modal", { views:{ "modal": { templateUrl: "modal.html" } }, onEnter: ["$state", function($state) { $(document).on("keyup", function(e) { if(e.keyCode == 27) { $(document).off("keyup"); $state.go("Default"); } }); $(document).on("click", ".Modal-backdrop, .Modal-holder", function() { $state.go("Default"); }); $(document).on("click", ".Modal-box, .Modal-box *", function(e) { e.stopPropagation(); }); }], abstract: true }); $stateProvider.state("Modal.confirmAddToCart", { views: { "modal": { templateUrl: "modals/confirm.html" } } }); });
Поangular.module("modalApp", ["ui.router"]).config(function($stateProvider) { $stateProvider.state("Default", {}); $stateProvider.state("Modal", { views:{ "modal": { templateUrl: "modal.html" } }, onEnter: ["$state", function($state) { $(document).on("keyup", function(e) { if(e.keyCode == 27) { $(document).off("keyup"); $state.go("Default"); } }); $(document).on("click", ".Modal-backdrop, .Modal-holder", function() { $state.go("Default"); }); $(document).on("click", ".Modal-box, .Modal-box *", function(e) { e.stopPropagation(); }); }], abstract: true }); $stateProvider.state("Modal.confirmAddToCart", { views: { "modal": { templateUrl: "modals/confirm.html" } } }); });
События, которые мы создали, довольно тривиальны. Нам просто нужно слушать клавишу esc
, а также любые клики на заднем плане. Кроме того, был добавлен вызов e.stopPropagation()
для предотвращения щелчков внутри модального пузыря до фонового уровня и непреднамеренного закрытия модального режима.
Идите и проверьте это.
11. Переход на другой модал
Теперь, когда мы можем открывать и закрывать модальное пространство, мы можем начать видеть истинную силу этого подхода. Мало того, что это ясно и значимо, чтобы каждый модал был представлен состоянием, но это также позволяет нам проходить через состояния внутри этих модалов.
Мы начали с того, что просили пользователей подтвердить их покупку. Теперь давайте покажем им сообщение об успехе в другом модале. Опять же, мы просто определим новое состояние и связанный с ним шаблон:
angular.module("modalApp", ["ui.router"]).config(function($stateProvider) { $stateProvider.state("Default", {}); $stateProvider.state("Modal", { ... } $stateProvider.state("Modal.confirmAddToCart", { views:{ "modal": { templateUrl: "modals/confirm.html" } } }); $stateProvider.state("Modal.successfullyAdded", { views:{ "modal": { templateUrl: "modals/success.html" } } }); });
<div class="Modal-box"> Added! Yay, now you too can look like Pusheen 🙂 <button ui-sref="Default">Awesome!</button> </div>
Теперь все, что осталось сделать, это связать кнопку «Да» из режима подтверждения с новым состоянием успеха. Пока мы там, мы можем добавить кнопку «Нет», которая ссылается на наше состояние по умолчанию, чтобы закрыть модальное окно.
<div class="Modal-box"> Are you sure you want to do that? <button ui-sref="Modal.successfullyAdded">Yes</button> <button ui-sref="Default">No</button> </div>
И вот оно! Основанные на состоянии, судоходные модалы. Я считаю, что обращение с модалами как с состояниями — верный путь к тому, чтобы ваши пользователи могли без проблем использовать модалы в вашем приложении.
Поскольку модалы являются просто состояниями, вы получаете все другие дополнительные преимущества Angular UI Router, включая:
- Вложенные состояния — вы можете отображать другие состояния в пределах ваших модальных
- Обратные вызовы — запуск событий, когда ваш модальный открывается или закрывается, чтобы сообщить другим частям вашего приложения
- Параметры состояния и внедрение зависимостей. Передайте данные в ваши модальные параметры с помощью параметров состояния или служб ввода данных зависимости в контроллеры вашего состояния.
- URL-адреса. Состояния являются маршрутизируемыми URI. Это означает, что у вас может быть URL, который ссылается на модальный, довольно изящный
Весь код из этой статьи доступен на GitHub . Я хотел бы услышать ваши отзывы об этом подходе к модалам в AngularJS, поэтому, пожалуйста, оставьте комментарий 🙂