AngularJS UI Router - alterar URL sem recarregar o estado

134

Atualmente, nosso projeto está usando o padrão $routeProvider, e eu estou usando esse "hack", para mudar urlsem recarregar a página:

services.service('$locationEx', ['$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;
}]);

e em controller

$locationEx.skipReload().path("/category/" + $scope.model.id).replace();

Estou pensando em substituir routeProvidercom ui-routerpara rotas de nidificação, mas não consigo encontrar isso em ui-router.

É possível - faça o mesmo com angular-ui-router?

Por que eu preciso disso? Deixe-me explicar com um exemplo:
Rota para criar nova categoria é /category/new depois clickingem SALVAR eu mostro success-alerte quero mudar de rota /category/newpara /caterogy/23(23 - é id do novo item armazenado em db)

vuliad
fonte
1
em ui-router Você não tem que definir uma URL para cada estados, você pode navegar de estado para estado sem alterar URL
Jonathan de M.
você deseja atualizar o URL inteiro ou apenas o caminho da pesquisa? Eu estava procurando uma solução de atualizar o caminho de pesquisa e encontrou-o aqui: stackoverflow.com/questions/21425378/...
Florian Loch
@johnathan Sério? Eu adoraria fazer isso, mostrando apenas um URL, mas $urlRouterProvider.otherwiseparece operar em um URL, não em um estado. Hmmm, talvez eu possa viver com 2 URLs ou encontrar outra maneira de indicar que é um URL inválido.
Mawg diz que restabelece Monica

Respostas:

164

Simplesmente você pode usar em $state.transitionTo vez de $state.go . $state.go chamadas $state.transitionTo internamente, mas define automaticamente as opções para { location: true, inherit: true, relative: $state.$current, notify: true } . Você pode ligar $state.transitionTo e definir notify: false . Por exemplo:

$state.go('.detail', {id: newId}) 

pode ser substituído por

$state.transitionTo('.detail', {id: newId}, {
    location: true,
    inherit: true,
    relative: $state.$current,
    notify: false
})

Edit: Como sugerido por fracz, pode ser simplesmente:

$state.go('.detail', {id: newId}, {notify: false}) 
rezKesh
fonte
16
Não é "manter o URL ao recarregar o estado" em vez de "alterar o URL sem recarregar o estado"?
Peter Hedberg
25
para mim ele trabalhou com o seguinte: $state.transitionTo('.detail', {id: newId}, { location: true, inherit: true, relative: $state.$current, notify: false }) Então, basicamente definir notificar à falsa e localização para true
Arjen de Vries
6
@ArjendeVries Sim, está funcionando como esperado, mas encontrei um comportamento inesperado. Depois de brincar com muitas chamadas de método transitTo (sem recarregar) quando você finalmente muda para um novo estado (por exemplo, url), ele reinicia o controlador antigo.
Premchandra Singh
2
@ Premchandra Singh: Tendo o mesmo problema aqui. Ao sair do estado, o antigo controlador é inicializado novamente. Eu recebo o mesmo problema com a solução aceita do wiherek. Veja também aqui github.com/angular-ui/ui-router/issues/64
martinoss
15
Ainda mais simples: $state.go('.detail', {id: newId}, {notify: false}).
Fracz
48

Ok, resolvido :) O Angular UI Router possui este novo método, $ urlRouterProvider.deferIntercept () https://github.com/angular-ui/ui-router/issues/64

basicamente, tudo se resume a isso:

angular.module('myApp', [ui.router])
  .config(['$urlRouterProvider', function ($urlRouterProvider) {
    $urlRouterProvider.deferIntercept();
  }])
  // then define the interception
  .run(['$rootScope', '$urlRouter', '$location', '$state', function ($rootScope, $urlRouter, $location, $state) {
    $rootScope.$on('$locationChangeSuccess', function(e, newUrl, oldUrl) {
      // Prevent $urlRouter's default handler from firing
      e.preventDefault();

      /** 
       * provide conditions on when to 
       * sync change in $location.path() with state reload.
       * I use $location and $state as examples, but
       * You can do any logic
       * before syncing OR stop syncing all together.
       */

      if ($state.current.name !== 'main.exampleState' || newUrl === 'http://some.url' || oldUrl !=='https://another.url') {
        // your stuff
        $urlRouter.sync();
      } else {
        // don't sync
      }
    });
    // Configures $urlRouter's listener *after* your custom listener
    $urlRouter.listen();
  }]);

Eu acho que esse método atualmente está incluído apenas na versão principal do roteador angular da interface do usuário, aquele com parâmetros opcionais (que também são bons, btw). Ele precisa ser clonado e construído a partir da fonte com

grunt build

Os documentos também podem ser acessados ​​da fonte, por meio de

grunt ngdocs

(eles são criados no diretório / site) // mais informações em README.MD

Parece haver outra maneira de fazer isso, por parâmetros dinâmicos (que eu não usei). Muitos créditos para nateabele.


