Angular.js: Opção em $location para permitir mudança de hash/path sem recarregar a rota

Criado em 12 dez. 2012  ·  194Comentários  ·  Fonte: angular/angular.js

Temos um paradigma em nosso aplicativo que o usuário cria coisas novas na mesma página em que visualiza as coisas. Então nossa rota é como /thing/:id. Ao criar uma nova coisa, eles vão para /thing/new. Uma vez que a coisa foi salva com sucesso, queremos alterar a rota para /thing/1234 (ou qualquer que seja seu novo id). Agora a parcial não precisa ser recarregada porque os dados são todos iguais. Queremos apenas que o caminho seja atualizado para que um usuário agora possa marcar o link correto, etc.

Ter uma opção em $location (não na definição de rota) para ativar/desativar o carregamento de rota funcionaria, mas tenho certeza de que existem outras maneiras de implementar o recurso.

Lots of comments $location ngRoute high confusing feature

Comentários muito úteis

A solução é essencialmente adicionar um parâmetro extra a $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]);
    };
}]);

E depois é só usar assim

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

Todos 194 comentários

+1, com o mesmo problema.

@cgross Como o serviço $location usa encadeamento de métodos no estilo JQuery e todos os comandos são adiados até $digest, sugiro adicionar um novo método de encadeamento, por exemplo, reload(boolean):

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

PS Este é o Chris Gross que iniciou o projeto Eclipse Nebula?

Sim :)

+1 :+1:

+1

No momento, estamos usando um hack sujo para contornar esse problema.

    $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

Aqui está minha tentativa de corrigir isso suportando preventDefault() para $routeChangeStart . O que você acha disso?
Commit: https://github.com/redhotsly/angular.js/commit/f8ac46e6ac9d76cf855077d21b68b4c2c8043db1

Estou trabalhando na abordagem @qualidafial há alguns dias e agora o PR está pronto. Eu criei um novo método notify que permite pular a notificação de mudança de local por uma vez. Isso deve evitar acionar qualquer alteração ou recarga de rota.

Isso não afeta o sistema de roteamento, portanto, você não precisa alterar suas definições de rota. Ele ainda funcionará mesmo se você usar ui-router vez do sistema de roteamento AngularJS padrão.

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

Também trabalhei alguns testes e bits de documentação.

+1

outro +1
Usando @lrlopez ' pull request já. Funciona como um encanto.

+1

+1

Infelizmente, o PR https://github.com/angular/angular.js/pull/2398 foi rejeitado pela equipe Angular, pois pular notificações pode levar a inconsistências entre o URL real e a rota atual. Há uma longa explicação no final da discussão.

Não preciso desse recurso em nenhum dos meus projetos, mas vou mantê-lo em meu repositório para que possa ser mesclado, se desejar. Apenas deixe-me saber se ficar fora de sincronia com o branch master para que eu possa rebasear as alterações. Obrigado!

+1

+1, preciso disso :(

+1

Edit: melhor abordagem aqui: https://github.com/angular/angular.js/issues/1699#issuecomment -22511464

Eu criei uma fábrica reutilizável para contornar isso (com base na ideia 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;
      }
    });
  };
}]);

Como usar:

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

  DoNotReloadCurrentTemplate($scope);
}]);

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

Se você definir "reloadOnSearch" como false na rota, parece corrigir as alterações de hash recarregando a página inteira.

+1

Isso seria ótimo para aplicativos móveis, onde o ciclo de vida de criação/destruição do ng-view não é apropriado (mata o desempenho e a usabilidade).

@tkrotoff Obrigado pelo exemplo. Como sua solução alternativa está cancelando as notificações de eventos, adicionei uma transmissão no rootscope para que outros controladores ainda possam ser notificados sobre a alteração da rota:

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

Então:

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

Eu tenho o mesmo problema que o autor do tópico e criou uma versão modificada da solução descrita acima.

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

Uso:

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

Usar o ouvinte de evento permanente que testa templateUrl para decidir se permite o recarregamento de rota pode interromper o roteamento para outras exibições que usam o mesmo templateUrl. Por exemplo, eu roteei para /thing/new e a exibição mostra o link para a última coisa adicionada /thing/5, não poderei navegar para a coisa 5 e para qualquer outra coisa (mesmo alterando a url no navegador explicitamente). Outro problema com o teste de templateUrl é que os templates podem ser especificados usando a propriedade template (como string ou função).

