Angular.js: Option sur $location pour permettre le changement de hachage/chemin sans recharger la route

Créé le 12 déc. 2012  ·  194Commentaires  ·  Source: angular/angular.js

Nous avons un paradigme dans notre application selon lequel les utilisateurs créent de nouvelles choses sur la même page qu'ils les voient. Notre itinéraire ressemble donc à /thing/:id. Lors de la création d'une nouvelle chose, ils vont à /thing/new. Une fois que la chose a été enregistrée avec succès, nous voulons changer la route vers /thing/1234 (ou quel que soit son nouvel identifiant). Maintenant, le partiel n'a pas besoin d'être rechargé car les données sont toutes les mêmes. Nous voulons juste que le chemin soit mis à jour afin qu'un utilisateur puisse maintenant mettre en signet le lien correct, etc.

Avoir une option sur $location (pas sur la définition de route) pour activer/désactiver le chargement de route fonctionnerait, mais je suis sûr qu'il existe d'autres moyens d'implémenter la fonctionnalité.

Lots of comments $location ngRoute high confusing feature

Commentaire le plus utile

La solution consiste essentiellement à ajouter un paramètre supplémentaire à $location.path().

app.run(['$route', '$rootScope', '$location', function ($route, $rootScope, $location) {
    var original = $location.path;
    $location.path = function (path, reload) {
        if (reload === false) {
            var lastRoute = $route.current;
            var un = $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = lastRoute;
                un();
            });
        }
        return original.apply($location, [path]);
    };
}]);

Et puis utiliser comme ça

$location.path('/path/that/you/need', false);

Tous les 194 commentaires

+1, j'ai le même problème.

@cgross Étant donné que le service $location utilise un chaînage de méthodes de style JQuery et que toutes les commandes sont de toute façon différées jusqu'à $digest, je suggère d'ajouter une nouvelle méthode de chaînage, par exemple reload(boolean):

$location.path("/thing/"+thing.id).replace().reload(false)

PS Est-ce le Chris Gross qui a lancé le projet Eclipse Nebula ?

Ouais :)

+1 :+1:

+1

À l'heure actuelle, nous utilisons un hack sale pour contourner ce problème.

    $scope.$on('$locationChangeSuccess', function(event) {

        // Want to prevent re-loading when going from /dataEntry/1 to some other dataEntry path
        if ($route && $route.current && $route.current.$route.templateUrl.indexOf('dataEntry') > 0) {
            $route.current = lastRoute; //Does the actual prevention of routing
        }
});

+1

+1

+1

+1

+1

Voici ma tentative pour résoudre ce problème en prenant en charge preventDefault() pour $routeChangeStart . Qu'est ce que tu penses de ça?
S'engager : https://github.com/redhotsly/angular.js/commit/f8ac46e6ac9d76cf855077d21b68b4c2c8043db1

Je travaille sur l'approche @qualidafial depuis quelques jours et maintenant le PR est prêt. J'ai créé une nouvelle méthode notify qui permet d'ignorer la notification de changement d'emplacement pour une fois. Cela devrait éviter de déclencher un changement de route ou un rechargement.

Cela ne touche pas le système de routage, vous n'avez donc pas à modifier vos définitions de route. Cela fonctionnera toujours même si vous utilisez ui-router au lieu du système de routage AngularJS standard.

Exemple : $location.path('/client/2').replace().notify(false);

J'ai également travaillé quelques tests et bits de documentation.

+1

un autre +1
Utilisation déjà de la pull request

+1

+1

Malheureusement, PR https://github.com/angular/angular.js/pull/2398 a été rejeté par l'équipe Angular car le fait de sauter des notifications pourrait entraîner des incohérences entre l'URL réelle et l'itinéraire actuel. Il y a une longue explication à la fin de la discussion.

Je n'ai besoin de cette fonctionnalité dans aucun de mes projets, mais je la garderai dans mon référentiel afin qu'elle puisse être fusionnée si vous le souhaitez. Faites-moi savoir s'il se désynchronise avec la branche principale afin que je puisse rebaser les modifications. Merci!

+1

+1, besoin de ça :(

+1

Edit : meilleure approche ici : https://github.com/angular/angular.js/issues/1699#issuecomment -22511464

J'ai créé une usine réutilisable pour contourner cela (basé sur l'idée de @shahmirn):

/**
 * HACK Do not reload the current template if it is not needed.
 *
 * See AngularJS: Change hash and route without completely reloading controller http://stackoverflow.com/questions/12115259/angularjs-change-hash-and-route-without-completely-reloading-controller
 */
app.factory('DoNotReloadCurrentTemplate', ['$route', function($route) {
  return function(scope) {
    var lastRoute = $route.current;
    scope.$on('$locationChangeSuccess', function() {
      if (lastRoute.$$route.templateUrl === $route.current.$$route.templateUrl) {
        console.log('DoNotReloadCurrentTemplate not reloading template: ' + $route.current.$$route.templateUrl);
        $route.current = lastRoute;
      }
    });
  };
}]);

Comment utiliser:

app.controller('MyCtrl',
  ['$scope', 'DoNotReloadCurrentTemplate',
  function($scope, DoNotReloadCurrentTemplate) {

  DoNotReloadCurrentTemplate($scope);
}]);

Source : http://stackoverflow.com/a/16496112/990356

Si vous définissez "reloadOnSearch" sur false sur la route, cela semble corriger les modifications de hachage en rechargeant toute la page.

+1

Ce serait formidable pour les applications mobiles, où le cycle de vie de création/destruction de ng-view n'est pas approprié (tue les performances et la convivialité).

@tkrotoff Merci pour l'exemple. Étant donné que votre solution de contournement annule les notifications d'événements, j'ai ajouté une diffusion sur le rootscope afin que les autres contrôleurs puissent toujours être informés du changement de route :

/**
 * HACK Do not reload the current template if it is not needed.
 *
 * See AngularJS: Change hash and route without completely reloading controller http://stackoverflow.com/questions/12115259/angularjs-change-hash-and-route-without-completely-reloading-controller
 */