Como nota de rodapé , aqui estão os parâmetros opcionais no $ stateProvider do Angular UI Router, que eu usei em combinação com o acima:

angular.module('myApp').config(['$stateProvider', function ($stateProvider) {    

  $stateProvider
    .state('main.doorsList', {
      url: 'doors',
      controller: DoorsListCtrl,
      resolve: DoorsListCtrl.resolve,
      templateUrl: '/modules/doors/doors-list.html'
    })
    .state('main.doorsSingle', {
      url: 'doors/:doorsSingle/:doorsDetail',
      params: {
        // as of today, it was unclear how to define a required parameter (more below)
        doorsSingle: {value: null},
        doorsDetail: {value: null}
      },
      controller: DoorsSingleCtrl,
      resolve: DoorsSingleCtrl.resolve,
      templateUrl: '/modules/doors/doors-single.html'
    });

}]);

o que isso faz é permitir resolver um estado, mesmo que um dos parâmetros esteja ausente. SEO é um objetivo, legibilidade outro.

No exemplo acima, eu queria que doorsSingle fosse um parâmetro obrigatório. Não está claro como defini-los. Ele funciona bem com vários parâmetros opcionais, portanto, não é realmente um problema. A discussão está aqui https://github.com/angular-ui/ui-router/pull/1032#issuecomment-49196090

wiherek
fonte
Parece que os parâmetros opcionais não funcionam. Error: Both params and url specicified in state 'state'. Diz nos documentos que também é uso inválido. Um pouco decepcionante.
Rhys van der Waerden
1
Você construiu do mestre? Observe que no momento em que adicionei a solução, os parâmetros opcionais foram incluídos apenas na versão que precisou ser criada manualmente a partir da fonte. isso não será incluído no release até que a v.0.3 seja lançada.
wiherek
1
Apenas uma nota rápida. Graças ao nateabele, os parâmetros opcionais estão disponíveis na v0.2.11, lançada há poucos dias.
precisa saber é o seguinte
isso é muito confuso: como uso $ urlRouterProvider.deferIntercept (); para que eu possa atualizar os parâmetros sem recarregar o controlador? Isso realmente não me mostra isso. Dentro da função de execução, você pode avaliar uma instrução if para sincronizar ou não, mas tudo o que tenho para trabalhar é o URL antigo e o novo. Como sei que essa é uma instância em que tudo que eu quero fazer é atualizar os parâmetros com apenas dois URLs para trabalhar? A lógica seria .... (se o estado antigo e o novo estado forem iguais, não recarregue o controlador?) Estou confuso.
btm1
certo, acho que esse caso de uso estava com estados aninhados, não queria recarregar o estado filho, portanto era interceptador. Acho que agora prefiro fazer isso com visões de segmentação absolutas e definir a visão do estado que sei que não mudaria. Enfim, isso ainda é bom. Você obtém URLs completos, ou seja, é possível adivinhar o estado a partir da URL. E também os parâmetros de consulta e caminho, etc. Se você criar seu aplicativo em torno de estados e URLs, isso é muita informação. No bloco de execução, você também pode acessar os serviços etc. Isso responde à sua pergunta?
wiherek
16

Depois de passar muito tempo com esse problema, aqui está o que eu comecei a trabalhar

$state.go('stateName',params,{
    // prevent the events onStart and onSuccess from firing
    notify:false,
    // prevent reload of the current state
    reload:false, 
    // replace the last record when changing the params so you don't hit the back button and get old params
    location:'replace', 
    // inherit the current params on the url
    inherit:true
});
Pankaj Parkar
fonte
As outras soluções estão relacionadas ao provedor de rotas. Esta solução funciona para o caso, como o meu, em que estou usando $ stateProvider e não usando $ routeProvider.
eeejay
@eeejay basicamente a pergunta foi feita para ui-routersomente, como você pode dizer isso, outras soluções estavam trabalhando para $routerProvider, $routeProvidere $stateProvidersão totalmente diferentes na arquitetura ..
Pankaj Parkar
e se não quisermos que o botão voltar funcione com isso? Quer dizer, em volta de imprensa ir para o estado anterior / url, não para o mesmo estado, com diferentes parâmetros
gaurav5430
Eu acho que, por esta volta navegador solução não funcionaria, como estamos mudando um estado sem deixar que sabe sobre ele para ui-router
Pankaj Parkar
2
Eu tentei isso, e parece que $onInit()é chamado no meu componente toda vez que eu uso o $state.go. Não parece 100% ok para mim.
Carrm
8

Chamando

$state.go($state.current, {myParam: newValue}, {notify: false});

ainda recarregará o controlador.

Para evitá-lo, você deve declarar o parâmetro como dinâmico:

$stateProvider.state({
    name: 'myState',
    url: '/my_state?myParam',
    params: {
        myParam: {
          dynamic: true,
        }
    },
    ...
});

Então você nem precisa do notify, apenas ligando

