AngularJS UI Router - URL ändern, ohne den Status neu zu laden


134

Derzeit verwendet unser Projekt die Standardeinstellung $routeProvider, und ich verwende diesen "Hack", um Änderungen urlvorzunehmen, ohne die Seite neu zu laden:

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

und in controller

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

Ich denke dabei an Ersatz routeProvidermit ui-routerfür die Verschachtelung Strecken, kann aber nicht mit diesen in finden ui-router.

Ist es möglich - machen Sie dasselbe mit angular-ui-router?

Warum brauche ich das? Lassen Sie mich anhand eines Beispiels erklären: Die
Route zum Erstellen einer neuen Kategorie ist /category/new nach clickingSAVE, die ich zeige, success-alertund ich möchte die Route ändern /category/newzu /caterogy/23(23 - ist die ID des neuen Elements, das in db gespeichert ist).


1
In UI-Router müssen Sie nicht für jeden Status eine URL definieren. Sie können von Status zu Status navigieren, ohne die URL zu ändern
Jonathan de M.

Möchten Sie die gesamte URL oder nur den Suchpfad aktualisieren? Ich suchte nach einer Lösung, die den Suchpfad aktualisiert, und fand sie hier: stackoverflow.com/questions/21425378/…
Florian Loch

@ Johnathan Wirklich? Ich würde das gerne tun und nur eine einzige URL anzeigen, aber es $urlRouterProvider.otherwisescheint, als würde eine URL verwendet, kein Status. Hmmm, vielleicht könnte ich mit 2 URLs leben oder einen anderen Weg finden, um anzuzeigen, dass es sich um eine ungültige URL handelt.
Mawg sagt, Monica

Antworten:


164

Einfach können Sie $state.transitionTo anstelle von verwenden $state.go . $state.go ruft $state.transitionTo intern auf, setzt aber automatisch Optionen auf { location: true, inherit: true, relative: $state.$current, notify: true } . Sie können anrufen $state.transitionTo und einstellen notify: false . Beispielsweise:

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

kann ersetzt werden durch

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

Edit: Wie von fracz vorgeschlagen, kann es einfach sein:

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

16
Ist das nicht "URL beim erneuten Laden beibehalten" statt "URL ändern, ohne den Status neu zu laden"?
Peter Hedberg

25
für mich funktionierte es mit folgendem: $state.transitionTo('.detail', {id: newId}, { location: true, inherit: true, relative: $state.$current, notify: false }) also im Grunde genommen notify auf false und location auf true setzen
Arjen de Vries

6
@ArjendeVries Ja, es funktioniert wie erwartet, aber ich habe ein unerwartetes Verhalten festgestellt. Nach dem Herumspielen mit vielen TransitionTo-Methodenaufrufen (ohne erneutes Laden), wenn Sie schließlich in einen neuen Status (z. B. URL) wechseln, wird der alte Controller erneut initiiert.
Premchandra Singh

2
@Premchandra Singh: Das gleiche Problem hier haben. Beim Verlassen des Status wird der alte Controller erneut initialisiert. Ich habe das gleiche Problem mit der akzeptierten Lösung von wiherek. Siehe auch hier github.com/angular-ui/ui-router/issues/64
martinoss

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

48

Ok, gelöst :) Angular UI Router hat diese neue Methode, $ urlRouterProvider.deferIntercept () https://github.com/angular-ui/ui-router/issues/64

Im Grunde kommt es darauf an:

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

Ich denke, diese Methode ist derzeit nur in der Master- Version des eckigen UI-Routers enthalten, der mit optionalen Parametern (die übrigens auch nett sind). Es muss geklont und von der Quelle mit erstellt werden

grunt build

Auf die Dokumente kann auch von der Quelle aus zugegriffen werden

grunt ngdocs

(Sie werden in das Verzeichnis / site integriert.) // Weitere Informationen in README.MD

Es scheint einen anderen Weg zu geben, dies durch dynamische Parameter (die ich nicht verwendet habe) zu tun . Viele Kredite an Nateabele.


Als Nebenbemerkung sind hier optionale Parameter im $ stateProvider von Angular UI Router aufgeführt, die ich in Kombination mit den oben genannten verwendet habe:

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

}]);

Dadurch kann ein Status aufgelöst werden, auch wenn einer der Parameter fehlt. SEO ist ein Zweck, Lesbarkeit ein anderer.

Im obigen Beispiel wollte ich, dass doorSingle ein erforderlicher Parameter ist. Es ist nicht klar, wie diese definiert werden sollen. Es funktioniert gut mit mehreren optionalen Parametern, also kein wirkliches Problem. Die Diskussion ist hier https://github.com/angular-ui/ui-router/pull/1032#issuecomment-49196090


Es scheint, dass Ihre optionalen Parameter nicht funktionieren. Error: Both params and url specicified in state 'state'. In den Dokumenten heißt es, dass dies auch eine ungültige Verwendung ist. Ein bisschen enttäuschend.
Rhys van der Waerden

1
Hast du vom Meister gebaut? Bitte beachten Sie, dass zu dem Zeitpunkt, als ich die Lösung hinzufügte, die optionalen Parameter nur in der Version enthalten waren, die manuell aus dem Quellcode erstellt werden musste. Dies wird nicht in der Version enthalten sein, bis v.0.3 veröffentlicht ist.
wiherek

1
Nur eine schnelle Anmerkung. Dank nateabele sind die optionalen Parameter in Version 0.2.11 verfügbar, die erst vor einigen Tagen veröffentlicht wurde.
rich97