app.factory('DoNotReloadCurrentTemplate', ['$route', '$rootScope', function($route, $rootScope) {
  return function(scope) {
    var lastRoute = $route.current;
    scope.$on('$locationChangeSuccess', function() {
      if (lastRoute.$$route.templateUrl === $route.current.$$route.templateUrl) {
        console.log('DoNotReloadCurrentTemplate not reloading template: ' + $route.current.$$route.templateUrl);
        $rootScope.$broadcast('locationChangedWithoutReload', $route.current);
        $route.current = lastRoute;        
      }
    });
  };
}]);

Puis:

app.controller('MyCtrl',
  ['$scope', 'DoNotReloadCurrentTemplate',
  function($scope, DoNotReloadCurrentTemplate) {

  DoNotReloadCurrentTemplate($scope);

  $rootScope.$on('locationChangedWithoutReload', function(event, route) {
    // set location specific breadcrumbs
    $scope.breadcrumbs = assembleBreadCrumbs();

    // do something for specific route
    if (route.$$route.action == 'item') {
      $scope.item = $scope.items[params.itemId];
      $rootScope.title = $scope.item.name;
    }
  });
}]);

J'ai le même problème que l'auteur du sujet et j'ai créé une version modifiée de la solution décrite ci-dessus.

app.factory('skipReload', [
    '$route',
    '$rootScope',
    function ($route, $rootScope) {
        return function () {
            var lastRoute = $route.current;
            var un = $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = lastRoute;
                un();
            });
        };
    }
]);

Usage:

app.controller('ThingCtrl', ['$scope', '$http', '$location', 'skipReload', function ($scope, $http, $location, skipReload) {
    ...
    $scope.submit = function () {
        ...
        $http.post('thing', thing).success(function (id) {
            skipReload();
            $location.path('/thing/' + id).replace();
        });
    };
}]);

L'utilisation d'un écouteur d'événements permanent qui teste templateUrl pour décider s'il faut autoriser le rechargement de la route, pourrait interrompre le routage vers d'autres vues qui utilisent la même templateUrl. Par exemple, j'ai routé vers /thing/new et la vue affiche le lien vers le dernier élément ajouté /thing/5, je ne pourrai pas accéder à l'objet 5 et à tout autre élément (même en changeant explicitement l'URL dans le navigateur). Un autre problème avec les tests de templateUrl est que les modèles peuvent être spécifiés en utilisant la propriété de modèle (sous forme de chaîne ou de fonction).

Ce hack permet de sauter exactement un rechargement de route et de ne pas interrompre le routage.

J'ai également essayé une solution moins détaillée (pour une utilisation dans le contrôleur):

app.factory('location', [
    '$location',
    '$route',
    '$rootScope',
    function ($location, $route, $rootScope) {
        $location.skipReload = function () {
            var lastRoute = $route.current;
            var un = $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = lastRoute;
                un();
            });
            return $location;
        };
        return $location;
    }
]);
app.controller('ThingCtrl', ['$scope', '$http', 'location', function ($scope, $http, location) {
    ...
    $scope.submit = function () {
        ...
        $http.post('thing', thing).success(function (id) {
            location.skipReload().path('/thing/' + id).replace();
        });
    };
}]);

Semble bien fonctionner.

+1

@sergey-buturlakin votre $location.skipReload est tout simplement merveilleux ! Je l'utilise à la place de DoNotReloadCurrentTemplate

@sergey-buturlakin Votre solution semble fonctionner pour moi. Merci!

après avoir exécuté skipReload() partir de https://github.com/angular/angular.js/issues/1699#issuecomment -22511464, le rechargement est désactivé
il semble que un() devrait être appelé plus tard

Il empêche tout rechargement suivant et doit être appelé juste avant le changement de chemin.
Une fois le changement de chemin terminé, "un" sera appelé.

oui, mais je voulais sauter le rechargement une seule fois, cependant l'approche proposée empêche tout rechargement supplémentaire

+1

+1

+1

PR https://github.com/angular/angular.js/pull/2398 ne saute qu'une seule fois le rechargement. Je ne sais pas s'il y a des conflits de fusion avec le maître actuel. Si c'est le cas, envoyez-moi un message et je le corrigerai.

J'utilise la solution de sergey, en laissant tomber la variable inutile (selon moi) "un" et call.

Cela mène à:

  angular.module('services.locationChanger', [])
    .service('locationChanger', ['$location', '$route', '$rootScope', function ($location, $route, $rootScope) {

        this.skipReload = function () {
            var lastRoute = $route.current;
            $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = lastRoute;
            });
            return this;
        };

        this.withoutRefresh = function (url, doesReplace) {
            if(doesReplace){
                $location.path(url).replace();
            }
            else {
                $location.path(url || '/');
            }
        };
    }]);

et tout appelant :

 locationChanger.skipReload().withoutRefresh("/", true);

+1

+1

+1

+1

@mica16 La un n'est-elle pas utilisée pour dissocier l'événement ?

J'ai créé deux plunkrs pour les tests, avec et sans la variable un pour délier les événements et il semble que sans cela, l'événement continue de se déclencher encore et encore

Oui en effet, la variable un semble bizarre.
Selon l'auteur original, cela serait considéré comme utile..mais je ne suis pas convaincu que ce soit la solution parfaite.

J'ai préféré le réécrire, mais j'ai saccagé la fonctionnalité car je ne m'y attendais plus.

Alors allez-y si votre test unitaire peut garantir qu'il fonctionne comme vous l'attendez :)

vous avez raison les gars
Ma solution était

app.factory 'location', ['$location', '$route', '$rootScope', '$timeout', ($location, $route, $rootScope, $timeout) ->
  $location.skipReload = ->
    lastRoute = $route.current
    un = $rootScope.$on('$locationChangeSuccess', ->
      $route.current = lastRoute
    )
    $timeout((->
      un()
    ), 1000)
    $location

  $location
]

puis en controleur

location.skipReload().path('/new/path').replace()

+1

+1

+1

+1

une autre façon de le faire est de le mettre hors de l'"étape" javascript actuelle afin que la route ne soit pas au courant de la modification apportée à la fenêtre.emplacement :

