AngularJS中带有自定义URL的模态窗口

时间:2022-04-01 06:53:50

I need to display a modal window (basically just a hidden div that will be loaded with a compiled template) in my Angular app. The problem is, I need the URL to change when the modal opens so users can copy the link and go directly to the modal window and also use the back button to close the modal window and return to the previous page. This is similar to the way Pinterest handles modal windows when you click on a pin.

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

So far I've created a directive that loads the template, compiles it using $compile, injects the $scope and then displays the compiled template. This works fine.

到目前为止,我已经创建了一个加载模板的指令,使用$ compile编译它,注入$ scope然后显示已编译的模板。这很好用。

The problem is as soon as I use $location to change the path, the route controller fires and loads the template into ng-view.

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

I thought of 2 ways of overcoming this, but have not been able to implement either:

我想到了两种克服这个问题的方法,但是还没能实现:

  1. Somehow prevent the route controller from firing when I change the url using $location. I've added a listener to $routeChangeStart to prevent the default from happening, but that does not seem to work.

    当我使用$ location更改URL时,以某种方式阻止路由控制器触发。我已经为$ routeChangeStart添加了一个监听器来防止默认发生,但这似乎不起作用。

  2. Somehow add another view handler to the page (basically have 2 named ng-view directives on the page) and have each able to handle different routes. Can't see that Angular supports this at the moment though.

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

The URL needs to be of the format /item/item_id and not /item?item_id=12345.

URL必须是格式/ item / item_id而不是/ item?item_id = 12345。

Any help would be appreciated.

任何帮助,将不胜感激。

5 个解决方案

#1


13  

You can now do this if you use the ui.router module as described here: https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-open-a-dialogmodal-at-a-certain-state.

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

Their example:

他们的例子:

$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");
            }
        });
    }
});

I also pass an 'errorCallback' function to the then() function to handle modal dismissal by pressing Escape or clicking on the background.

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

#2


7  

I had similar requirements. I needed:

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

  • Modal window to trigger a change to the route, so it has it's own hash url.
  • 用于触发路由更改的模态窗口,因此它具有自己的哈希URL。
  • When you visit the modal url, the main screen loads then triggers the modal to popop.
  • 当您访问模态URL时,主屏幕加载然后触发模式到popop。
  • The back button closes the modal (assuming you started on the home screen).
  • 后退按钮关闭模态(假设您在主屏幕上启动)。
  • When you click OK or Cancel the modal is closed and the route changes back to the home screen.
  • 单击“确定”或“取消”时,模式将关闭,路径将更改回主屏幕。
  • When you click the background to close the modal, the route changes back to the home screen.
  • 单击背景以关闭模式时,路径将更改回主屏幕。

I couldn't find a good example of all of this working together, but I was able to find examples of bits and pieces, so I put them all together into a working example as follows:

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

<!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>

You can view it in action at plunker: http://plnkr.co/edit/tLyfRP6sx9C9Vee7pOfC

你可以在plunker上查看它:http://plnkr.co/edit/tLyfRP6sx9C9Vee7pOfC

Click the "Open the embedded view" link in plunker to see the hash tag working.

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

#3


5  

hermanschutte's solution worked (see comment on Tiago Roldão's post). I'm posting step by step to help clarify.

hermanschutte的解决方案有效(见TiagoRoldão的帖子评论)。我正在逐步发布以帮助澄清。

First, in the config:

首先,在配置中:

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

Then, on opening modal (you'll need the location module):

然后,在打开模态(你需要位置模块):

$location.search('modal');

And in modal controller:

在模态控制器中:

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

#4


2  

The named view solution you mentioned has had great discussion on the angularJS mailing list, but as of now (and the near to mid-future) the $route system doesn't handle multiple views.

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

As I have said in a few places, and here in SO a few times as well, the $route system is one of the situations where angular's very opinionated stance can become a hinderance: it's meant to be simple, but it isn´t very configurable. I personally don't use it directly, but rather bypass it via a solution I found here.

正如我在一些地方所说的那样,在这里也是如此,$ route系统是角度非常自以为是的立场可能成为障碍的情况之一:它意味着简单,但它不是很可配置的。我个人不直接使用它,而是通过我在这里找到的解决方案绕过它。

It complicates things a little, but it gives you greater flexibility - the idea being that the $route only serves to trigger a (manual) render function, where you can assign the values and trigger $broadcasts as you wish (normally, from the main controller).

它使事情变得复杂,但它给你更大的灵活性 - 这个想法是$ route只用于触发(手动)渲染功能,你可以根据需要分配值和触发$ broadcasts(通常,从主要控制器)。

One thing I haven't tried is a "hybrid" solution - that is, using the $route system normally, and configuring your /item/item_id route to NOT have a view parameter (so as to not change the main view, initiated by the other routes) and do something via the $routeChangeStart event (remember you can assign any value to the routes, as you can see by the page I referenced)

我没有尝试的一件事是“混合”解决方案 - 即正常使用$ route系统,并将/ item / item_id路由配置为NOT没有视图参数(以便不更改主视图,由其他路线)并通过$ routeChangeStart事件做一些事情(记住你可以为路线分配任何值,你可以通过我引用的页面看到)

Again, personally I prefer fully using the alternative method.

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

#5


2  

You might want to try to grab the AngularJS source code, tweak and load your own brewing of $route and $routeProvider, then inject them over the Angular natives.

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

You probably only need to add a flag to intercept the trigger of the route change.

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

Another simpler option would be to hook into the events system, using $scope.$on(event_name, function callback(ev) {...}) and by calling .preventDefault() on the passed-in event, hoping it will stop the internal $route trigger. The location system broadcasts the $locationChangeStart event and it does check the defaultPrevented attribute before yielding the $locationChangeSuccess event.

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

Don't forget to publish your results!

不要忘记发布您的结果!

#1


13  

You can now do this if you use the ui.router module as described here: https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-open-a-dialogmodal-at-a-certain-state.

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

Their example:

他们的例子:

$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");
            }
        });
    }
});

