web-dev-qa-db-fra.com

Angular-ui.router: mise à jour de l'URL sans actualisation de la vue

J'ai un spa angulaire qui présente diverses listes de recommandations et une carte Google des lieux, basée sur différentes données de restaurant (voir m.amsterdamfoodie.nl ). Je veux que chacune de ces listes ait sa propre URL. Pour que Google puisse explorer les différentes listes, j'utilise les balises <a> pour la navigation offcanvas.

Actuellement, la balise <a> provoque un rafraîchissement de la vue, ce qui est très visible avec la carte.

  • Je peux empêcher cela en utilisant ng-click et $event.preventDefault() (voir les extraits de code ci-dessous), mais je dois alors mettre en œuvre un moyen de mettre à jour l'URL du navigateur.
  • Mais en essayant le $state d'Angular ou le history.pushstate du navigateur, je finis par déclencher des changements d'état et une actualisation de la vue ...!

Ma question est donc comment puis-je mettre à jour un modèle et l'URL sans actualiser la vue? (Voir aussi Angular/UI-Router - Comment puis-je mettre à jour l'URL sans tout rafraîchir? )

J'ai expérimenté beaucoup d'approches et j'ai actuellement ce code HTML

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

Dans le contrôleur:

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(...)

Et, je pense que ce qui se passe, c'est que je déclenche le code $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'

Un indice possible est celui avec le code ci-dessous si je charge par exemple. http://afmnew.herokuapp.com/criteria/cuisine/italian la vue s'actualise au fur et à mesure que vous naviguez, alors que si je charge http://afmnew.herokuapp.com/ il n'y a pas d'actualisation, mais pas d'URL à la place. Je ne comprends pas pourquoi cela se passe du tout.

23
Simon H

Sur la base de nos discussions précédentes, je veux vous donner une idée de la façon d'utiliser UI-Router ici. Je crois, je comprends bien votre défi ... Il y a un exemple de travail . Si ce n’est pas tout à fait la suite, prenez-le comme inspiration

CLAUSE DE NON-RESPONSABILITÉ: avec un plunker, je n'ai pas pu atteindre cet objectif: http://m.amsterdamfoodie.nl/ , mais le principe devrait être le suivant: exemple similaire}

Donc, il y a une définition d'état (nous n'avons que deux états)

  $stateProvider
    .state('main', {
        url: '/',
        views: {
          '@' : {
            templateUrl: 'tpl.layout.html',
            controller: 'MainCtrl',
          },
          '[email protected]' : { templateUrl: 'tpl.right.html',}, 
          '[email protected]' : {
            templateUrl: 'tpl.map.html',
            controller: 'MapCtrl',
          },
          '[email protected]' : {
            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',
          },
        },
      })
}];

Ce serait notre principal 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>

Comme nous pouvons le constater, l’état principal cible ces vues imbriquées de l’état principal: 'viewName @ main', par exemple. '[email protected]'

La sous-vue, main.criteria, est également injectée dans les vues layout

_ {Son URL commence par le signe ^ (url : '^/criteria/:criteria/:value'), ce qui permet d'avoir une barre oblique / pour la barre principale et non doublée pour l'enfant

Et il y a aussi des contrôleurs, ils sont ici un peu naïfs, mais ils devraient montrer que, sur le fond, il pourrait y avoir une charge de données réelle (basée sur des critères).

La chose la plus importante ici est que PARENT MainCtrl crée le $scope.Model = {}. Cette propriété sera (grâce à l'héritage) partagée entre le parent et les enfants. C'est pourquoi tout cela fonctionnera:

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];  
  }
})

Cela devrait vous donner une idée de la manière dont nous pouvons utiliser les fonctionnalités fournies par UI-Router:

Vérifiez l'extrait ci-dessus ici , dans l'exemple de travail

Prolonger: nouveau plongeur ici

Si nous ne voulons pas que la vue cartographique soit recréée, nous pouvons simplement omettre de définir la forme de l'état enfant def:

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

Maintenant, notre carte VIEW recevra seulement les modifications du modèle (peut être regardé) mais la vue et le contrôleur ne seront pas rendus.

AUSSI, il existe un autre lecteur http://plnkr.co/edit/y0GzHv?p=preview qui utilise la variable 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
      },
    },
  })

et qui pourrait être utilisé comme ceci:

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

Le dernier plunker est ici

11
Radim Köhler

Voici un exemple de chemin à suivre si je comprends bien:

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

De l'utilisateur l-liava-l dans le problème https://github.com/angular-ui/ui-router/issues/64

Vous pouvez vérifier l'API $state ici: http://angular-ui.github.io/ui-router/site/#/api/ui.router.state . $ State

16
John Bernardsson

vous pouvez utiliser l'héritage de la portée pour mettre à jour l'URL sans actualiser la vue

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

si la vue enfant est à l'intérieur de la vue parent, les deux contrôleurs partagent la même portée . Vous pouvez donc placer une vue virtuelle factice (ou nécessaire) dans la vue parent qui sera remplie par la vue enfant.

et insérer un 

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

fonction dans le contrôleur parent qui sera appelé par le contrôleur enfant lors du chargement du contrôleur. alors quand un utilisateur clique 

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

seuls le contrôleur enfant et la vue enfant seront actualisés. alors vous pouvez appeler la fonction de portée parent avec les paramètres nécessaires.

2
bahadir

La réponse courte a finalement été de faire pas de placer la carte dans une vue qui change. La réponse acceptée fournit beaucoup plus de détails sur la manière de structurer une page avec des sous-vues, mais le point clé n'est pas de faire de la carte une partie de la vue mais de connecter son comportement à une vue qui change et d'utiliser mettre à jour les icônes du marché.

0
Simon H