Das ist sehr verwirrend: Wie verwende ich $ urlRouterProvider.deferIntercept (); damit ich die params aktualisieren kann, ohne den controller neu zu laden? Das zeigt mir das nicht wirklich. Innerhalb der Ausführungsfunktion können Sie eine if-Anweisung auswerten, um sie entweder zu synchronisieren oder nicht zu synchronisieren, aber alles, mit dem ich arbeiten muss, ist die alte und die neue URL. Woher weiß ich, dass dies eine Instanz ist, in der ich nur die Parameter mit nur zwei URLs aktualisieren möchte, mit denen ich arbeiten kann? Wäre die Logik ... (wenn der alte und der neue Zustand gleich sind, laden Sie den Controller nicht neu?) Ich bin verwirrt.
BTM1

Richtig, ich denke, dieser Anwendungsfall war mit verschachtelten Zuständen, ich wollte den untergeordneten Zustand nicht neu laden, also wurde abgefangen. Ich denke, jetzt würde ich das lieber mit absoluten Zielansichten tun und die Ansicht über den Zustand definieren, von dem ich weiß, dass er sich nicht ändern würde. Jedenfalls ist das immer noch gut. Sie erhalten vollständige URLs, dh Sie können den Status anhand der URL erraten. Und auch die Abfrage- und Pfadparameter usw. Wenn Sie Ihre App um Zustände und URLs herum erstellen, ist dies eine Menge Informationen. Im Ausführungsblock können Sie auch auf Dienste usw. zugreifen. Beantwortet das Ihre Frage?
wiherek

16

Nachdem ich viel Zeit mit diesem Thema verbracht habe, habe ich Folgendes getan

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

Die anderen Lösungen beziehen sich auf den Routenanbieter. Diese Lösung funktioniert für den Fall wie meinen, in dem ich $ stateProvider und nicht $ routeProvider verwende.
Eeejay

@eeejay im Grunde wurde die Frage ui-routernur gestellt, wie kann man das sagen, andere Lösungen haben funktioniert $routerProvider, $routeProvider&$stateProvider ist völlig verschieden in der Architektur ..
Pankaj Parkar

Was ist, wenn wir nicht möchten, dass der Zurück-Button damit funktioniert? Ich meine, auf Zurück drücken gehe zum vorherigen Status / URL, nicht zum gleichen Status mit verschiedenen Parametern
gaurav5430

Ich denke, mit dieser Lösung würde der Browser zurück nicht funktionieren, da wir einen Status ändern, ohne es dem
UI

2
Ich habe es versucht und es sieht so aus, $onInit()als würde meine Komponente jedes Mal aufgerufen, wenn ich das verwende $state.go. Scheint mir nicht 100% in Ordnung zu sein.
Carrm

8

Berufung

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

lädt den Controller weiterhin neu.

Um dies zu vermeiden, müssen Sie den Parameter als dynamisch deklarieren:

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

Dann brauchen Sie nicht einmal das notify, nur anrufen

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

genügt. Neato!

Aus der Dokumentation :

Wenn dies der Fall dynamicist true, führen Änderungen am Parameterwert nicht dazu, dass der Status eingegeben / beendet wird. Die Auflösungen werden nicht erneut abgerufen und die Ansichten werden nicht neu geladen.

[...]

Dies kann nützlich sein, um eine Benutzeroberfläche zu erstellen, in der sich die Komponente selbst aktualisiert, wenn sich die Parameterwerte ändern.


7

Dieses Setup löste folgende Probleme für mich:

  • Der Trainingscontroller wird beim Aktualisieren der URL von .../bis nicht zweimal aufgerufen.../123
  • Der Trainingscontroller wird beim Navigieren in einen anderen Status nicht erneut aufgerufen

Statuskonfiguration

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

Aufrufen der Status (von einem anderen Controller)

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

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

Schulungsleiter

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

Ich habe versucht, eine ähnliche Strategie zu verwenden, aber mein Controller erhält nie den Wert von trainigId von der Bearbeitungsseite. Gibt es etwas, das mir dort möglicherweise fehlt? Ich habe versucht, direkt über die URL und mit ui-sref auf die Bearbeitungsseite zuzugreifen. Mein Code ist genau wie dein.
Nikhil Bhandari

Dies hat bei mir funktioniert und ist bei weitem die
klarste

3

Wenn Sie nur die URL ändern müssen, aber den Änderungsstatus verhindern möchten:

Ändern Sie den Speicherort mit (fügen Sie .replace hinzu, wenn Sie im Verlauf ersetzen möchten):

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

Verhindern Sie die Weiterleitung in Ihren Bundesstaat:

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

2

Ich habe dies aber vor langer Zeit in Version: v0.2.10 von UI-Router wie so etwas getan ::

$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

Versuchen Sie so etwas

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

-6

Ich glaube nicht, dass Sie dafür einen UI-Router brauchen. Die für den $ location-Service verfügbare Dokumentation heißt es im ersten Absatz: "... Änderungen an $ location werden in der Adressleiste des Browsers angezeigt." Später heißt es weiter: "Was macht es nicht? Es verursacht kein erneutes Laden der gesamten Seite, wenn die Browser-URL geändert wird."

In diesem Sinne sollten Sie den $ location.path (da die Methode sowohl ein Getter als auch ein Setter ist) einfach wie folgt ändern:

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

In der Dokumentation wird darauf hingewiesen, dass der Pfad immer mit einem Schrägstrich beginnen sollte, der jedoch hinzugefügt wird, wenn er fehlt.


Wenn ich die Seite verwende ui-routerund verwende $location.path(URL_PATH), wird sie automatisch neu gerendert?!
Kousha

Ja, es wird neu gerendert. Ich habe mit event.preventDefault () auf $ locationChangeStart versucht, dass dies auch nicht funktioniert - dh es verhindert, dass der Status erneut gerendert wird, verhindert aber auch, dass die URL aktualisiert wird.
wiherek
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.