Este hack permite pular exatamente uma recarga de rota e não quebra o roteamento.

Também tentei uma solução menos detalhada (para uso no controlador):

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

Parece funcionar bem.

+1

@sergey-buturlakin seu $location.skipReload é simplesmente maravilhoso! Eu uso em vez de DoNotReloadCurrentTemplate

@sergey-buturlakin Sua solução parece estar funcionando para mim. Obrigado!

depois de executar skipReload() de https://github.com/angular/angular.js/issues/1699#issuecomment -22511464 ele continua recarregando desativado
parece que un() deve ser chamado mais tarde

Ele evita qualquer próximo recarregamento e deve ser chamado logo antes da mudança de caminho.
Quando a mudança de caminho terminar - "un" será chamado.

sim, mas eu queria pular o recarregamento apenas uma vez, no entanto, a abordagem proposta impede qualquer recarregamento adicional

+1

+1

+1

PR https://github.com/angular/angular.js/pull/2398 só pula o recarregamento uma vez. Não sei se tem conflitos de mesclagem com o mestre atual. Se for esse o caso, apenas me mande uma linha e eu vou corrigi-lo.

Eu uso a solução do sergey, enquanto descarto a variável "un" inútil (segundo mim) e chamo.

Isto leva a:

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

e qualquer chamador:

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

+1

+1

+1

+1

@mica16 A un é usada para desvincular o evento?

Eu criei dois plunkrs para teste, com e sem a variável un para desvincular os eventos e parece que sem ela o evento continua disparando repetidamente

Sim, de fato, a variável un soa estranha.
Segundo o autor original, seria considerado útil... mas não estou convencido de que seja a solução perfeita.

Preferi reescrevê-lo, mas descartei o recurso, pois não o espero mais.

Então vá em frente se o seu teste de unidade puder garantir que funcione como você espera :)

Vocês estão certos caras
Minha solução foi

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
]

então no controlador

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

+1

+1

+1

+1

outra maneira de fazer isso é colocá-lo fora da "etapa" atual do javascript para que a rota não fique ciente da alteração feita no window.location:

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

+1

+1

+1

+1

Que tal usar $anchorScroll? Eu estava vasculhando a net e descobri que existem algumas ferramentas para fazer isso. Funciona para mim pelo menos.

Você deve conferir: http://docs.angularjs.org/api/ng. $anchorScroll

+1

Isso é uma má ideia? Admito que não testei extensivamente. Não gostei da ideia de chamar um método separado antes de definir o caminho. Do ponto de vista de uso, pelo menos, isso parece mais limpo.

Eu estendi a função $location.path embutida, usando a técnica 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]);
        };
    }])

Uso:

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

+1

As soluções

+1

A correção do @EvanWinstanley/sergey parece legal e não recarrega o controlador, mas ao alterar a rota, as resoluções são solicitadas novamente.

Meu principal caso de uso para isso seria alterar manualmente o $location.path sem recarregar o resolve/controller para que eu possa fazer o cache.

Por exemplo: Eu tenho um select que permite ao usuário escolher entre vários aplicativos, o que também altera o $location.path para urls mais agradáveis. Quando um usuário seleciona um aplicativo, os resultados da chamada da API são armazenados em cache. Mesmo com a correção, ele sempre faz a resolução novamente, o que é desnecessário, pois os resultados já estão armazenados em cache.

+1

A solução @sergey-buturlakin funciona bem para mim, não recarrega o controlador, mas ainda recarrega a função reslove e envia solicitação para o servidor.

+1

