【问题标题】:Modal window with custom URL in AngularJSAngularJS中带有自定义URL的模态窗口
【发布时间】:2012-11-28 13:48:34
【问题描述】:

我需要在我的 Angular 应用程序中显示一个模态窗口(基本上只是一个隐藏的 div,它将使用已编译的模板加载)。问题是,我需要在模式打开时更改 URL,以便用户可以复制链接并直接转到模式窗口,还可以使用后退按钮关闭模式窗口并返回上一页。这类似于 Pinterest 在您单击图钉时处理模态窗口的方式。

到目前为止,我已经创建了一个加载模板的指令,使用 $compile 对其进行编译,注入 $scope,然后显示编译后的模板。这工作正常。

问题是,只要我使用 $location 更改路径,路由控制器就会触发并将模板加载到 ng-view 中。

我想了两种方法来克服这个问题,但都没有实现:

  1. 当我使用 $location 更改 url 时,以某种方式阻止路由控制器触发。我在 $routeChangeStart 中添加了一个侦听器以防止发生默认情况,但这似乎不起作用。

  2. 以某种方式向页面添加另一个视图处理程序(页面上基本上有 2 个命名的 ng-view 指令)并且每个都能够处理不同的路由。不过目前看不到 Angular 支持这一点。

网址的格式必须是 /item/item_id 而不是 /item?item_id=12345。

任何帮助将不胜感激。