$state.go($state.current, {myParam: newValue})

é suficiente. Neato!

A partir da documentação :

Quando dynamicfor true, alterações no valor do parâmetro não farão com que o estado seja inserido / encerrado. As resoluções não serão buscadas novamente, nem as visualizações serão recarregadas.

[...]

Isso pode ser útil para criar a interface do usuário em que o componente se atualiza quando os valores dos parâmetros mudam.

Moritz Ringler
fonte
7

Esta configuração resolveu os seguintes problemas para mim:

  • O controlador de treinamento não é chamado duas vezes ao atualizar o URL de .../para.../123
  • O controlador de treinamento não está sendo chamado novamente ao navegar para outro estado

Configuração do estado

state('training', {
    abstract: true,
    url: '/training',
    templateUrl: 'partials/training.html',
    controller: 'TrainingController'
}).
state('training.edit', {
    url: '/:trainingId'
}).
state('training.new', {
    url: '/{trainingId}',
    // Optional Parameter
    params: {
        trainingId: null
    }
})

Chamando os estados (de qualquer outro controlador)

$scope.editTraining = function (training) {
    $state.go('training.edit', { trainingId: training.id });
};

$scope.newTraining = function () {
    $state.go('training.new', { });
};

Controlador de treinamento

var newTraining;

if (!!!$state.params.trainingId) {

    // new      

    newTraining = // create new training ...

    // Update the URL without reloading the controller
    $state.go('training.edit',
        {
            trainingId : newTraining.id
        },
        {
            location: 'replace', //  update url and replace
            inherit: false,
            notify: false
        });     

} else {

    // edit

    // load existing training ...
}   
martinoss
fonte
Eu estava tentando usar uma estratégia semelhante, mas meu controlador nunca obtém o valor de trainigId na página de edição, algo que eu possa estar perdendo lá? Tentei acessar a página de edição diretamente do URL e usando ui-sref. Meu código é exatamente igual ao seu.
Nikhil Bhandari
Isso funcionou para mim, e é de longe a solução mais clara
OoDeLally
3

Se você precisar alterar apenas o URL, mas evitar o estado de alteração:

Altere o local com (adicione .replace se você deseja substituir no histórico):

this.$location.path([Your path]).replace();

Impedir o redirecionamento para o seu estado:

$transitions.onBefore({}, function($transition$) {
 if ($transition$.$to().name === '[state name]') {
   return false;
 }
});
egor.xyz
fonte
2

Eu fiz isso, mas há muito tempo na versão: v0.2.10 do roteador da interface do usuário como algo assim:

$stateProvider
  .state(
    'home', {
      url: '/home',
      views: {
        '': {
          templateUrl: Url.resolveTemplateUrl('shared/partial/main.html'),
          controller: 'mainCtrl'
        },
      }
    })
  .state('home.login', {
    url: '/login',
    templateUrl: Url.resolveTemplateUrl('authentication/partial/login.html'),
    controller: 'authenticationCtrl'
  })
  .state('home.logout', {
    url: '/logout/:state',
    controller: 'authenticationCtrl'
  })
  .state('home.reservationChart', {
    url: '/reservations/?vw',
    views: {
      '': {
        templateUrl: Url.resolveTemplateUrl('reservationChart/partial/reservationChartContainer.html'),
        controller: 'reservationChartCtrl',
        reloadOnSearch: false
      },
      '[email protected]': {
        templateUrl: Url.resolveTemplateUrl('voucher/partial/viewVoucherContainer.html'),
        controller: 'viewVoucherCtrl',
        reloadOnSearch: false
      },
      '[email protected]': {
        templateUrl: Url.resolveTemplateUrl('voucher/partial/voucherContainer.html'),
        controller: 'voucherCtrl',
        reloadOnSearch: false
      }
    },
    reloadOnSearch: false
  })
Sheelpriy
fonte
0

Tente algo como isto

$state.go($state.$current.name, {... $state.params, 'key': newValue}, {notify: false})
Eyad Farra
fonte
-6

Eu não acho que você precise de ui-router para isso. A documentação disponível para o serviço $ location diz no primeiro parágrafo: "... as alterações em $ location são refletidas na barra de endereços do navegador". Mais tarde, ele continua dizendo: "O que não faz? Não causa o recarregamento de uma página inteira quando o URL do navegador é alterado".

Portanto, com isso em mente, por que não simplesmente alterar o $ location.path (como o método é um getter e um setter) com algo como o seguinte:

var newPath = IdFromService;
$location.path(newPath);

A documentação observa que o caminho deve sempre começar com uma barra, mas isso será adicionado se estiver faltando.

Xaniff
fonte
Quando eu uso ui-routere uso $location.path(URL_PATH), a página é renderizada automaticamente ?!
Kousha
sim, é renderizado novamente. Tentei com event.preventDefault () em $ locationChangeStart, que também não funciona - ou seja, interrompe a renderização do estado, mas também impede a atualização do URL.
21714