AngularJS UI Router - zmień adres URL bez stanu przeładowania

134

Obecnie nasz projekt używa default $routeProvider, a ja używam tego "hacka", aby zmienić urlbez przeładowywania strony:

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

i w controller

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

Myślę o wymianie routeProviderz ui-routerdo gniazdowania tras, ale nie mogę znaleźć to w ui-router.

Czy to możliwe - zrób to samo z angular-ui-router?

Po co mi to? Pozwól, że wyjaśnię na przykładzie:
Trasa do tworzenia nowej kategorii jest /category/new po clickingSAVE Pokazuję success-alerti chcę zmienić trasę /category/newna /caterogy/23(23 - to identyfikator nowego elementu przechowywanego w db)

vuliad
źródło
1
w ui-routerze nie musisz definiować adresu URL dla każdego stanu, możesz przechodzić od stanu do stanu bez zmiany adresu URL
Jonathan de M.
chcesz zaktualizować cały adres URL czy tylko ścieżkę wyszukiwania? Szukałem rozwiązania aktualizującego ścieżkę wyszukiwania i znalazłem je tutaj: stackoverflow.com/questions/21425378/ ...
Florian Loch
@johnathan Naprawdę? Chciałbym to zrobić, pokazując tylko jeden adres URL, ale $urlRouterProvider.otherwisewydaje się, że działa na adresie URL, a nie na stanie. Hmmm, może mógłbym żyć z dwoma adresami URL lub znaleźć inny sposób wskazania, że ​​jest to nieprawidłowy adres URL.
Mawg mówi, że przywróć Monikę

Odpowiedzi:

164

Po prostu możesz użyć $state.transitionTo zamiast $state.go . $state.go wywołuje $state.transitionTo wewnętrznie, ale automatycznie ustawia opcje na { location: true, inherit: true, relative: $state.$current, notify: true } . Możesz zadzwonić $state.transitionTo i ustawić notify: false . Na przykład:

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

można zastąpić

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

Edycja: zgodnie z sugestią fracza może to być po prostu:

$state.go('.detail', {id: newId}, {notify: false}) 
rezKesh
źródło
16
Czy nie jest to „zachowaj adres URL podczas ponownego ładowania stanu” zamiast „zmień adres URL bez ponownego ładowania stanu”?
Peter Hedberg
25
dla mnie działało z następującymi $state.transitionTo('.detail', {id: newId}, { location: true, inherit: true, relative: $state.$current, notify: false }) elementami : więc w zasadzie ustaw powiadomienie na fałsz i lokalizację na prawda
Arjen de Vries
6
@ArjendeVries Tak, działa zgodnie z oczekiwaniami, ale znalazłem nieoczekiwane zachowanie. Po zabawie z wieloma wywołaniami metody transitTo (bez przeładowywania), kiedy w końcu przechodzisz do nowego stanu (np. Url), ponownie inicjuje stary kontroler.
Premchandra Singh
2
@Premchandra Singh: Mając tutaj ten sam problem. Opuszczając ten stan, stary kontroler zostaje ponownie zainicjowany. Ten sam problem pojawia się w przypadku zaakceptowanego rozwiązania firmy Wiherek. Zobacz także tutaj github.com/angular-ui/ui-router/issues/64
martinoss
15
Jeszcze prostsze: $state.go('.detail', {id: newId}, {notify: false}).
fracz
48

OK, rozwiązane :) Angular UI Router ma tę nową metodę, $ urlRouterProvider.deferIntercept () https://github.com/angular-ui/ui-router/issues/64

w zasadzie sprowadza się do tego:

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

Myślę, że ta metoda jest obecnie dostępna tylko w wersji głównej routera kątowego ui, tej z opcjonalnymi parametrami (które też są fajne, przy okazji). Musi zostać sklonowany i zbudowany ze źródła z

grunt build

Dokumenty są również dostępne ze źródła za pośrednictwem

grunt ngdocs

