【问题标题】:Angular-ui.router: Update URL without view refreshAngular-ui.router:在不刷新视图的情况下更新 URL
【发布时间】:2014-12-14 07:11:48
【问题描述】:

我有一个 Angular SPA,它根据一些餐厅数据的不同部分(参见 m.amsterdamfoodie.nl)提供各种推荐列表和 Google 位置地图。我希望这些列表中的每一个都有自己的 URL。为了让 Google 抓取不同的列表,我使用<a> 标签进行画布导航。

目前<a>标签会导致视图刷新,这在地图上非常明显。

  • 我可以使用ng-click$event.preventDefault() 来防止这种情况发生(参见下面的代码sn-ps),但是我需要实现一种更新浏览器URL 的方法。
  • 但是在尝试 Angular 的 $state 或浏览器的 history.pushstate 时,我最终会触发状态更改和视图刷新...!

因此,我的问题是如何在不刷新视图的情况下更新模型和 URL?(另请参阅 Angular/UI-Router - How Can I Update The URL Without Refreshing Everything?

我尝试了很多方法,目前有这个html

<a href="criteria/price/1" class="btn btn-default" ng-click="main.action($event)">Budget</a>

在控制器中:

this.action = ($event) ->
    $event.preventDefault()
    params = $event.target.href.match(/criteria\/(.*)\/(.*)$/)

    # seems to cause a view refresh
    # history.pushState({}, "page 2", "criteria/"+params[1]+"/"+params[2]);

    # seems to cause a view refresh
    # $state.transitionTo 'criteria', {criteria:params[1], q:params[2]}, {inherit:false}

    updateModel(...)

而且,我认为正在发生的是我正在触发$stateProvider 代码:

angular.module 'afmnewApp'
.config ($stateProvider) ->
  $stateProvider
  .state 'main',
    url: '/'
    templateUrl: 'app/main/main.html'
    controller: 'MainCtrl'
    controllerAs: 'main'
  .state 'criteria',
    url: '/criteria/:criteria/:q'
    templateUrl: 'app/main/main.html'
    controller: 'MainCtrl'
    controllerAs: 'main'

一个可能的线索是,如果我加载下面的代码,例如http://afmnew.herokuapp.com/criteria/cuisine/italian 然后视图会在您导航时刷新,而如果我加载 http://afmnew.herokuapp.com/ 则不会刷新,而是不会更新 URL。我完全不明白为什么会这样。

【问题讨论】:

  • 我还没有深入研究过这篇文章,但你可能会觉得它很有趣。 weluse.de/blog/angularjs-seo-finally-a-piece-of-cake.html
  • 不幸的是,这是关于预渲染,而我的问题是关于以 SEO 友好的方式导航 SPA
  • 你见过 ui-sref 吗?状态配置中还有 reloadOnSearch 参数吗?
  • reloadOnSearch 看起来可能有帮助,但它是 ui.router 的一部分吗?

标签: angularjs seo angular-ui-router single-page-application


【解决方案1】:

如果我理解正确,这是一个示例:

$state.go('my.state', {id:data.id}, {notify:false, reload:false});
//And to remove the id from the url:
$state.go('my.state', {id:undefined}, {notify:false, reload:false});

来自用户 l-liava-l 在问题https://github.com/angular-ui/ui-router/issues/64

您可以在此处查看$state API:http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$state

【讨论】:

  • awesome thx John - 仅供参考,这在旧路由器上不起作用 - 在我升级到 angular-ui-router/0.2.18 后它就像一个魅力(在 0.2.13 上)
  • 没问题。这似乎有一些“次要”问题,您可以查看我在答案中指出的github问题。但如果我没记错的话,它应该在 UI-Router 的 1.x 版本中得到解决
  • 点击链接可以正常工作,但如果我在浏览器上前进/后退,它会重新加载视图
  • 是的,这是我在评论中提到的问题之一。我没有机会检查 1.0 alpha 版本(今天的最新版本)是否仍然发生这种情况……是这样吗?
【解决方案2】:

根据我们之前的讨论,我想在这里给你一些想法,如何使用UI-Router。我相信,我完全理解你的挑战......有a working example。如果这不完全适合,请将其作为一些灵感

免责声明:使用 plunker,我无法做到这一点:http://m.amsterdamfoodie.nl/,但原理应该与 example 类似

所以,有一个状态定义(我们只有两个状态)

  $stateProvider
    .state('main', {
        url: '/',
        views: {
          '@' : {
            templateUrl: 'tpl.layout.html',
            controller: 'MainCtrl',
          },
          'right@main' : { templateUrl: 'tpl.right.html',}, 
          'map@main' : {
            templateUrl: 'tpl.map.html',
            controller: 'MapCtrl',
          },
          'list@main' : {
            templateUrl: 'tpl.list.html',
            controller: 'ListCtrl',
          },
        },
      })
    .state('main.criteria', {
        url: '^/criteria/:criteria/:value',
        views: {
          'map' : {
            templateUrl: 'tpl.map.html',
            controller: 'MapCtrl',
          },
          'list' : {
            templateUrl: 'tpl.list.html',
            controller: 'ListCtrl',
          },
        },
      })
}];

这将是我们的主要tpl.layout.html

<div>

  <section class="main">

    <section class="map">
      <div ui-view="map"></div>
    </section>

    <section class="list">
      <div ui-view="list"></div>
    </section>

  </section>

  <section class="right">
    <div ui-view="right"></div>
  </section>

</div>

正如我们所见,主状态确实针对主状态的这些嵌套视图:'viewName@main',例如'right@main'

还有子视图,main.criteria 确实注入到 layout 视图中。