【问题讨论】:

    标签: angularjs


    【解决方案1】:

    如果您使用此处所述的 ui.router 模块,您现在可以执行此操作:https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-open-a-dialogmodal-at-a-certain-state

    他们的例子:

    $stateProvider.state("items.add", {
        url: "/add",
        onEnter: function($stateParams, $state, $modal, $resource) {
            $modal.open({
                templateUrl: "items/add",
                resolve: {
                  item: function() { new Item(123).get(); }
                },
                controller: ['$scope', 'item', function($scope, item) {
                    $scope.dismiss = function() {
                        $scope.$dismiss();
                    };
    
                    $scope.save = function() {
                        item.update().then(function() {
                            $scope.$close(true);
                        });
                    };
                }]
            }).result.then(function(result) {
                if (result) {
                    return $state.transitionTo("items");
                }
            });
        }
    });
    

    我还将“errorCallback”函数传递给 then() 函数,以通过按 Escape 或单击背景来处理模态解除。

    【讨论】:

      【解决方案2】:

      我也有类似的要求。我需要:

      • 触发路由更改的模态窗口,因此它有自己的哈希 url。
      • 当您访问模态 url 时,主屏幕加载然后触发模态弹出。
      • 后退按钮关闭模式(假设您从主屏幕开始)。
      • 当您单击“确定”或“取消”时,模式会关闭,路线会返回主屏幕。
      • 当您点击背景关闭模态框时,路线会变回主屏幕。

      我找不到所有这些一起工作的好例子,但我能够找到零碎的例子,所以我把它们放在一个工作示例中,如下所示:

      <!DOCTYPE html>
      <html lang="en" ng-app="app">
      
      <head>
        <meta charset="UTF-8">
        <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.0/angular.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.11/angular-ui-router.js"></script>
        <script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.11.2/ui-bootstrap-tpls.js"></script>
        <link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.1/css/bootstrap.css" rel="stylesheet">
        <script>
      
          // init the app with ui.router and ui.bootstrap modules
          var app = angular.module('app', ['ui.router', 'ui.bootstrap']);
      
          // make back button handle closing the modal
          app.run(['$rootScope', '$modalStack',
            function($rootScope, $modalStack) {
              $rootScope.$on('$stateChangeStart', function() {
                var top = $modalStack.getTop();
                if (top) {
                  $modalStack.dismiss(top.key);
                }
              });
            }
          ]);
      
          // configure the stateProvider
          app.config(['$stateProvider', '$urlRouterProvider',
            function($stateProvider, $urlRouterProvider) {
      
              // default page to be "/" (home)
              $urlRouterProvider.otherwise('/');
      
              // configure the route states
              $stateProvider
      
                // define home route "/"
                .state('home', {
                  url: '/'
                })
      
                // define modal route "/modal"
                .state('modal', {
                  url: '/modal',
      
                  // trigger the modal to open when this route is active
                  onEnter: ['$stateParams', '$state', '$modal',
                    function($stateParams, $state, $modal) {
                      $modal
      
                        // handle modal open
                        .open({
                          template: '<div class="modal-header"><h3 class="modal-title">Modal</h3></div><div class="modal-body">The modal body...</div><div class="modal-footer"><button class="btn btn-primary" ng-click="ok()">OK</button><button class="btn btn-warning" ng-click="cancel()">Cancel</button></div>',
                          controller: ['$scope',
                            function($scope) {
                              // handle after clicking Cancel button
                              $scope.cancel = function() {
                                $scope.$dismiss();
                              };
                              // close modal after clicking OK button
                              $scope.ok = function() {
                                $scope.$close(true);
                              };
                            }
                          ]
                        })
      
                        // change route after modal result
                        .result.then(function() {
                          // change route after clicking OK button
                          $state.transitionTo('home');
                        }, function() {
                          // change route after clicking Cancel button or clicking background
                          $state.transitionTo('home');
                        });
      
                    }
                  ]
      
                });
            }
          ]);
        </script>
      </head>
      
      <body>
      
        <a href="#/modal" class="btn btn-default">POPUP!</a>
      
        <div ui-view></div>
      
      </body>
      
      </html>
      

      您可以在 plunker 上查看它的实际效果: http://plnkr.co/edit/tLyfRP6sx9C9Vee7pOfC

      单击 plunker 中的“打开嵌入式视图”链接以查看哈希标签的工作情况。

      【讨论】:

      • 不幸的是,这里的这个和其他解决方案有一个很大的缺陷,即。以这种方式使用 ui-router 模式会杀死底层控制器,并且每次都会重新创建它。这意味着在模态关闭后,主视图再次初始化,您将失去整个状态(如分页、过滤器等)。我一直在尝试以几种方式实现类似的东西,但这对于实际应用程序来说是不可接受的。为了以良好的外观和感觉实现它,您需要在每个控制器中做出额外的努力来恢复以前的状态,或者将模态作为每个视图的子状态。
      【解决方案3】:

      hermanschutte 的解决方案有效(请参阅 comment on Tiago Roldão's post)。我正在逐步发布以帮助澄清。

      首先,在配置中:

      .when('/posts', {
        templateUrl: 'views/main.html',
        controller: 'MainCtrl',
        reloadOnSearch: false     // this allows us to use the back button to get out of modals.
      })
      

      然后,在打开模式时(您需要 location 模块):

      $location.search('modal');
      

      在模态控制器中:

      $scope.$on('$locationChangeSuccess', function() {
        $scope.close(); // call your close function
      });
      

      【讨论】:

      • 谢谢,但很抱歉我不知道如何实现这一点。有一个完整的可重现示例会很棒。
      【解决方案4】:

      您提到的命名视图解决方案在 angularJS 邮件列表上进行了很好的讨论,但截至目前(以及近期到中期),$route 系统无法处理多个视图。

      正如我在几个地方说过的,在 SO 中也有几次,$route 系统是 angular 非常固执的立场可能成为障碍的情况之一:它的本意是简单,但它不是'不是很可配置。我个人不直接使用它,而是通过我找到here 的解决方案绕过它。

      它使事情变得有点复杂,但它为您提供了更大的灵活性——这个想法是 $route 仅用于触发(手动)渲染功能,您可以在其中分配值并根据需要触发 $broadcasts(通常,来自主控制器)。

      我没有尝试过的一件事是“混合”解决方案 - 即正常使用 $route 系统,并将 /item/item_id 路由配置为没有视图参数(以免更改主视图,由其他路由发起)并通过 $routeChangeStart 事件执行某些操作(请记住,您可以为路由分配任何值,正如您在我引用的页面中看到的那样)

      再次,我个人更喜欢完全使用替代方法。

      【讨论】:

      • 谢谢。虽然这个解决方案应该可以工作,但它会使应用程序复杂化很多。现在我选择将弹出窗口的模板包装在一个指令中,然后将其加载到第一页的隐藏 div 中。我已经为所有路由设置了配置以忽略对搜索参数的更改,然后当模式弹出窗口打开时,我使用 $location.search() 添加搜索参数。这行得通,但不幸的是,这不是最好的解决方案。但是在 Angular 支持多个视图之前,这是必须的。
      【解决方案5】:

      您可能想尝试获取 AngularJS 源代码,调整并加载您自己的 $route 和 $routeProvider 酿造,然后将它们注入 Angular 本机。

      你可能只需要添加一个标志来拦截路由更改的触发器。

      另一个更简单的选择是挂钩到事件系统,使用$scope.$on(event_name, function callback(ev) {...}) 并在传入的事件上调用.preventDefault(),希望它会停止内部$route 触发器。定位系统会广播$locationChangeStart 事件,它会在产生$locationChangeSuccess 事件之前检查defaultPrevented 属性。

      别忘了公布你的结果!

      【讨论】:

      • 不幸的是,当您调用 .preventDefault() 时,使用 $locationChangeStart 会导致 URL 不更新。所以看起来这行不通。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多