AngularJS UI Router - cambia l'URL senza ricaricare lo stato


134

Attualmente il nostro progetto utilizza l'impostazione predefinita $routeProvidere sto utilizzando questo "hack" per cambiare urlsenza ricaricare la pagina:

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 dentro controller

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

Sto pensando di sostituire routeProvidercon ui-routerle rotte di nidificazione, ma non riesco a trovarlo ui-router.

È possibile - fare lo stesso con angular-ui-router?

Perché ne ho bisogno? Permettetemi di spiegare con un esempio: il
percorso per la creazione di una nuova categoria è /category/new dopo clickingSALVA Mostra success-alerte voglio cambiare il percorso /category/newin /caterogy/23(23 - è l'id del nuovo elemento memorizzato in db)


1
in ui-router non devi definire un URL per ogni stato, puoi navigare da uno stato all'altro senza cambiare URL
Jonathan de M.

vuoi aggiornare l'intero URL o solo il percorso di ricerca? Stavo cercando una soluzione che aggiornava il percorso di ricerca e l'ho trovata qui: stackoverflow.com/questions/21425378/…
Florian Loch,

@johnathan Davvero? Mi piacerebbe farlo, mostrando solo un singolo URL, ma $urlRouterProvider.otherwisesembra funzionare su un URL, non su uno stato. Hmmm, forse potrei vivere con 2 URL o trovare un altro modo per indicare che si tratta di un URL non valido.
Mawg dice di ripristinare Monica il

Risposte:


164

Semplicemente puoi usare $state.transitionTo invece di $state.go . $state.go chiama $state.transitionTo internamente ma imposta automaticamente le opzioni su { location: true, inherit: true, relative: $state.$current, notify: true } . Puoi chiamare $state.transitionTo e impostare notify: false . Per esempio:

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

può essere sostituito da

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

Modifica: come suggerito da Fracz può essere semplicemente:

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

16
Non è "mantenere l'URL quando si ricarica lo stato" invece di "cambiare l'URL senza ricaricare lo stato"?
Peter Hedberg,

25
per me ha funzionato con il seguente: $state.transitionTo('.detail', {id: newId}, { location: true, inherit: true, relative: $state.$current, notify: false }) quindi in pratica imposta notifica su falso e posizione su vero
Arjen de Vries

6
@ArjendeVries Sì, funziona come previsto ma ho riscontrato un comportamento inaspettato. Dopo aver giocato con molte transizioni Per chiamare il metodo (senza ricaricare) quando finalmente ti sposti in un nuovo stato (es. Url), riavvia il vecchio controller.
Premchandra Singh, il

2
@Premchandra Singh: avere lo stesso problema qui. All'uscita dallo stato, il vecchio controller viene inizializzato nuovamente. Ottengo lo stesso problema con la soluzione accettata da wiherek. Vedi anche qui github.com/angular-ui/ui-router/issues/64
martinoss,

15
Ancora più semplice: $state.go('.detail', {id: newId}, {notify: false}).
fracz,

48

Ok, risolto :) Il router dell'interfaccia utente angolare ha questo nuovo metodo, $ urlRouterProvider.deferIntercept () https://github.com/angular-ui/ui-router/issues/64

fondamentalmente si riduce a questo:

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

Penso che questo metodo sia attualmente incluso solo nella versione principale del router angolare dell'interfaccia utente, quella con parametri opzionali (che sono anche belli, a proposito). Deve essere clonato e costruito da sorgente con

grunt build

I documenti sono accessibili anche dalla fonte, attraverso

grunt ngdocs

(vengono incorporati nella directory / site) // maggiori informazioni in README.MD

Sembra esserci un altro modo per farlo, tramite parametri dinamici (che non ho usato). Molti crediti a nateabele.


Come sidenote, ecco i parametri opzionali nel $ stateProvider del router angolare dell'interfaccia utente, che ho usato in combinazione con quanto sopra:

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

}]);

ciò che fa è che consente di risolvere uno stato, anche se manca uno dei parametri. SEO è uno scopo, leggibilità un altro.

Nell'esempio sopra, volevo che doorsSingle fosse un parametro obbligatorio. Non è chiaro come definirli. Funziona bene con più parametri opzionali, quindi non è un problema. La discussione è qui https://github.com/angular-ui/ui-router/pull/1032#issuecomment-49196090


Sembra che i parametri opzionali non funzionino. Error: Both params and url specicified in state 'state'. Si dice nei documenti che anche questo è un utilizzo non valido. Un po 'deludente.
Rhys van der Waerden,

1
Hai costruito dal maestro? Si noti che nel momento in cui ho aggiunto la soluzione, i parametri opzionali sono stati inclusi solo nella versione che doveva essere compilata manualmente dalla fonte. questo non sarà incluso nella versione fino a quando la versione.0.0 non sarà disponibile.
wiherek,

1
Solo una breve nota. Grazie a nateabele i parametri opzionali sono disponibili in v0.2.11 che è stato appena rilasciato pochi giorni fa.
rich97

questo è molto confuso: come posso usare $ urlRouterProvider.deferIntercept (); in modo da poter aggiornare i parametri senza ricaricare il controller? Questo non me lo mostra davvero. All'interno della funzione di esecuzione è possibile valutare un'istruzione if da sincronizzare o non sincronizzare, ma tutto ciò che devo lavorare è l'URL vecchio e nuovo. Come faccio a sapere che questa è un'istanza in cui tutto ciò che voglio fare è aggiornare i parametri con solo due URL con cui lavorare? La logica sarebbe .... (se il vecchio stato e il nuovo stato sono uguali, non ricaricare il controller?) Sono confuso.
btm1