A solução do @EvanWinstanley não está funcionando para mim. Por outro lado, a solução do @mica16 funcionou muito bem... (o botão voltar não atualizava a página). Tentei escrever minha própria versão sem sucesso :(.

+1

+1

+1

+1
Por favor, suporte: $location.reload();

@jvmvik você já tentou $route.current.reload() ?

7.1.1

nenhuma notícia sobre isso depois de mais de um ano?

+1

+1

Eu fiz um pouco de melhoria na solução de Sergey.
Agora ele suporta, por exemplo, urls personalizadas para guias (guias de navegação) sem recarregar a página inteira.

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

Outra solução para evitar o recarregamento da rota ao alterar o caminho de $location é definir o pathParams da rota atual para corresponder ao novo caminho de local.

Na minha configuração $routeProvider, tenho uma rota para /tasks da seguinte forma

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

A rota acima tem duas seções pathParams e taskId (taskId é opcional) e reloadOnSearch é definido como false.

Para navegar de, digamos, /tasks/inbox para /tasks/inbox/122 sem recarregar a rota, defina o taskId pathParam antes de alterar o caminho $location da seguinte maneira

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

Observe que a seção pathParam não precisa ser configurada, pois já foi configurada pela rota.

Quando a rota descobre que o pathParams extraído do novo caminho $location corresponde à rota atual pathParams (e reloadOnSearch está definido como false), ele não recarregará a rota, em vez disso, um evento $routeUpdate será acionado.

Para navegar de volta de /tasks/inbox/122 para /tasks/inbox sem recarregar a rota, exclua o taskId pathParam antes de alterar o caminho $location da seguinte maneira

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

+1

+:100:

Você leu o artigo de Derick Bailey sobre o roteador Backbone http://lostechies.com/derickbailey/2011/08/28/dont-execute-a-backbone-js-route-handler-from-your-code/ ? O Backbone fornece essa opção e, de acordo com Derick, você deve sempre evitar acionar uma recarga (que parece o oposto do que o roteador do angular parece fazer aqui).
Existem 48 "+1" neste post... Não acho que todas essas pessoas estejam fazendo algo errado. Ter que armazenar var lastRoute = $route.current; e restaurá-lo mais tarde em $locationChangeSuccess com $route.current = lastRoute; parece bastante complicado.
@IgorMinar você tem alguma opinião?

OK, eu sou o dono deste tópico para a próxima semana para que possamos finalmente fechar isso.

Primeiramente, não acho que a rota deva recarregar quando o valor #hash for alterado.

website.com/#/my-route#top

website.com/#/my-route#bottom

Portanto, quando o topo muda para o fundo, ele não deve recarregar, mas se o valor da minha rota for alterado, ele deverá. Todos podemos concordar com isso certo?

Segundo, o que é mais importante, precisamos de uma maneira fácil de apontar que o caminho da rota não será recarregado quando alterado, mas isso se tornará problemático se um fragmento anterior da rota for alterado. Então, por que não temos uma notação diferente para um parâmetro de rota usando dois dois-pontos :: para apontar que ele age como um valor curinga.

// 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')

O que é que vocês acham?

Isso realmente não ajuda com a situação de onde você deseja se mudar:

/users/new

para

/users/123

uma vez que o novo usuário foi criado e atribuído um id, sem recarregar o usuário, pois ele já está lá.

Porque de alguma forma é preciso mapear o novo objeto de usuário para o objeto de usuário atual com id 123. Isso é bastante específico do aplicativo, não?

Pelo que vale a pena, o Ember Router não suporta a alteração de URL sem fazer a transição:

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

@matsko Podemos apenas capacitar o engenheiro e deixá-lo decidir se a rota deve desencadear uma mudança ou não?
Estou bem com bons padrões.
Crie uma maneira para que o engenheiro possa alterar a url sem executar toda a cadeia de eventos de recarga. Pessoalmente, estou bem em adicionar um parâmetro extra opcional com opções (ex: {reload: false}) em ".path()", ".search()", etc.

@ChrisCinelli sim, mas o que acontece quando há dois parâmetros em uma definição de rota? Como você pode impedir que um recarregue enquanto permite que o outro? A bandeira seria um recurso de tudo ou nada.

@matsko Então, no final da implementação, está sendo considerado que poderíamos escolher quais parâmetros causam recargas e quais não? E em caso afirmativo, você já tem uma abordagem em mente ou é aí que estamos atingindo um obstáculo?

@cgross cherry-pick sim (usando os dois pontos duplos). Esta é apenas uma ideia por enquanto, então nada de concreto. Se isso estiver alinhado com os planos para o angular 2.0, talvez possamos usá-lo ou tentar usar algo que o 2.0 está planejando ter ou algo que o Dart já possui (para manter a API consistente).

Eu acho que seria perfeito e ficaria bem (pelo menos IMO) como parte da rota.

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

E então, no controlador

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

Sim, algo assim, mas os dois pontos duplos '::id' seriam usados ​​em vez de :id[reload=no] .

Tenha em mente que isso é apenas uma ideia, mas muitas pessoas aqui querem esse recurso.

Eu estava pensando em talvez adicionar outras opções, mas acho que não há realmente outras opções que possam existir. É bom demais ver que esse pedido está sendo levado a sério. URLs bonitas são um grande negócio para muitos de nós.

Matsko +1 para dois pontos duplos, mas acho que também seria útil poder passar opções para path() também para alguns casos de borda mencionados acima

+1 para dois pontos duplos

@matsko me pediu para avaliar como estamos lidando com isso no roteador do Angular 2.0. Eu sei que isso não ajuda aqueles de vocês que precisam disso para ng1.x, mas pensei que poderia ser interessante para você.

O novo roteador tem um método navigate para o qual você pode passar um _url_ e um objeto _options_ opcional. Isso é baseado nos recursos de histórico do roteador Backbone, então ele suporta duas opções replace e trigger Se você quiser ir de /users/new para /users/123 sem causando uma navegação na tela, você simplesmente especifica trigger:false . Se você também quiser que ele substitua a entrada do histórico /users/new pelo novo URL baseado em id, especifique também replace:true .

Atualizar rotas de uma rota _new_ para uma rota _id-based_ sem causar transformações DOM etc. é um caso de uso importante para essa API. Outro exemplo comum é a paginação. Por exemplo, se você tem uma grade e deseja que o usuário navegue pelos dados, pode usar isso para atualizar o fragmento com as informações da página atual sem causar navegação. Tenho certeza de que existem outros usos, mas esses são os que tenho visto repetidamente em minha consultoria ao longo dos anos.

Espero que esta informação ajude. Talvez o roteador 1.x possa adotar essa técnica?

@EisenbergEffect para confirmar. Para 1.x, o método para fazer isso sem alterar o caminho ficaria assim:

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

E isso substituiria efetivamente o caminho sem fazer com que a nova rota + controlador ficasse ciente. Isso pode funcionar por enquanto, mas como você se sente sobre a ideia de dois pontos duplos :: ?

Para todos os outros aqui, você prefere que a mudança de URL (sem recarregar a rota) seja facilitada pela rota ou pelo desenvolvedor quando a URL for alterada?

Deve haver uma fonte central de verdade para a localização, como um serviço que
rotas e navegadores podem se referir e influenciar, mas continua sendo o único
verdade
Em 1º de julho de 2014, 23h28, "Matias Niemelä" [email protected] escreveu:

@EisenbergEffect https://github.com/EisenbergEffect para confirmar. Por
1.x, o método para fazer isso sem alterar o caminho ficaria assim:

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

E isso substituiria efetivamente o caminho sem causar a nova rota

  • controlador se conscientize. Isso pode funcionar por agora, mas como você se sente
    sobre o duplo :: ideia de dois pontos?

Para todos os outros aqui, você preferiria que a URL mudasse (sem
recarregar a rota) ser facilitado pela rota ou pelo desenvolvedor
quando o URL é alterado?


Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/angular/angular.js/issues/1699#issuecomment -47741350.

Aqui está outra idéia... Permitir dois novos métodos em cada definição de rota, digamos entering(transitionInfo) e leaving(transitionInfo) ou algo assim. Se eles existirem, então o método leaving seria chamado na rota antiga antes de sair e então o método entering() seria chamado na nova rota. O transitionInfo seria um objeto parecido com o seguinte:

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

Nesse método, podemos verificar as rotas/urls antigas e novas e, se você quiser que a URL seja alterada, mas não execute novamente a transição da rota, defina ignoreRouteChange para true . Se você quiser cancelar a mudança de rota e voltar para a rota anterior, defina cancelRouteChange como true.

Isso dá ao desenvolvedor controle completo sobre quando as transições de rota ocorrem, funcionará se a URL for modificada diretamente na barra de endereço e também localizará a funcionalidade para decidir sobre uma alteração de rota específica perto da definição das rotas individuais que se preocupam com a alteração .

Acredito que exista algo semelhante no roteador AngularV2 chamado activate() . @EisenbergEffect você pode confirmar?

O que você acha?

@matsko Sim, essa API pode ser assim. Você também pode considerar implementar a opção replace , já que isso também é frequentemente necessário. Neste caso específico de /users/new a /users/123 eu acho que você aproveitaria as duas opções, já que você não quer que /users/new permaneça na pilha de trás depois que o novo usuário for criado.

Em relação ao :: , não tenho certeza se é totalmente útil. A razão é que, no exemplo de usuários acima, não é o padrão de url que importa, mas o contexto específico do aplicativo. Por exemplo, se você fizer a transição de customers para users/123 , você deseja notificar. Além disso, se você abandonar users/new e for para users/456 , você também deseja notificar. É somente quando você está em users/new e finalizou a criação do novo usuário que você deseja simplesmente atualizar o fragmento para representar o id do usuário recém-criado sem causar nenhuma transição. Isso depende muito do que o usuário está realmente fazendo no contexto da tela do usuário naquele momento.

@petebacondarwin Um problema com essa ideia é que o conhecimento da permissão da notificação de rota não é algo que está globalmente disponível junto com as definições de rota. Em vez disso, ele é incorporado ao controlador de usuário específico, pois esse controlador é o pedaço de código que sabe se um novo usuário está sendo criado ou não neste momento, em vez de simplesmente navegar da tela do novo usuário para um usuário pré-existente.

Para mim, o caminho a seguir é dar controle ao chamador que deseja alterar uma rota. Então eu sou definitivamente a favor de duas opções replace e trigger .

@EisenbergEffect - você está certo. Em geral, essa informação não está imediatamente disponível, embora eu imagine implementá-la da seguinte maneira:

Tenha um serviço chamado currentUser , que contém o usuário com o qual estamos trabalhando no momento. Eu gosto de fazer isso geralmente, pois não me sinto confortável em sobrecarregar meus controladores com código. Se isso parecer muito específico, você poderá ter currentEntity , que é a "coisa" atual que você está editando/visualizando.

Então o método route enter() poderia ter isso injetado e decidir se navegar para essa rota realmente mudaria a entidade atual. Caso contrário, não nos preocupamos em executar a transição, apenas atualize o URL.

Eu aprecio que isso possa se tornar um pouco complicado em um grande aplicativo, mas pensei em jogá-lo como uma opção.

Quais são os argumentos contra dar ao chamador (ou seja, aquele que solicita a mudança de rota/url) o controle sobre se a transição de rota realmente ocorre? Lembro que @IgorMinar estava preocupado que isso quebrasse a restrição de que um estado do aplicativo fosse mapeado diretamente para um URL e que permitir que o URL mudasse sem executar novamente a rota potencialmente colocaria o aplicativo em um estado diferente dependendo do maneira como você chegou lá.

@petebacondarwin Isso poderia funcionar para esse cenário específico, mas fica um pouco mais complicado lidar com o exemplo de paginação que mencionei acima, que não deseja acionar e não substituir, enquanto o novo cenário de usuário deseja não acionar, mas substituir.

Para resolver a questão do Estado é um pouco mais complicado. A razão é que temos duas URLs que representam o mesmo estado do controlador... mas a diferença está no modelo. Então, na verdade, temos dois estados diferentes na realidade... mas de uma perspectiva de UX, não queremos sacudir o usuário ao fazer a transição. Do ponto de vista do desempenho, não queremos jogar fora o DOM desnecessariamente e recriar tudo. Portanto, no caso do novo usuário => transição de usuário salvo, realmente queremos evitar o acionamento e também substituir o fragmento para que o botão Voltar _não_ leve você de volta à tela do novo usuário. Internamente, o roteador ou mecanismo de localização precisaria apenas rastrear qual é o fragmento atual. (Estou fazendo uma suposição, pois não sei como o ng1.x funciona a esse respeito. Com o 2.x, certificamo-nos de rastrear cuidadosamente o fragmento atual/anterior, independentemente de como ele foi alterado.) Em no cenário de paginação, não queremos acionar porque queremos um bom UX para paginar os dados e não queremos descarte desnecessário de DOM. Mas nós queremos que o fragmento anterior seja mantido para que o usuário possa usar o botão Voltar para retroceder pelas páginas de dados.

Observe que, em ambos os casos, não se trata apenas do URL e do estado do aplicativo, mas também de como fazemos a transição do estado atual para o novo estado. Nada disso importa se você fizer um link direto diretamente para a nova visualização. Importa quando você está se movendo de um para outro dentro do contexto desses casos muito específicos.

+1

+1

+1

+1
E também recurso para alterar o URL sem alterar a rota

+1 - bom ver que não estou sozinho querendo isso!

+1, ainda não adicionou(

+1

+1

+1

+1

+1

+1

+1

:+1:

Você também @basarat? :sorriso:

@johnnyreilly apenas tem um serviço onde você injeta $route e $location e você pode ter esta função:

/** 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();
            });
        }
    }

Legal @basarat!

+1

+1

Uma coisa que estou percebendo com as soluções atuais é que meu bloco de resolução do roteador ainda está sendo executado. Alguém mais percebe isso? Foi bastante fácil passar algo no objeto $route.current, mas isso parece complicado. Além disso, parece que esse problema nunca será corrigido? :choro:

+1

+1

Para aqueles de vocês que não querem ter que rolar para trás... até onde eu posso dizer, isso é o que temos até agora (desculpe, coffeescript). Infelizmente, isso não está funcionando 100% para mim. Ainda estou executando meu resolve e os botões voltar/avançar parecem quebrados agora.

#
# 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 você está chamando unsub duas vezes. Preferência: https://github.com/angular/angular.js/issues/1699#issuecomment -54931571

@basarat encolher... Eu adicionei isso porque estava descobrindo que unsub() nem sempre estava sendo chamado em seu exemplo preferido. Talvez o melhor seja adicionar um guarda na função unsub.

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

$timeout(unsub, 500)

Independentemente disso, esta solução é uma bagunça total. Quebra todo tipo de coisa. Nós realmente precisamos de uma correção angular aprovada.

porque eu estava descobrindo que unsub() nem sempre estava sendo chamado em seu exemplo preferido

@lookfirst você está usando location.path eu estou usando location.url . Isso pode estar impactando.

Apenas picuinhas (eu gosto de ser útil): em vez de originalPath.apply($location, [url]) você pode fazer originalPath.call($location, url) (você já deve saber disso)

Interessante... location.url funciona muito melhor (o botão voltar funciona de novo!), mas parece estar causando outro problema do meu lado. Vou ter que me aprofundar mais nisso quando não estiver codificando por 10 horas seguidas. Obrigado pelas dicas e discussão, sério!

+1

+1

+1

@kuchumovn Sua solução é ótima. Um problema que tenho é que $routeParams não muda depois que o caminho mudou. Resolvi isso substituindo o valor de $routeParams dentro dos ouvintes do evento. Não tenho certeza se esta é a maneira correta, mas funciona para mim.

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

Falei com @IgorMinar pessoalmente ontem... ele diz que o futuro é o roteador em Angular 2.0, que voltará a ser portado para 1.3 em breve. Algo a considerar...

por que você quer apenas mesclar ui-router em angular?
O roteador uui parece estar resolvendo tudo o que pode ser necessário.

concordo com @elennaro. embora o ui-router exija alguma criatividade e estudo para fazê-lo funcionar, ele funciona em todos os cenários que encontrei. curioso quais são as desvantagens de adotar o ui-router e jogar recursos do Google nele?

Parece que nenhuma das soluções não vai executar a resolução, alguma ideia?

+1

+1

+1 por $location.path(value, { trigger : false })
Algum progresso nisso?

+1

+1

+1 que tal um método completamente separado? deixando path() como está, e apenas adicionando um método $location.hash que simplesmente altera a url do navegador sem recarregar o controlador. isso nos permite controlar a navegação de retorno do navegador e é um cenário de caso de uso válido em aplicativos avançados.

+1

Atualmente usando o trecho aqui nesta página: http://joelsaupe.com/programming/angularjs-change-path-without-reloading/

+1

+1

+1

Uma solução que encontrei foi adicionar um estado filho com um URL absoluto.

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

Em seguida, basta fazer $state.go('.child') e isso deve alterar o URL para o que você deseja sem recarregar e manter todos felizes. Observe esse acento circunflexo, que indica que o URL não herdará do pai. Demorei um pouco para descobrir isso.

+1

+1

+1

+1

+1

+1

+1

@btford como isso é tratado (se for) no componentRouter ?

Obrigado @balsarori por uma solução simples e funcional .

Elaborado em cima dele para permitir a atualização dos parâmetros do caminho $route , sem recarregar o controlador.

Ele usa console.log vez de $log pois podemos simplesmente remover todos os logs para compilações de produção com uglifyjs.

Edit: Corrige ao usar em um projeto:

/**
 * 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;
});

Qual é o status atual deste problema? Será resolvido no novo roteador? A tempo para o lançamento 1.4? Em breve?

Estou tendo o mesmo problema, mas com parâmetros de string de consulta. Eu não quero uma recarga ao alterá-los. Mas não consigo desabilitar reloadOnSearch porque preciso saber se o usuário altera a string de consulta manualmente! :)

Obrigado!

@marcalj fwiw, reloadOnSearch e pegar parâmetros de consulta. Além disso, os usuários podem navegar na url diretamente via bookmark ou link.

Com ng-router ou ui-router? Você pode me fornecer um link com o código ou algo assim?

Talvez isso possa ser resolvido usando um novo roteador, posso esperar, pois meu projeto ainda não está concluído.

Obrigado!

@marcalj nosso exemplo é um pouco complicado e possivelmente desatualizado, mas veja https://github.com/irthos/mngr/blob/master/app.js e verifique a linha 10. ele usa nosso método api.loadState que identifica parâmetros no url para construir o estado adequado.

NgSilent foi a solução para mim.
Veja https://github.com/garakh/ngSilent
Testado com ngRoute 1.3.15

Eu usei a solução @sergey-buturlakin e @kuchumovn e continuamente me deparo com o mesmo problema.

Na página em questão (pagex) eu uso location.skipReload e funciona muito bem - posso atualizar o caminho várias vezes sem alterar a rota. Se eu usar um $location.path real para alterar a rota de pagex para um novo controlador, funcionará, mas se eu pressionar o botão Voltar para retornar ao pagex e tentar navegar para outra página, não consigo mais alterar mais minha rota sem atualizar o navegador. Existe uma maneira de contornar isso?

+1

+1. Eu gosto da abordagem de dois pontos que Matias sugeriu

+1

É um caso de uso comum para o aplicativo de página única não recarregar a página na mudança de caminho,
muito estranho porque ainda não está incorporado ao angular.
plugin feito com base na solução @EvanWinstanley :
https://github.com/anglibs/angular-location-update

Vamos revisitar isso em 1.5. Há um PR relacionado em #5860

Eu preciso da mesma função para atualização de string de consulta sem acionar o controlador de rota! :)

Algo como: $location.searchSilent('sf', SearchFilter.getForRouteParams());

$localização.caminho(href);
escopo.$apply();
é a solução!!
precisamos informar para angular a mudança com $apply()

Este tópico não está aberto há 2 anos porque o angular não foi notificado - queremos alterar uma parte da rota sem invocar tudo.

@petebacondarwin seria bom ter não apenas atualização de caminho de localização sem recarregar, mas também atualização de parâmetros de caminho sem recarregar ... como implementado acima https://github.com/angular/angular.js/issues/1699#issuecomment -96126887

A solução do @jicasada funcionou perfeitamente bem para mim! THX!

@jicasada isso funciona com ui-router? Eu levantei um bug com ui-router onde reloa dOnSearch:false não funciona se o estado pai e filho compartilharem a mesma URL. Se eu fizer a transição para o estado filho com o parâmetro de solicitação atualizado, ele chamará o controlador pai duas vezes.

+1

+1

Confirmo que o ngSilent está fazendo perfeitamente o trabalho. Mesmo assim, uma solução oficial seria muito apreciada.

A solução é essencialmente adicionar um parâmetro extra a $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]);
    };
}]);

E depois é só usar assim

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

Eu deveria ser capaz de fazer $location.replace() assim como a função window.location.replace. Isso é bobagem porque toda criação em uma operação CRUD sempre terá esse problema.

(e) +1

+1

Eu aconselho você usar "search param" assim:
/thing?id=1234
Não configure o parâmetro no provedor do roteador, ele não recarregará a visualização e o controlador.
Se você deseja obter a identificação
$location.search().id //1234
@cgross

Demonstração http://codepen.io/dolymood/details/jrEQBx/
Use decorator para o decorador ngViewDirective

+1

+1

+1

+1

+1

+1

+!

Chega de +1s já :-)
Recebemos a mensagem. Veremos isso antes do Natal!

Fechado por #15002.

Esta página foi útil?
0 / 5 - 0 avaliações