它的 url 以 ^ (url : '^/criteria/:criteria/:value') 符号开头,它允许使用 / 斜杠作为主斜杠,而不是双斜杠作为子斜杠

还有控制器,他们在这里有点幼稚,但他们应该表明,在后台可能是真实的数据加载(基于标准)。

这里最重要的是,PARENT MainCtrl 创建了 $scope.Model = {}。该属性将(由于继承)在父母和孩子之间共享。这就是为什么这一切都会奏效的原因:

app.controller('MainCtrl', function($scope)
{
  $scope.Model = {};
  $scope.Model.data = ['Rest1', 'Rest2', 'Rest3', 'Rest4', 'Rest5'];  
  $scope.Model.randOrd = function (){ return (Math.round(Math.random())-0.5); };
})
.controller('ListCtrl', function($scope, $stateParams)
{
  $scope.Model.list = []
  $scope.Model.data
    .sort( $scope.Model.randOrd )
    .forEach(function(i) {$scope.Model.list.push(i + " - " + $stateParams.value || "root")})
  $scope.Model.selected = $scope.Model.list[0];
  $scope.Model.select = function(index){
    $scope.Model.selected = $scope.Model.list[index];  
  }
})

这应该让我们了解如何使用 UI-Router 为我们提供的功能:

检查上面的提取here,在the working example

扩展:新插件here

如果我们不想重新创建地图视图,我们可以省略子状态定义的形式:

.state('main.criteria', {
    url: '^/criteria/:criteria/:value',
    views: {
      // 'map' : {
      //  templateUrl: 'tpl.map.html',
      //  controller: 'MapCtrl',
      //},
      'list' : {
        templateUrl: 'tpl.list.html',
        controller: 'ListCtrl',
      },
    },
  })

现在我们的地图 VIEW 将只接收模型中的更改(可以观察),但不会重新渲染视图和控制器

另外,还有另一个 plunker http://plnkr.co/edit/y0GzHv?p=preview 使用 controllerAs

.state('main', {
    url: '/',
    views: {
      '@' : {
        templateUrl: 'tpl.layout.html',
        controller: 'MainCtrl',
        controllerAs: 'main',        // here
      },
      ...
    },
  })
.state('main.criteria', {
    url: '^/criteria/:criteria/:value',
    views: {
      'list' : {
        templateUrl: 'tpl.list.html',
        controller: 'ListCtrl',
        controllerAs: 'list',      // here
      },
    },
  })

可以这样使用:

<h4>{{main.hello()}}</h4>
<h4>{{list.hello()}}</h4>

最后一个plunker是here

【讨论】:

  • 快速提问:在 MainCtrl 中创建模型是否至关重要 - 到目前为止我一直在使用共享工厂?
  • 西蒙,拜托,在我看来这并不严重。我想帮忙,但你给我链接到带有缩小 Javascript 的版本?那是行不通的。你应该看到我需要帮助。所以尝试调整,扩展我的 plunker 来重现这个问题。我也可以重新设计你的生活应用程序,但是......(这有点超过关于 SO 的问答)......无论如何,我想按照承诺给你一个提示。我创建了非常复杂的 plunker。如果你想让我做更多,请让我轻松一点。如果您不再需要我的帮助,我明白了……这有意义吗?
  • 好的,原谅上一个问题。我的主要兴趣不是刷新地图,而是让控制器在模型更改时删除并重新创建标记(由单击“右”面板引起)。我修改了您的 plnkr plnkr.co/edit/9atRT1?p=preview 并在每次加载 MapCtrl 时添加了一条 console.log 消息,这相当于视图重新加载(我认为),并且它一直在触发。这对我来说是一个问题,但这可能是因为我将模型保存在工厂中(我在上面的第一条评论)
  • 以后会来找你的……承诺
  • 我目前的想法是,我需要将地图本身放入主视图,但要让标记由子视图创建
【解决方案3】:

您可以使用范围继承来更新 url 而无需刷新视图

$stateProvider
            .state('itemList', {
                url: '/itemlist',
                templateUrl: 'Scripts/app/item/ItemListTemplate.html',
                controller: 'ItemListController as itemList'
                //abstract: true //abstract maybe?
            }).state('itemList.itemDetail', {
                url: '/:itemName/:itemID',
                templateUrl: 'Scripts/app/item/ItemDetailTemplate.html',
                controller: 'ItemDetailController as itemDetail',
                resolve: {
                    'CurrentItemID': ['$stateParams',function ($stateParams) {
                        return $stateParams['itemID'];
                    }]
                }
            })

如果子视图在父视图中,则两个控制器共享相同的范围。 因此您可以在父视图中放置一个虚拟(或必要的)ui-view,该视图将由子视图填充。

并插入一个

$scope.loadChildData = function(itemID){..blabla..};

父控制器中的函数,子控制器在控制器加载时将调用该函数。所以当用户点击

<a ui-sref="childState({itemID: 12})">bla</a> 

只有子控制器和子视图会被刷新。然后您可以使用必要的参数调用父作用域函数。

【讨论】:

  • 谢谢。事实上,我最终用控制器将地图包装在一个 div 中,但没有链接到视图。不使用范围继承,但在服务中拥有控制器当然可以访问的所有关键数据
【解决方案4】:

简短的回答最终是将地图放入一个变化的视图中。已接受的答案提供了有关如何使用子视图构建页面的更多详细信息,但关键不是使地图成为视图的一部分,而是将其行为连接到确实发生变化的视图并使用控制器来更新市场图标。

【讨论】:

    猜你喜欢
    • 2017-03-24
    • 1970-01-01
    • 1970-01-01
    • 2014-01-01
    • 1970-01-01
    • 2020-10-31
    • 2018-02-13
    • 2016-05-25
    • 1970-01-01
    相关资源
    最近更新 更多