(są wbudowane w katalog / site) // więcej informacji w README.MD

Wydaje się, że jest na to inny sposób, poprzez parametry dynamiczne (których nie używałem). Wiele kredytów dla nateabele.


Na marginesie, oto opcjonalne parametry w $ stateProvider Angular UI Routera, których użyłem w połączeniu z powyższym:

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

}]);

co to robi, to pozwala rozwiązać stan, nawet jeśli brakuje jednego z parametrów. SEO to jeden cel, czytelność inny.

W powyższym przykładzie chciałem, aby parametr doorSingle był wymaganym parametrem. Nie jest jasne, jak je zdefiniować. Działa jednak dobrze z wieloma opcjonalnymi parametrami, więc nie stanowi to problemu. Dyskusja jest tutaj https://github.com/angular-ui/ui-router/pull/1032#issuecomment-49196090

wiherek
źródło
Wygląda na to, że twoje opcjonalne parametry nie działają. Error: Both params and url specicified in state 'state'. W dokumentach jest napisane, że jest to również nieprawidłowe użycie. Trochę rozczarowujący.
Rhys van der Waerden
1
Budowałeś od mistrza? Zwróć uwagę, że w momencie dodawania rozwiązania parametry opcjonalne były zawarte tylko w wersji, która musiała zostać zbudowana ręcznie ze źródła. nie zostanie to uwzględnione w wydaniu, dopóki nie zostanie wydana wersja 0.3.
wiherek
1
Tylko krótka notatka. Dzięki nateabele opcjonalne parametry są dostępne w wersji 0.2.11, która została właśnie wydana kilka dni temu.
rich97
jest to bardzo mylące: jak używać $ urlRouterProvider.deferIntercept (); żebym mógł zaktualizować parametry bez przeładowywania kontrolera? To naprawdę mi tego nie pokazuje. W ramach funkcji run możesz ocenić instrukcję if, aby zsynchronizować lub nie synchronizować, ale wszystko, z czym muszę pracować, to stary i nowy adres URL. Skąd mam wiedzieć, że jest to instancja, w której wszystko, co chcę zrobić, to zaktualizować parametry za pomocą tylko dwóch adresów URL do pracy? Czy logika byłaby taka ... (jeśli stary stan i nowy stan są takie same, nie ładuj ponownie kontrolera?) Jestem zdezorientowany.
btm1
racja, myślę, że ten przypadek użycia dotyczył stanów zagnieżdżonych, nie chciałem ponownie ładować stanu potomnego, więc przechwytywałem. Myślę, że teraz wolałbym to zrobić z absolutnie ukierunkowanymi poglądami i zdefiniowaniem poglądu na stan, o którym wiem, że się nie zmieni. Tak czy inaczej, to nadal jest dobre. Otrzymujesz pełne adresy URL, czyli możesz odgadnąć stan z adresu URL. A także parametry zapytania i ścieżki, itp. Jeśli tworzysz aplikację na podstawie stanów i adresów URL, jest to mnóstwo informacji. W bloku uruchamiania można również uzyskać dostęp do usług itp. Czy to odpowiada na Twoje pytanie?
wiherek
16

Po spędzeniu dużej ilości czasu z tym problemem, oto co mam w pracy

$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
źródło
Pozostałe rozwiązania dotyczą dostawcy tras. To rozwiązanie działa w przypadku, takim jak mój, w którym używam $ stateProvider, a nie $ routeProvider.
eeejay
@eeejay zasadzie pytanie został poproszony o ui-routertylko, jak można powiedzieć, że inne rozwiązania zostały pracuje $routerProvider, $routeProvideri $stateProvidersą zupełnie różne w architekturze ..
Pankaj Parkar
co jeśli nie chcemy, aby przycisk Wstecz z tym działał? Chodzi mi o to, że na wstecznym naciśnięciu przejdź do poprzedniego stanu /
adresu
Myślę, że przez to rozwiązanie przeglądarka nie działałaby, ponieważ zmieniamy stan bez powiadomienia o tym do ui-routera
Pankaj Parkar
2
Próbowałem tego i wygląda na to, że $onInit()mój komponent jest wywoływany za każdym razem, gdy używam $state.go. Nie wydaje mi się to w 100% w porządku.
Carrm,
9