I also pass an 'errorCallback' function to the then() function to handle modal dismissal by pressing Escape or clicking on the background.

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

#2


7  

I had similar requirements. I needed:

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

  • Modal window to trigger a change to the route, so it has it's own hash url.
  • 用于触发路由更改的模态窗口,因此它具有自己的哈希URL。
  • When you visit the modal url, the main screen loads then triggers the modal to popop.
  • 当您访问模态URL时,主屏幕加载然后触发模式到popop。
  • The back button closes the modal (assuming you started on the home screen).
  • 后退按钮关闭模态(假设您在主屏幕上启动)。
  • When you click OK or Cancel the modal is closed and the route changes back to the home screen.
  • 单击“确定”或“取消”时,模式将关闭,路径将更改回主屏幕。
  • When you click the background to close the modal, the route changes back to the home screen.
  • 单击背景以关闭模式时,路径将更改回主屏幕。

I couldn't find a good example of all of this working together, but I was able to find examples of bits and pieces, so I put them all together into a working example as follows:

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

<!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>

You can view it in action at plunker: http://plnkr.co/edit/tLyfRP6sx9C9Vee7pOfC

你可以在plunker上查看它:http://plnkr.co/edit/tLyfRP6sx9C9Vee7pOfC

Click the "Open the embedded view" link in plunker to see the hash tag working.

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

#3


5  

hermanschutte's solution worked (see comment on Tiago Roldão's post). I'm posting step by step to help clarify.

hermanschutte的解决方案有效(见TiagoRoldão的帖子评论)。我正在逐步发布以帮助澄清。

First, in the config:

首先,在配置中:

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

Then, on opening modal (you'll need the location module):

然后,在打开模态(你需要位置模块):

$location.search('modal');

And in modal controller:

在模态控制器中:

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

#4


2  

The named view solution you mentioned has had great discussion on the angularJS mailing list, but as of now (and the near to mid-future) the $route system doesn't handle multiple views.

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

As I have said in a few places, and here in SO a few times as well, the $route system is one of the situations where angular's very opinionated stance can become a hinderance: it's meant to be simple, but it isn´t very configurable. I personally don't use it directly, but rather bypass it via a solution I found here.

正如我在一些地方所说的那样,在这里也是如此,$ route系统是角度非常自以为是的立场可能成为障碍的情况之一:它意味着简单,但它不是很可配置的。我个人不直接使用它,而是通过我在这里找到的解决方案绕过它。

It complicates things a little, but it gives you greater flexibility - the idea being that the $route only serves to trigger a (manual) render function, where you can assign the values and trigger $broadcasts as you wish (normally, from the main controller).

它使事情变得复杂,但它给你更大的灵活性 - 这个想法是$ route只用于触发(手动)渲染功能,你可以根据需要分配值和触发$ broadcasts(通常,从主要控制器)。

One thing I haven't tried is a "hybrid" solution - that is, using the $route system normally, and configuring your /item/item_id route to NOT have a view parameter (so as to not change the main view, initiated by the other routes) and do something via the $routeChangeStart event (remember you can assign any value to the routes, as you can see by the page I referenced)

我没有尝试的一件事是“混合”解决方案 - 即正常使用$ route系统,并将/ item / item_id路由配置为NOT没有视图参数(以便不更改主视图,由其他路线)并通过$ routeChangeStart事件做一些事情(记住你可以为路线分配任何值,你可以通过我引用的页面看到)

Again, personally I prefer fully using the alternative method.

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

#5


2  

You might want to try to grab the AngularJS source code, tweak and load your own brewing of $route and $routeProvider, then inject them over the Angular natives.

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

You probably only need to add a flag to intercept the trigger of the route change.

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

Another simpler option would be to hook into the events system, using $scope.$on(event_name, function callback(ev) {...}) and by calling .preventDefault() on the passed-in event, hoping it will stop the internal $route trigger. The location system broadcasts the $locationChangeStart event and it does check the defaultPrevented attribute before yielding the $locationChangeSuccess event.

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

Don't forget to publish your results!

不要忘记发布您的结果!