setTimeout(function() {
    window.location.hash = "";
    if (typeof (history.pushState) !== "undefined") {
        history.pushState("", document.title, location.pathname);
    }
}, 0);

+1

+1

+1

+1

Qu'en est-il de l'utilisation de $anchorScroll ? J'ai fouiné sur le net et j'ai découvert qu'il existe des outils pour le faire. Cela fonctionne pour moi au moins.

Vous devriez le vérifier : http://docs.angularjs.org/api/ng. $anchorScroll

+1

Est-ce une mauvaise idée? J'avoue que je ne l'ai pas testé en profondeur. Je n'aimais pas l'idée d'appeler une méthode distincte avant de définir le chemin. Du point de vue de l'utilisation, au moins, cela semble plus propre.

J'ai étendu la fonction $location.path intégrée, en utilisant la technique de Sergey.

App.run(['$route', '$rootScope', '$location', function ($route, $rootScope, $location) {
        var original = $location.path;
        $location.path = function (path, reload) {
            if (reload === false) {
                var lastRoute = $route.current;
                var un = $rootScope.$on('$locationChangeSuccess', function () {
                    $route.current = lastRoute;
                    un();
                });
            }

            return original.apply($location, [path]);
        };
    }])

Usage:

$location.path('/my/path', false);

+1

Les solutions

+1

Le correctif de @EvanWinstanley/sergey a l'air bien et il ne recharge pas le contrôleur, mais lors de la modification de l'itinéraire, les résolutions sont à nouveau demandées.

Mon cas d'utilisation principal pour cela serait de changer manuellement le $location.path sans recharger les résolutions/contrôleur afin que je puisse faire la mise en cache.

Par exemple : j'ai une sélection qui permet à un utilisateur de choisir entre plusieurs applications, ce qui modifie également le $location.path pour des URL plus agréables. Lorsqu'un utilisateur sélectionne une application, les résultats de l'appel d'API sont mis en cache. Même avec le correctif, il refait toujours la résolution, ce qui est inutile car les résultats sont déjà mis en cache.

+1

La solution @sergey-buturlakin fonctionne bien pour moi, elle ne recharge pas le contrôleur mais recharge toujours la fonction reslove et envoie la demande au serveur.

+1

La solution de @EvanWinstanley ne fonctionne pas pour moi. D'un autre côté, la solution de @mica16 fonctionnait trop bien... (le bouton retour ne rafraîchissait pas la page). J'ai essayé d'écrire ma propre version sans succès :(.

+1

+1

+1

+1
Veuillez prendre en charge : $location.reload();

@jvmvik as-tu essayé $route.current.reload() ?

7.1.1

pas de nouvelles depuis plus d'un an ?

+1

+1

J'ai fait un peu d'amélioration sur la solution de Sergey.
Désormais, il prend en charge, par exemple, les URL personnalisées pour les onglets (onglets de navigation) sans recharger la page entière.

// Original: https://github.com/angular/angular.js/issues/1699#issuecomment-22511464
//
// Usage:
//
// (interception is needed for Back/Forward buttons to work)
//
// location.intercept($scope._url_pattern, function(matched) {
//   // can return false to abort interception
//   var type = matched[1]
//   if (!type) {
//     return;
//   }
//   $scope.safeApply(function() {
//     $scope.data_type = type; 
//     $scope.params.page = 1; 
//     $scope.get_data(); 
//   });
// });
// 
// anywhere in your controller: 
// location.skipReload().path(url);
//
// to replace in history stack:
// location.skipReload().path(url).replace();

app.factory('location', [
    '$location',
    '$route',
    '$rootScope',
    function ($location, $route, $rootScope) {
        var page_route = $route.current;

        $location.skipReload = function () {
            //var lastRoute = $route.current;
            var unbind = $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = page_route;
                unbind();
            });
            return $location;
        };

        if ($location.intercept) {
            throw '$location.intercept is already defined';
        }

        $location.intercept = function(url_pattern, load_url) {

            function parse_path() {
                var match = $location.path().match(url_pattern)
                if (match) {
                    match.shift();
                    return match;
                }
            }

            var unbind = $rootScope.$on("$locationChangeSuccess", function() {
                var matched = parse_path();
                if (!matched || load_url(matched) === false) {
                  return unbind();
                }
                $route.current = page_route;
            });
        };

        return $location;
    }
]);

+1

+1

Une autre solution de contournement pour empêcher le rechargement de la route lors de la modification du chemin $location consiste à définir le pathParams de la route actuelle pour qu'il corresponde au nouveau chemin de localisation.

Dans ma configuration $routeProvider, j'ai une route vers /tasks comme suit

when('/tasks/:section/:taskId?', {
      templateUrl: 'partials/tasks.html',
      controller: 'TaskCtrl',
      reloadOnSearch: false
      })

L'itinéraire ci-dessus a deux sections pathParams et taskId (taskId est facultatif) et reloadOnSearch est défini sur false.

Pour naviguer de /tasks/inbox à /tasks/inbox/122 sans recharger l'itinéraire, définissez le taskId pathParam avant de modifier le chemin $location comme suit

$scope.showTask = function(task) {
    // set taskId to prevent route reloading
    $route.current.pathParams ['taskId'] = task.id;
    $location.path('/tasks/'+$route.current.pathParams ['section']+'/'+task.id);
};

Notez que la section pathParam n'a pas besoin d'être définie car elle a déjà été définie par la route.

Lorsque la route découvre que les pathParams extraits du nouveau chemin $location correspondent aux pathParams de la route actuelle (et que reloadOnSearch est défini sur false), elle ne rechargera pas la route à la place, un événement $routeUpdate sera déclenché.

Pour revenir de /tasks/inbox/122 à /tasks/inbox sans recharger l'itinéraire, supprimez le taskId pathParam avant de modifier le chemin $location comme suit

$scope.back = function () {
    // delete taskId to prevent route reloading
    delete $route.current.pathParams ['taskId'];
    $location.path('/tasks/'+$route.current.pathParams ['section']);
};

+1

+:100 :

