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)


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

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


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

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.


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

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

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
      },
      '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

Spróbuj czegoś takiego

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

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


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.