giusto, penso che questo caso d'uso fosse con stati nidificati, non volevo ricaricare lo stato figlio, quindi intercettavo. Penso che ora preferirei farlo con viste di targeting assolute e definire la vista sullo stato che so non cambierebbe. Comunque, questo è ancora buono. Ottieni URL completi, ovvero puoi indovinare lo stato dall'URL. E anche i parametri di query e percorso, ecc. Se crei la tua app in base a stati e URL, questa è una grande quantità di informazioni. Nel blocco di esecuzione puoi anche accedere a servizi ecc. Risponde alla tua domanda?
Con l'

16

Dopo aver dedicato molto tempo a questo problema, ecco cosa mi ha permesso di lavorare

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

Le altre soluzioni sono correlate al provider di route. Questa soluzione funziona per il caso, come il mio, in cui sto usando $ stateProvider e non sto usando $ routeProvider.
eeejay,

@eeejay fondamentalmente la domanda è stato chiesto per il ui-routersolo, come si può dire che, altre soluzioni sono state lavorando per $routerProvider, $routeProvidere $stateProvidersono totalmente diverse in architettura ..
Pankaj Parkar

cosa succede se non vogliamo che il pulsante Indietro funzioni con questo? Voglio dire, premendo il tasto back vai allo stato / url precedente, non allo stesso stato con parametri diversi
gaurav5430

Immagino che con questa soluzione il browser back non funzionerebbe, dato che stiamo cambiando uno stato senza informarlo su ui-router
Pankaj Parkar

2
Ho provato questo, e sembra che $onInit()venga chiamato sul mio componente ogni volta che uso il $state.go. Non mi sembra al 100% ok.
Carrm,

8

chiamata

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

ricaricherà comunque il controller.

Per evitarlo, devi dichiarare il parametro come dinamico:

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

Quindi non hai nemmeno bisogno del notify, solo chiamando

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

basta. Neato!

Dalla documentazione :

Quando lo dynamicè true, le modifiche al valore del parametro non causano l'inserimento / l'uscita dello stato. Le risoluzioni non verranno recuperate, né verranno ricaricate le viste.

[...]

Ciò può essere utile per creare l'interfaccia utente in cui il componente si aggiorna automaticamente quando cambiano i valori dei parametri.


7

Questa configurazione ha risolto i seguenti problemi per me:

  • Il controller di allenamento non viene chiamato due volte quando si aggiorna l'URL da .../a.../123
  • Il controller di allenamento non viene più richiamato durante la navigazione verso un altro stato

Configurazione dello stato

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

Invocare gli stati (da qualsiasi altro controller)

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

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

Controller di allenamento

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 ...
}   

Stavo cercando di utilizzare una strategia simile ma il mio controller non ottiene mai il valore di trainigId dalla pagina di modifica, qualcosa che mi potrebbe mancare lì? Ho provato ad accedere alla pagina di modifica direttamente dall'URL e utilizzando ui-sref. Il mio codice è esattamente come il tuo.
Nikhil Bhandari,

Questo ha funzionato per me ed è di gran lunga la soluzione più chiara
OoDeLally,

3

Se hai solo bisogno di cambiare l'URL ma impedisci di cambiare stato:

Cambia posizione con (aggiungi .replace se vuoi sostituire nella cronologia):

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

Impedisci il reindirizzamento al tuo stato:

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

2

l'ho fatto ma molto tempo fa nella versione: v0.2.10 di UI-router come qualcosa del genere ::

$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
      },
      'viewVoucher@home.reservationChart': {
        templateUrl: Url.resolveTemplateUrl('voucher/partial/viewVoucherContainer.html'),
        controller: 'viewVoucherCtrl',
        reloadOnSearch: false
      },
      'addEditVoucher@home.reservationChart': {
        templateUrl: Url.resolveTemplateUrl('voucher/partial/voucherContainer.html'),
        controller: 'voucherCtrl',
        reloadOnSearch: false
      }
    },
    reloadOnSearch: false
  })

0

Prova qualcosa del genere

$state.go($state.$current.name, {... $state.params, 'key': newValue}, {notify: false})

-6

Non credo che tu abbia bisogno dell'interfaccia utente per questo. La documentazione disponibile per il servizio $ location dice nel primo paragrafo "... le modifiche a $ location si riflettono nella barra degli indirizzi del browser". In seguito continua a dire: "Cosa non fa? Non provoca il ricaricamento di una pagina intera quando viene modificato l'URL del browser."

Quindi, con questo in mente, perché non cambiare semplicemente $ location.path (poiché il metodo è sia getter che setter) con qualcosa di simile al seguente:

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

La documentazione osserva che il percorso dovrebbe sempre iniziare con una barra, ma questo verrà aggiunto se manca.


Quando utilizzo ui-routere utilizzo $location.path(URL_PATH), la pagina viene nuovamente riprodotta automaticamente ?!
Kousha,

sì, si esegue nuovamente il rendering. Ho provato con event.preventDefault () su $ locationChangeStart, che non funziona neanche - cioè impedisce allo stato di ripetere il rendering, ma impedisce anche l'aggiornamento dell'URL.
wiherek,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.