Powołanie

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

nadal ładuje kontroler, co oznacza, że utracisz dane stanu .

Aby tego uniknąć, po prostu zadeklaruj parametr jako dynamiczny:

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

Wtedy nawet nie potrzebujesz notify, po prostu dzwonisz

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

wystarczy. Neato!

Z dokumentacji :

Gdy dynamicjest true, zmiany wartości parametru nie spowodują wejścia / wyjścia ze stanu. Rozwiązania nie zostaną ponownie pobrane, ani widoki nie zostaną ponownie załadowane.

Może to być przydatne do tworzenia interfejsu użytkownika, w którym składnik aktualizuje się sam, gdy zmieniają się wartości parametrów.

Moritz Ringler
źródło
to jak dotąd najlepsze rozwiązanie! rekwizyty !!!!!
Patrik Laszlo
7

Ta konfiguracja rozwiązała dla mnie następujące problemy:

  • Kontroler szkoleniowy nie jest wywoływany dwukrotnie podczas aktualizowania adresu URL z .../do.../123
  • Kontroler szkoleniowy nie jest ponownie wywoływany podczas przechodzenia do innego stanu

Konfiguracja stanu

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

Wywołanie stanów (z dowolnego innego kontrolera)

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

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

Kontroler szkolenia

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
źródło
Próbowałem zastosować podobną strategię, ale mój kontroler nigdy nie pobiera wartości trainigId ze strony edycji, czy czegoś tam może mi brakować? Próbowałem uzyskać dostęp do strony edycji bezpośrednio z adresu URL i za pomocą ui-sref. Mój kod jest dokładnie taki sam jak Twój.
Nikhil Bhandari
To zadziałało dla mnie i jest zdecydowanie najbardziej przejrzystym rozwiązaniem
OoDeLally
3

Jeśli potrzebujesz tylko zmienić adres URL, ale nie chcesz zmienić stanu:

Zmień lokalizację za pomocą (dodaj .replace, jeśli chcesz zastąpić w historii):

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

Zapobiegaj przekierowaniu do swojego stanu:

$transitions.onBefore({}, function($transition$) {
 if ($transition$.$to().name === '[state name]') {
   return false;
 }
});
egor.xyz
źródło
2

zrobiłem to, ale dawno temu w wersji: v0.2.10 UI-routera coś takiego:

$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
źródło
0

Spróbuj czegoś takiego

$state.go($state.$current.name, {... $state.params, 'key': newValue}, {notify: false})
Eyad Farra
źródło
-6

Myślę, że wcale nie potrzebujesz do tego routera ui. Dokumentacja dostępna dla usługi $ location mówi w pierwszym akapicie: „... zmiany dotyczące $ location są odzwierciedlane na pasku adresu przeglądarki”. W dalszej części artykułu można przeczytać: „Czego to nie robi? Nie powoduje ponownego załadowania całej strony po zmianie adresu URL przeglądarki”.

Mając to na uwadze, dlaczego nie zmienić po prostu $ location.path (ponieważ metoda jest zarówno pobierającym, jak i ustawiającym) na coś podobnego do następującego:

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

W dokumentacji zauważa, że droga powinna zawsze zaczynać się od ukośnika, ale będzie go dodać jeśli brakuje.

Xaniff
źródło
Kiedy używam ui-routeri używam $location.path(URL_PATH), strona automatycznie renderuje się ponownie ?!
Kousha,
tak, renderuje się ponownie. Próbowałem z event.preventDefault () na $ locationChangeStart, to też nie działa - tj. Zatrzymuje stan przed ponownym renderowaniem, ale także zapobiega aktualizacji adresu URL.
wiherek