Avez-vous lu l'article de Derick Bailey sur le routeur Backbone http://lostechies.com/derickbailey/2011/08/28/dont-execute-a-backbone-js-route-handler-from-your-code/ ? Backbone fournit cette option et selon Derick, vous devez toujours éviter de déclencher un rechargement (cela semble le contraire de ce que le routeur d'angular semble faire ici).
Il y a 48 "+1" sur ce post... Je ne pense pas que tous ces gens fassent quelque chose de mal. Devoir stocker var lastRoute = $route.current; et le restaurer plus tard sur $locationChangeSuccess avec $route.current = lastRoute; semble assez compliqué.
@IgorMinar avez-vous des idées ?

OK, je suis propriétaire de ce sujet pour la semaine prochaine afin que nous puissions enfin le clore.

Tout d'abord, je ne pense pas que la route doive se recharger lorsque la valeur de #hash change.

website.com/#/my-route#top

website.com/#/my-route#bottom

Ainsi, lorsque le haut passe au bas, il ne devrait pas se recharger, mais si la valeur de my-route change, cela devrait le faire. Nous pouvons tous être d'accord sur ce droit?

Deuxièmement, ce qui est plus important, nous avons besoin d'un moyen simple de signaler que le chemin de la route ne va pas se recharger lorsqu'il est modifié, mais cela devient problématique si un fragment antérieur de la route change. Alors pourquoi n'avons-nous pas une notation différente pour un paramètre de route en utilisant deux deux-points :: pour souligner qu'il agit comme une valeur générique.

// this will reload the page when the id changes like normal
$routeProvider.register('/users/:id')

// this will reload the page when the id changes, but not the section (since two colons are used)
$routeProvider.register('/users/:id/::section')

Qu'en pensez-vous?

Cela n'aide pas vraiment dans la situation d'où vous souhaitez passer :

/users/new

à

/users/123

une fois le nouvel utilisateur créé et attribué un identifiant, sans recharger l'utilisateur, puisqu'il est déjà là.

Parce que d'une manière ou d'une autre, il faut mapper le nouvel objet utilisateur à l'objet utilisateur actuel avec l'ID 123. C'est plutôt spécifique à l'application non?

Pour ce que cela vaut, Ember Router ne prend pas en charge le changement d'URL sans transition non plus :

http://stackoverflow.com/questions/19076834/change-url-without-transition

@matsko Pouvons-nous simplement responsabiliser l'ingénieur et le laisser décider si l'itinéraire doit déclencher un changement ou non ?
Je suis d'accord avec de bons paramètres par défaut.
Créez un moyen pour que l'ingénieur puisse modifier l'URL sans exécuter toute la chaîne d'événements de rechargement. Personnellement, je suis d'accord pour ajouter un paramètre supplémentaire facultatif avec des options (ex: {reload: false}) sur ".path()", ".search()", etc.

@ChrisCinelli oui, mais que se passe-t-il lorsqu'il y a deux paramètres dans une définition de route ? Comment pouvez-vous empêcher l'un de recharger tout en permettant à l'autre de le faire ? Le drapeau serait une fonction tout ou rien.

@matsko Donc, du point de vue de l'implémentation, est-il envisagé que nous serions en mesure de sélectionner les paramètres qui provoquent les rechargements et ceux qui ne le font pas ? Et si oui, avez-vous déjà une approche en tête ou est-ce là qu'on se heurte à un hic ?

@cgross cherry-pick oui (en utilisant le double deux-points). Ce n'est qu'une idée pour l'instant donc rien de concret. Si cela est conforme aux plans pour angulaire 2.0, nous pourrons peut-être l'utiliser ou essayer d'utiliser quelque chose que 2.0 prévoit d'avoir ou quelque chose que Dart a déjà (pour garder l'API cohérente).

Je pense que ce serait parfait et aurait l'air bien (au moins l'OMI) dans le cadre de la route.

$routeProvider,when("/:categoryName/:id[reload=no]", { ... })

Et puis, dans le contrôleur

$scope.$on("$routeChangeSuccess", function () {
  if ($routeParams.id !== $scope.activeId) {
    // React to ID update without reload
  }
});

Oui quelque chose comme ça, mais le double deux-points '::id' serait utilisé à la place de :id[reload=no] .

Gardez à l'esprit que ce n'est qu'une idée, mais beaucoup de gens ici veulent cette fonctionnalité.

Je pensais peut-être à ajouter éventuellement d'autres options, mais je suppose qu'il n'y a pas vraiment d'autres options qu'il pourrait y avoir. C'est bien aussi de voir que cette demande est prise au sérieux. Les jolies URL sont un gros problème pour beaucoup d'entre nous.

Matsko +1 pour les doubles deux points, mais je pense qu'il serait également utile de pouvoir passer des options à path() également pour certains cas extrêmes mentionnés ci-dessus

+1 pour les deux points

@matsko m'a demandé de me gérons cela dans le routeur d'Angular 2.0. Je sais que cela n'aide pas ceux d'entre vous qui en ont besoin pour ng1.x, mais j'ai pensé que cela pourrait vous intéresser.

Le nouveau routeur a une méthode navigate laquelle vous pouvez passer un _url_ et un objet _options_ facultatif. Ceci est basé sur les capacités d'historique du routeur Backbone, il prend donc en charge deux options replace et trigger Si vous voulez passer de /users/new à /users/123 sans provoquant une navigation à l'écran, vous spécifiez simplement trigger:false . Si vous souhaitez également qu'il remplace l'entrée d'historique /users/new par la nouvelle URL basée sur l'identifiant, vous devez également spécifier replace:true .

La mise à jour des routes d'une _nouvelle_ route vers une route _basée sur l'identifiant_ sans provoquer de transformations DOM, etc. est un cas d'utilisation clé pour cette API. Un autre exemple courant est la pagination. Par exemple, si vous avez une grille et que vous souhaitez que l'utilisateur parcoure les données, vous pouvez l'utiliser pour mettre à jour le fragment avec les informations de la page actuelle sans provoquer de navigation. Je suis sûr qu'il y a d'autres utilisations, mais ce sont celles que j'ai vues à plusieurs reprises dans mes consultations au fil des ans.

