Статьи

Создание модальных состояний в AngularJS с Angular UI Router

Существует несколько способов приблизиться к реализации модалов в приложении 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, поэтому, пожалуйста, оставьте комментарий 🙂