Espérons que cette information aide. Peut-être que le routeur 1.x peut adopter cette technique ?

@EisenbergEffect donc à confirmer. Pour 1.x, la méthode pour le faire sans changer le chemin ressemblerait à ceci :

$location.path(value, { trigger : false })

Et cela remplacerait efficacement le chemin sans provoquer la prise de conscience du nouveau contrôleur route +. Cela pourrait fonctionner pour le moment, mais que pensez-vous de l'idée du double côlon :: ?

Pour tous les autres ici, préféreriez-vous que le changement d'URL (sans recharger la route) soit facilité par la route ou par le développeur lorsque l'URL est modifiée ?

Il devrait y avoir une source centrale de vérité pour la localisation, comme un service qui
les routes et les navigateurs peuvent à la fois se référer et influencer mais restent le seul
vérité
Le 1er juillet 2014 à 23h28, "Matias Niemelä" [email protected] a écrit :

@EisenbergEffect https://github.com/EisenbergEffect donc à confirmer. Pour
1.x, la méthode pour le faire sans changer le chemin ressemblerait à ceci :

$location.path(value, { trigger : false })

Et cela remplacerait effectivement le chemin sans provoquer la nouvelle route

  • contrôleur de prendre conscience. Cela pourrait fonctionner pour l'instant, mais comment vous sentez-vous
    à propos du double :: idée du côlon?

Pour tous les autres ici, préféreriez-vous que l'URL change (sans
rechargement de la route) être facilitée par la route ou par le développeur
quand l'URL est modifiée ?

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/angular/angular.js/issues/1699#issuecomment-47741350 .

Voici une autre idée... Autorisez deux nouvelles méthodes sur chaque définition de route, disons entering(transitionInfo) et leaving(transitionInfo) ou quelque chose du genre. S'ils existent, la méthode leaving sera appelée sur l'ancienne route avant de partir, puis la méthode entering() sera appelée sur la nouvelle route. Le transitionInfo serait un objet qui ressemblerait à ce qui suit :

{
  newUrl: ...
  newRoute: ...
  oldUrl: ...
  oldRoute: ...
  cancelRouteChange: false,
  ignoreRouteChange: false
}

Dans cette méthode, nous pouvons vérifier les anciennes et les nouvelles routes/urls, puis si vous souhaitez que l'URL change mais ne réexécute pas la transition de route, vous définissez ignoreRouteChange sur true . Si vous souhaitez annuler le changement d'itinéraire et revenir à l'itinéraire précédent, définissez cancelRouteChange sur true.

Cela donne ensuite au développeur un contrôle total sur le moment où les transitions de route se produisent, fonctionnera si l'URL est modifiée directement dans la barre d'adresse et localise également la fonctionnalité pour décider d'un changement de route particulier proche de la définition des routes individuelles qui se soucient du changement .

Je crois qu'il existe quelque chose de similaire dans le routeur AngularV2 appelé activate() . @EisenbergEffect pouvez-vous confirmer ?

Qu'en penses-tu?

@matsko Oui, cette API pourrait ressembler à ça. Vous pouvez également envisager d'implémenter l'option replace , car elle est également fréquemment nécessaire. Dans ce cas particulier de /users/new à /users/123 je pense que vous tireriez parti des deux options puisque vous ne voulez pas réellement que /users/new reste dans la pile arrière après que le nouvel utilisateur est établi.

En ce qui concerne le :: , je ne suis pas sûr que ce soit entièrement utile. La raison en est que, dans l'exemple d'utilisateurs ci-dessus, ce n'est pas le modèle d'URL qui compte, mais le contexte d'application spécifique. Par exemple, si vous passez de customers à users/123 alors vous voulez notifier. De plus, si vous abandonnez users/new et passez à users/456 vous souhaitez également notifier. Ce n'est que lorsque vous êtes à users/new et que vous avez finalisé la création du nouvel utilisateur que vous souhaitez simplement mettre à jour le fragment pour représenter l'identifiant de l'utilisateur nouvellement créé sans provoquer de transition. Cela dépend fortement de ce que l'utilisateur fait réellement dans le contexte de cet écran d'utilisateur à ce moment-là.

@petebacondarwin Un problème avec cette idée est que la connaissance de l'allocation de la notification d'itinéraire n'est pas quelque chose qui est globalement disponible avec les définitions d'itinéraire. Il est plutôt intégré dans le contrôleur utilisateur particulier, car ce contrôleur est le morceau de code qui sait si un nouvel utilisateur est réellement créé à ce moment plutôt que de simplement naviguer du nouvel écran utilisateur vers un utilisateur préexistant.

Pour moi, la voie à suivre est de donner le contrôle à l'appelant qui souhaite modifier un itinéraire. Je suis donc définitivement en faveur de deux options replace et trigger .

@EisenbergEffect - vous avez raison. En général, cette information n'est pas disponible dans l'immédiat, bien que j'imagine la mettre en œuvre de la manière suivante :

Avoir un service appelé currentUser , qui contient l'utilisateur avec lequel nous travaillons actuellement. J'aime faire cela en général car je ne suis pas à l'aise avec la surcharge de mes contrôleurs avec du code. Si cela vous semble trop spécifique, vous pourriez avoir currentEntity , qui est la "chose" actuelle que vous modifiez/visualisez.

Ensuite, la méthode de route enter() pourrait l'injecter et décider si la navigation vers cette route changerait réellement l'entité actuelle. Sinon, nous ne prenons pas la peine d'exécuter la transition, mettez simplement à jour l'URL à la place.

J'apprécie que cela puisse devenir un peu lourd dans une grosse application, mais j'ai pensé que je le proposerais en option.

Quels sont les arguments contre le fait de donner à l'appelant (c'est-à-dire à celui qui demande le changement de route/URL) le contrôle pour savoir si la transition de route se produit réellement ? Je me souviens que @IgorMinar craignait que cela brise la contrainte selon laquelle un état de l'application soit directement mappé sur une URL et que le fait de permettre à l'URL de changer sans réexécuter la route mettrait potentiellement l'application dans un état différent selon le façon dont vous êtes arrivé là-bas.

@petebacondarwin Cela pourrait fonctionner pour ce scénario spécifique, mais il devient un peu plus difficile de gérer l'exemple de pagination que j'ai mentionné ci-dessus, qui voudrait ne pas déclencher et ne pas remplacer, tandis que le nouveau scénario utilisateur veut ne pas déclencher mais remplacer.

Aborder la question de l'État est un peu plus compliqué. La raison en est que nous avons deux URL qui représentent le même état de contrôleur... mais la différence réside dans le modèle. Donc, nous avons en réalité deux états différents... mais d'un point de vue UX, nous ne voulons pas secouer l'utilisateur lors de la transition. Du point de vue des performances, nous ne voulons pas jeter inutilement le DOM et tout recréer. Ainsi, dans le cas du nouvel utilisateur => transition utilisateur enregistrée, nous voulons vraiment éviter de déclencher et également remplacer le fragment afin que le bouton de retour ne vous ramène pas à l'écran du nouvel utilisateur. En interne, le routeur ou le mécanisme de localisation aurait juste besoin de suivre quel est le fragment actuel. (Je fais une hypothèse là-bas car je ne sais pas comment ng1.x fonctionne à cet égard. Avec 2.x, nous nous assurons de suivre attentivement le fragment actuel/précédent, quelle que soit la façon dont il a été modifié.) Dans le scénario de pagination, nous ne voulons pas déclencher parce que nous voulons une belle UX pour la pagination des données et ne voulons pas d'élimination inutile du DOM. Mais nous VOULONS que le fragment précédent soit conservé afin que l'utilisateur puisse utiliser le bouton Précédent pour revenir en arrière dans les pages de données.

Notez que dans ces deux cas, il ne s'agit pas seulement de l'URL et de l'état de l'application, mais également de la manière dont nous passons de l'état actuel à ce nouvel état. Rien de tout cela n'a d'importance si vous créez un lien profond directement vers la nouvelle vue. Peu importe quand vous passez de l'un à l'autre dans le cadre de ces cas très spécifiques.

+1

+1

+1

+1
Et aussi fonction pour changer l'URL sans changer de routage

+1 - ça fait plaisir de voir que je ne suis pas le seul à vouloir ça !

+1, pas encore ajouté(

+1

+1

+1

+1

+1

+1

+1

:+1:

Toi aussi @basarat ? :le sourire:

@johnnyreilly a juste un service où vous injectez $route et $location et vous pouvez avoir cette fonction :

/** navigate */
    public navigate(url: string, reload = true) {
        this.$location.url(url);

        // Don't do a controller reload on this navigation. Note: *you* need to be careful not to actually *need* a controller reload
        if (!reload) {
            var lastRoute = this.$route.current;
            var unsub = this.$rootScope.$on('$locationChangeSuccess', () => {
                this.$route.current = lastRoute; // fake this to make the route logic think nothing changed
                unsub();
            });
        }
    }

Bien @basarat !

+1

+1

Une chose que je remarque avec les solutions actuelles est que mon bloc de résolution de routeur est toujours en cours d'exécution. Quelqu'un d'autre a remarqué ça ? Il était assez facile de passer quelque chose dans l'objet $route.current, mais cela semble bidon. De plus, il semble que ce problème ne sera jamais résolu? :cri:

+1

+1

Pour ceux d'entre vous qui ne veulent pas avoir à revenir en arrière... pour autant que je sache, c'est ce que nous avons jusqu'à présent (désolé coffeescript). Malheureusement, cela ne fonctionne pas à 100% pour moi. Je suis toujours en train d'exécuter mon resolve et les boutons arrière/avant semblent cassés maintenant.

#
# Special $location.path function that allows you to change the url, but not
# send things through the router.
# <strong i="7">@see</strong> https://github.com/angular/angular.js/issues/1699
#
@app.run(['$rootScope', '$location', '$route', '$timeout', ($rootScope, $location, $route, $timeout) ->

    originalPath = $location.path

    $location.path = (url, reload=true) ->
        if ! reload
            lastRoute = $route.current
            if lastRoute
                unsub = $rootScope.$on('$locationChangeSuccess', ->
                    $route.current = lastRoute # fake this to make the route logic think nothing changed
                    $route.current.ignore = true
                    unsub()
                )
                # make sure to clean up and unregister the unsub function
                $timeout( ->
                    unsub()
                , 500)

        originalPath.apply($location, [url])

])

@lookfirst, vous appelez unsub deux fois. Préférez : https://github.com/angular/angular.js/issues/1699#issuecomment -54931571

@basarat haussement d'épaules... J'ai ajouté cela parce que je trouvais que unsub() n'était pas toujours appelé dans votre exemple préféré. Le mieux serait peut-être d'ajouter un garde dans la fonction unsub.

unsubbed = false
unsub = ... ->
    if ! unsubbed
        ...
        unsubbed = true
        unsub()

$timeout(unsub, 500)

Quoi qu'il en soit, cette solution est un gâchis total. Ça casse toutes sortes de choses. Nous avons vraiment besoin d'un correctif angulaire approuvé.

parce que je trouvais que unsub() n'était pas toujours appelé dans votre exemple préféré

@lookfirst vous utilisez location.path J'utilise location.url . Cela pourrait avoir un impact.

Juste pinailler (j'aime être utile) : au lieu de originalPath.apply($location, [url]) vous pouvez faire originalPath.call($location, url) (vous le savez peut-être déjà)

Intéressant... location.url fonctionne beaucoup mieux (le bouton de retour fonctionne à nouveau !), mais semble causer un autre problème de mon côté. Je vais devoir creuser davantage quand je n'ai pas codé pendant 10 heures d'affilée. Merci pour les conseils et la discussion, vraiment!

+1

+1

+1

@kuchumovn Votre solution est excellente. Un problème que j'ai eu est que $routeParams ne change pas après le changement de chemin. Je l'ai résolu en remplaçant la valeur de $routeParams dans les écouteurs d'événement. Je ne sais pas si c'est la bonne méthode, mais cela fonctionne pour moi.

app.factory('Location', [
    '$location',
    '$route',
    '$rootScope',
    '$routeParams',
    function ($location, $route, $rootScope, $routeParams) {
        var page_route = $route.current;

        $location.skipReload = function () {
            //var lastRoute = $route.current;
            var unbind = $rootScope.$on('$locationChangeSuccess', function () {
                angular.copy($route.current.params, $routeParams);
                $route.current = page_route;
                unbind();
            });
            return $location;
        };

        if ($location.intercept) {
            throw '$location.intercept is already defined';
        }

        $location.intercept = function(url_pattern, load_url) {

            function parse_path() {
                var match = $location.path().match(url_pattern);
                if (match) {
                    match.shift();
                    return match;
                }
            }

            var unbind = $rootScope.$on("$locationChangeSuccess", function() {
                var matched = parse_path();
                if (!matched || load_url(matched) === false) {
                  return unbind();
                }
                angular.copy($route.current.params, $routeParams);
                $route.current = page_route;
            });
        };

        return $location;
    }
]);

+1

+1

J'ai parlé à @IgorMinar en personne hier... il dit que l'avenir est le routeur dans Angular 2.0, qui sera bientôt de nouveau porté en 1.3. Quelque chose à considérer...

pourquoi voulez-vous simplement fusionner ui-router en angulaire ?
Le routeur uui semble assez résoudre tout ce qui peut être nécessaire.

d'accord avec @elennaro. bien que ui-router demande un peu de créativité et d'étude pour le faire fonctionner, il fait l'affaire dans tous les scénarios que j'ai rencontrés. curieux de savoir quels sont les inconvénients de l'adoption d'un routeur ui et de l'utilisation de ressources Google ?

Il semble qu'aucune des solutions n'exécutera la résolution, des idées ?

+1

+1

+1 pour $location.path(value, { trigger : false })
Des progrès là-dessus ?

+1

+1

+1 qu'en est-il d'une méthode complètement séparée ? en laissant path() tel quel et en ajoutant simplement une méthode $location.hash qui modifie simplement l'URL du navigateur sans recharger le contrôleur. cela nous permet de contrôler la navigation arrière du navigateur et constitue un scénario d'utilisation valide dans les applications riches.

+1

Utilise actuellement l'extrait de code ici sur cette page : http://joelsaupe.com/programming/angularjs-change-path-without-reloading/

+1

+1

+1

Une solution de contournement que j'ai trouvée consistait à ajouter un état enfant avec une URL absolue.

$stateProvider.state('state.child', {
    url: '^/some-other-url'
});

Ensuite, faites simplement $state.go('.child') et cela devrait changer l'URL en celle que vous voulez sans recharger et garder tout le monde heureux. Notez que caret, qui indique que l'URL n'héritera pas du parent.. m'a pris un certain temps pour comprendre cela.

+1

+1

+1

+1

+1

+1

+1

@btford comment cela est-il traité (le cas échéant) dans le componentRouter ?

Merci @balsarori pour une solution simple et fonctionnelle .

Élaboré dessus pour permettre la mise à jour des paramètres de chemin $route , sans recharger le contrôleur.

Il utilise console.log au lieu de $log car nous pouvons simplement supprimer tous les journaux pour les versions de production avec uglifyjs.

Edit : Correction lors de l'utilisation dans un projet :

/**
 * parameter handling
 */

'use strict';

angular.module('route-params', [
  'ng',
  'ngRoute',
])
.service('routeParams', function(
  $route,
  $location
) {
  var R = {};

  /**
   * <strong i="15">@ngdoc</strong> method
   * <strong i="16">@name</strong> routeParams#replace
   *
   * <strong i="17">@description</strong>
   * Replace route params, including path params, without reloading controller.
   *
   * Causes `$route` service to update the current URL, replacing
   * current route parameters with those specified in `params`.
   *
   * Provided property names that match the route's path segment
   * definitions will be interpolated into the location's path, while
   * remaining properties will be treated as query params.
   *
   * If `options.reload` is truthy, the current controller is reloaded.
   *
   * If `options.history` is truthy, the browser history is updated.
   * If `options.history` is undefined, the browser history is only updated
   * when changing value of any existing `$route` parameter,
   * ie. not when adding a previously undefined `$route` parameter.
   *
   * <strong i="18">@param</strong> {!Object<string, string>} params mapping of URL parameter names to values
   * <strong i="19">@param</strong> {!Object<string, boolean>} options `history` and `reload` options.
   *
   * <strong i="20">@returns</strong> {Object} self
   */
  R.replace = function(params, options) {
    var key, value, updateRequired;
    options = options || {};

    // Convert params to strings, and check if they differ from current route params.
    // If `options.history` is undefined, and passed params update any current route params, then set it to true.
    var currentParams = $route.current.params;

    console.log('route: params: replace', {last: angular.copy(currentParams), next: params,
      route: $route.current, options: options});

    if (!$route.current.$$route) {
      // Can't change location.
      return;
    }
    var currentPath = $route.current.$$route.originalPath;
    var history = options.history;

    for (key in params) {
      // jshint eqnull: true
      value = params[key] = (params[key] == null ? undefined : params[key] + '');

      if (value !== currentParams[key]) {
        updateRequired = true;

        console.log('route: params: replace: ' + (currentPath.search(':\\b' + key + '\\b') ? 'path.' : 'search.') +
          key + ' = ' + (key in $route.current.params ? $route.current.params[key] + ' -> ' : '') + params[key]);

        if (history === undefined && key in currentParams) {
          console.log('route: params: replace: update history: ' + key + ' = ' + currentParams[key] + ' -> ' + value);
          history = true;
        }
      }
    }

    if (updateRequired) {
      // If `options.reload` is falsey, then set current route `reloadOnSearch` to false,
      // and make passed path params equal with current route path params,
      // so that `$route` treats the change as update only, and does not broadcast `$routeChangeStart` event.
      //
      // See https://github.com/angular/angular.js/issues/1699#issuecomment-45048054
      // and https://github.com/angular/angular.js/tree/v1.3.x/src/ngRoute/route.js#L484
      // and https://github.com/angular/angular.js/tree/v1.3.x/src/ngRoute/route.js#L539
      if (!options.reload) {
        // Set current route `reloadOnSearch` to false.
        $route.current.$$route.reloadOnSearch = false;

        // Add any passed path params to current route path params, and convert them to strings.
        // Path params are detected by searching for respective name group `:key[*?]` in current route path.
        for (key in params) {
          if (currentPath.search(':\\b' + key + '\\b') !== -1) {
            $route.current.pathParams[key] = params[key];
          }
        }

        // Add any current route path params to passed params, if not set there already.
        for (key in $route.current.pathParams) {
          if (!(key in params)) {
            params[key] = $route.current.pathParams[key];
          }
        }

        // TODO: push state if `options.history` is truthy.
      }
      // If `options.history` is falsey, and controller is reloaded,
      // then make `$location` replace current history state, instead of pushing a new one.
      else if (!history) {
        $location.replace();
      }

      $route.updateParams(params);

      // Update current route params, so the change is reflected immediatelly,
      // and nearby replace() call work correctly.
      for (key in params) {
        $route.current.params[key] = params[key];
      }
    }

    return R;
  };

  return R;
});

Quel est l'état actuel de ce problème ? Il sera résolu dans le nouveau routeur? À temps pour la version 1.4 ? Bientôt ?

J'ai le même problème mais avec les paramètres de chaîne de requête. Je ne veux pas de rechargement en les changeant. Mais je ne peux pas désactiver reloadOnSearch car je dois savoir si l'utilisateur modifie la chaîne de requête manuellement ! :)

Merci!

@marcalj fwiw, nous avons piraté cette solution avec un service qui synchronisait l'URL avec la barre d'adresse afin que nous puissions désactiver reloadOnSearch et récupérer les paramètres de requête. les utilisateurs peuvent également naviguer dans l'url directement via un signet ou un lien.

Avec ng-router ou ui-router ? Pouvez-vous me fournir un lien avec le code ou quelque chose?

Peut-être que cela pourrait être résolu en utilisant un nouveau routeur, je peux l'attendre car mon projet n'est pas encore terminé.

Merci!

@marcalj notre exemple est un peu compliqué et peut-être obsolète mais regardez https://github.com/irthos/mngr/blob/master/app.js et vérifiez la ligne 10. il utilise notre méthode api.loadState qui identifie les paramètres dans le url pour construire l'état approprié.

NgSilent était la solution pour moi.
Regardez https://github.com/garakh/ngSilent
Testé avec ngRoute 1.3.15

J'ai utilisé les solutions @sergey-buturlakin et @kuchumovn , et je rencontre continuellement le même problème.

Sur la page en question (pagex), j'utilise location.skipReload et cela fonctionne très bien - je peux mettre à jour le chemin encore et encore sans changer d'itinéraire. Si j'utilise ensuite un $location.path réel pour changer l'itinéraire de pagex vers un nouveau contrôleur, cela fonctionne, mais si j'appuie sur le bouton de retour pour revenir à pagex, puis essayez de naviguer vers une autre page, je ne peux plus plus changer mon itinéraire sans rafraîchir le navigateur. Y a-t-il un moyen de contourner ceci?

+1

+1. J'aime l'approche du double côlon suggérée par Matias

+1

Il est courant que l'application à page unique ne recharge pas la page lors du changement de chemin,
très étrange pourquoi il n'est pas encore intégré à angulaire.
plugin créé basé sur la solution @EvanWinstanley :
https://github.com/anglibs/angular-location-update

Revoyons cela en 1.5. Il y a un PR connexe au #5860

J'ai besoin de la même fonction pour la mise à jour de la chaîne de requête sans déclencher le contrôleur de route ! :)

Quelque chose comme : $location.searchSilent('sf', SearchFilter.getForRouteParams());

$location.path(href);
portée.$apply();
est la solution !!
nous devons informer pour anguleux le changement avec $apply()

Ce fil n'a pas été ouvert depuis 2 ans car angular n'avait pas été notifié -- nous voulons changer une partie de la route sans tout appeler.

@petebacondarwin, il serait bon d'avoir non seulement la mise à jour du chemin de localisation sans rechargement, mais également la mise à jour des paramètres de chemin sans rechargement ... comme implémenté ci-dessus https://github.com/angular/angular.js/issues/1699#issuecomment -96126887

La solution de @jicasada a parfaitement fonctionné pour moi ! Merci!

@jicasada est-ce que cela fonctionne avec ui-router ? J'ai signalé un bogue avec ui-router où reloa dOnSearch:false ne fonctionne pas si les états parent et enfant partagent la même URL. Si je passe à l'état enfant avec un paramètre de demande mis à jour, il appellera le contrôleur parent deux fois.

+1

+1

Je confirme que ngSilent fait parfaitement le travail. Même ainsi, une solution officielle serait très appréciée.

La solution consiste essentiellement à ajouter un paramètre supplémentaire à $location.path().

app.run(['$route', '$rootScope', '$location', function ($route, $rootScope, $location) {
    var original = $location.path;
    $location.path = function (path, reload) {
        if (reload === false) {
            var lastRoute = $route.current;
            var un = $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = lastRoute;
                un();
            });
        }
        return original.apply($location, [path]);
    };
}]);

Et puis utiliser comme ça

$location.path('/path/that/you/need', false);

Je devrais juste pouvoir faire $location.replace() tout comme la fonction window.location.replace. C'est tout simplement idiot car chaque création dans une opération CRUD aura toujours ce problème.

(et) +1

+1

Je vous conseille d'utiliser "search param" comme ceci :
/thing?id=1234
Ne configurez pas le paramètre dans le fournisseur de routeur, il ne rechargera pas la vue et le contrôleur.
Si vous voulez obtenir un identifiant
$location.search().id //1234
@cgross

Démo http://codepen.io/dolymood/details/jrEQBx/
Utilisez decorator au décorateur ngViewDirective

+1

+1

+1

+1

+1

+1

+ !

Assez de +1 déjà :-)
Nous comprenons le message. On verra ça avant Noël !

Fermé par #15002.

Cette page vous a été utile?
0 / 5 - 0 notes