AngularJS UI Router - เปลี่ยน url โดยไม่ต้องโหลดซ้ำ


134

ขณะนี้โครงการของเราใช้ค่าเริ่มต้น$routeProviderและฉันใช้ "แฮ็ก" นี้เพื่อเปลี่ยนurlโดยไม่ต้องโหลดหน้าซ้ำ:

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

และใน controller

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

ผมคิดว่าการเปลี่ยนrouteProviderกับui-routerเส้นทางทำรัง ui-routerแต่ลาดเทพบนี้

เป็นไปได้ - ทำเช่นเดียวกันกับangular-ui-router?

ทำไมฉันถึงต้องการสิ่งนี้? ให้ฉันอธิบายด้วยตัวอย่าง:
เส้นทางสำหรับการสร้างหมวดหมู่ใหม่อยู่/category/new หลังจากclickingใน SAVE ฉันแสดงsuccess-alertและฉันต้องการเปลี่ยนเส้นทาง/category/newเป็น/caterogy/23(23 - เป็น id ของรายการใหม่ที่จัดเก็บในฐานข้อมูล)


1
ใน ui-router คุณไม่ต้องกำหนด URL สำหรับแต่ละรัฐคุณสามารถนำทางจากรัฐหนึ่งไปอีกรัฐหนึ่งโดยไม่ต้องเปลี่ยน URL
Jonathan de M.

คุณต้องการอัปเดต URL ทั้งหมดหรือเพียงแค่เส้นทางการค้นหา? ฉันกำลังค้นหาโซลูชันที่อัปเดตเส้นทางการค้นหาและพบที่นี่: stackoverflow.com/questions/21425378/…
Florian Loch

@johnathan จริงเหรอ? ฉันชอบที่จะทำเช่นนั้นโดยแสดงเพียง URL เดียว แต่$urlRouterProvider.otherwiseดูเหมือนว่าจะทำงานกับ URL ไม่ใช่สถานะ อืมบางทีฉันอาจใช้ URL 2 รายการหรือหาวิธีอื่นในการระบุว่าเป็น URL ที่ไม่ถูกต้อง
Mawg กล่าวว่าคืนสถานะ Monica

คำตอบ:


164

คุณสามารถใช้ $state.transitionTo แทน $state.go ไฟล์. $state.go โทร $state.transitionTo ภายใน { location: true, inherit: true, relative: $state.$current, notify: true } แต่จะตั้งค่าตัวเลือกในการ คุณสามารถโทรและการตั้งค่า $state.transitionTo notify: false ตัวอย่างเช่น:

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

สามารถแทนที่ได้ด้วย

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

แก้ไข: ตามที่แนะนำโดย fracz มันสามารถ:

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

16
"เก็บ url เมื่อโหลดสถานะ" แทน "เปลี่ยน URL โดยไม่โหลดสถานะ" ไม่ใช่หรือ
Peter Hedberg

25
สำหรับฉันมันใช้ได้กับสิ่งต่อไปนี้: $state.transitionTo('.detail', {id: newId}, { location: true, inherit: true, relative: $state.$current, notify: false }) โดยทั่วไปตั้งค่าการแจ้งเตือนเป็นเท็จและตำแหน่งเป็นจริง
Arjen de Vries

6
@ArjendeVries ใช่มันทำงานตามที่คาดไว้ แต่ฉันพบพฤติกรรมที่ไม่คาดคิด หลังจากเล่นกับการเรียกเมธอด transitionTo จำนวนมาก (โดยไม่ต้องโหลดซ้ำ) เมื่อคุณย้ายไปยังสถานะใหม่ (เช่น url) ในที่สุดมันจะเริ่มต้นคอนโทรลเลอร์เก่าอีกครั้ง
เปรมจันทราสิงห์

2
@Premchandra Singh: มีปัญหาเดียวกันที่นี่ เมื่อออกจากสถานะคอนโทรลเลอร์เก่าจะเริ่มต้นอีกครั้ง ฉันได้รับปัญหาเดียวกันกับวิธีแก้ปัญหาที่ได้รับการยอมรับจาก wiherek ดูได้ที่นี่github.com/angular-ui/ui-router/issues/64
martinoss

15
ง่ายยิ่งขึ้น: $state.go('.detail', {id: newId}, {notify: false}).
fracz

48

ตกลงแก้ไขแล้ว :) Angular UI Router มีวิธีการใหม่นี้ $ urlRouterProvider.deferIntercept () https://github.com/angular-ui/ui-router/issues/64

โดยพื้นฐานแล้วมันลงมาดังนี้:

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

ฉันคิดว่าวิธีนี้รวมอยู่ในเราเตอร์ ui เชิงมุมเวอร์ชันหลักเท่านั้นซึ่งเป็นวิธีที่มีพารามิเตอร์เสริม (ซึ่งก็ดีเช่นกัน btw) จำเป็นต้องโคลนและสร้างจากแหล่งที่มาด้วย

grunt build

สามารถเข้าถึงเอกสารได้จากแหล่งที่มาเช่นกันผ่าน

grunt ngdocs

(สร้างไว้ในไดเร็กทอรี / site) // ข้อมูลเพิ่มเติมใน README.MD

ดูเหมือนจะมีวิธีอื่นในการทำเช่นนี้โดยพารามิเตอร์แบบไดนามิก (ซึ่งฉันไม่ได้ใช้) เครดิตมากมายสำหรับ nateabele


ในฐานะ sidenote นี่คือพารามิเตอร์เสริมใน $ stateProvider ของ Angular UI Router ซึ่งฉันใช้ร่วมกับข้างต้น:

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

}]);

มันช่วยให้แก้ไขสถานะได้อย่างไรแม้ว่าพารามิเตอร์ตัวใดตัวหนึ่งจะหายไป SEO เป็นจุดประสงค์หนึ่งที่สามารถอ่านได้อีกอย่างหนึ่ง

ในตัวอย่างด้านบนฉันต้องการให้ doorsSingle เป็นพารามิเตอร์ที่จำเป็น ยังไม่ชัดเจนว่าจะกำหนดอย่างไร แม้ว่าจะใช้งานได้กับพารามิเตอร์ทางเลือกหลายตัวดังนั้นจึงไม่เป็นปัญหาจริงๆ การสนทนาอยู่ที่นี่https://github.com/angular-ui/ui-router/pull/1032#issuecomment-49196090


ดูเหมือนว่าพารามิเตอร์ทางเลือกของคุณจะไม่ทำงาน Error: Both params and url specicified in state 'state'. มันบอกในเอกสารว่านี่เป็นการใช้งานที่ไม่ถูกต้องด้วย น่าผิดหวังเล็กน้อย
Rhys van der Waerden

1
คุณสร้างจากผู้เชี่ยวชาญหรือไม่? โปรดทราบว่าเมื่อฉันเพิ่มโซลูชันพารามิเตอร์ที่เป็นทางเลือกจะรวมอยู่ในเวอร์ชันที่ต้องสร้างขึ้นด้วยตนเองจากแหล่งที่มาเท่านั้น สิ่งนี้จะไม่รวมอยู่ในรุ่นจนกว่า v.0.3 จะออก
wiherek

1
เพียงบันทึกสั้น ๆ ขอบคุณ nateabele พารามิเตอร์ทางเลือกมีอยู่ใน v0.2.11 ซึ่งเพิ่งเปิดตัวเมื่อไม่กี่วันที่ผ่านมา
rich97

สิ่งนี้สับสนมาก: ฉันจะใช้ $ urlRouterProvider.deferIntercept () ได้อย่างไร เพื่อให้ฉันสามารถอัปเดตพารามิเตอร์โดยไม่ต้องโหลดคอนโทรลเลอร์ใหม่? นี่ไม่ได้แสดงให้ฉันเห็นจริงๆ ภายในฟังก์ชัน run คุณสามารถประเมินคำสั่ง if ว่าจะซิงค์หรือไม่ซิงค์ แต่สิ่งที่ฉันต้องใช้คือ url เก่าและใหม่ ฉันจะรู้ได้อย่างไรว่านี่คืออินสแตนซ์ที่สิ่งที่ฉันต้องการทำคืออัปเดตพารามิเตอร์ที่มีเพียงสอง URL ที่จะใช้งานได้ ตรรกะจะเป็น .... (ถ้าสถานะเก่าและสถานะใหม่เหมือนกันอย่าโหลดคอนโทรลเลอร์ใหม่หรือไม่) ฉันสับสน
btm1

ใช่ฉันคิดว่ากรณีการใช้งานนี้มีสถานะซ้อนกันฉันไม่ต้องการโหลดสถานะลูกซ้ำดังนั้นจึงถูกดักฟัง ฉันคิดว่าตอนนี้ฉันอยากจะทำเช่นนั้นกับมุมมองการกำหนดเป้าหมายแบบสัมบูรณ์และกำหนดมุมมองเกี่ยวกับสถานะที่ฉันรู้ว่าจะไม่เปลี่ยนแปลง แค่นี้ก็ยังดี คุณได้รับ URL แบบเต็มนั่นคือคุณสามารถเดาสถานะได้จาก url และพารามิเตอร์คิวรีและพา ธ เป็นต้นหากคุณสร้างแอปของคุณตามสถานะและ URL นั่นคือข้อมูลมากมาย ในบล็อกการเรียกใช้คุณยังสามารถเข้าถึงบริการอื่น ๆ ได้ซึ่งตอบคำถามของคุณหรือไม่?
wiherek

16

หลังจากใช้เวลากับปัญหานี้มานานแล้วนี่คือสิ่งที่ฉันได้ผล

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

โซลูชันอื่น ๆ เกี่ยวข้องกับผู้ให้บริการเส้นทาง โซลูชันนี้ใช้ได้กับกรณีเช่นของฉันโดยที่ฉันใช้ $ stateProvider และไม่ใช้ $ routeProvider
eeejay

@eeejay พื้นคำถามที่ได้รับขอui-routerเพียงวิธีการที่คุณสามารถพูดได้ว่าการแก้ปัญหาอื่น ๆ ที่ได้ทำงานให้$routerProvider, $routeProviderและ$stateProviderมีความแตกต่างกันโดยสิ้นเชิงในงานสถาปัตยกรรม ..
Pankaj Parkar

จะเกิดอะไรขึ้นถ้าเราไม่ต้องการให้ปุ่มย้อนกลับทำงานกับสิ่งนี้? ฉันหมายถึงในการกดย้อนกลับไปที่สถานะ / url ก่อนหน้าไม่ใช่ไปยังสถานะเดียวกันโดยมี
พารามิเตอร์

ฉันเดาว่าโดยเบราว์เซอร์โซลูชันนี้จะไม่ทำงานเนื่องจากเรากำลังเปลี่ยนสถานะโดยไม่ต้องแจ้งให้ทราบเกี่ยวกับ ui-router
Pankaj Parkar

2
ฉันลองสิ่งนี้และดูเหมือนว่า$onInit()จะมีการเรียกส่วนประกอบของฉันทุกครั้งที่ใช้ไฟล์$state.go. ดูเหมือนจะไม่โอเคสำหรับฉัน 100%
Carrm

9

โทร

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

จะยังคงโหลดคอนโทรลเลอร์อีกครั้งซึ่งหมายความว่าคุณจะสูญเสียข้อมูลสถานะสูญเสียข้อมูลของรัฐ

เพื่อหลีกเลี่ยงสิ่งนี้เพียงแค่ประกาศพารามิเตอร์เป็นไดนามิก:

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

จากนั้นคุณไม่จำเป็นต้องใช้notifyเพียงแค่โทร

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

พอเพียง. นีโตะ!

จากเอกสารประกอบ :

เมื่อdynamicเป็นtrueเช่นนั้นการเปลี่ยนแปลงค่าพารามิเตอร์จะไม่ทำให้สถานะถูกป้อน / ออก การแก้ไขจะไม่ถูกดึงกลับมาใหม่และจะไม่โหลดมุมมองซ้ำ

สิ่งนี้มีประโยชน์ในการสร้าง UI ที่คอมโพเนนต์จะอัปเดตตัวเองเมื่อค่าพารามิเตอร์เปลี่ยนไป


นี่คือทางออกที่ดีที่สุด! อุปกรณ์ประกอบฉาก !!!!!
Patrik Laszlo

7

การตั้งค่านี้แก้ไขปัญหาต่อไปนี้ให้ฉัน:

  • ตัวควบคุมการฝึกอบรมจะไม่ถูกเรียกสองครั้งเมื่ออัปเดต url จาก .../เป็น.../123
  • ตัวควบคุมการฝึกจะไม่ถูกเรียกใช้อีกเมื่อนำทางไปยังสถานะอื่น

การกำหนดค่าสถานะ

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

การเรียกใช้สถานะ (จากตัวควบคุมอื่น ๆ )

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

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

ผู้ควบคุมการฝึกอบรม

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

ฉันพยายามใช้กลยุทธ์ที่คล้ายกัน แต่คอนโทรลเลอร์ของฉันไม่ได้รับค่า trainigId จากหน้าแก้ไขมีอะไรที่ฉันอาจขาดหายไป? ฉันพยายามเข้าถึงหน้าแก้ไขโดยตรงจาก URL และโดยใช้ ui-sref รหัสของฉันเหมือนกับของคุณทุกประการ
Nikhil Bhandari

สิ่งนี้ใช้ได้ผลสำหรับฉันและเป็นวิธีแก้ปัญหาที่ชัดเจนที่สุด
OoDeLally

3

หากคุณต้องการเพียงแค่เปลี่ยน url แต่ป้องกันไม่ให้เปลี่ยนสถานะ:

เปลี่ยนตำแหน่งด้วย (เพิ่ม. replace หากคุณต้องการแทนที่ในประวัติ):

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

ป้องกันการเปลี่ยนเส้นทางไปยังรัฐของคุณ:

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

2

ฉันทำสิ่งนี้ แต่นานมาแล้วในเวอร์ชัน: v0.2.10 ของเราเตอร์ UI เช่นนี้ ::

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


-6

ฉันไม่คิดว่าคุณต้องการ ui-router สำหรับสิ่งนี้ เอกสารประกอบสำหรับ$ location serviceระบุไว้ในย่อหน้าแรกว่า "... การเปลี่ยนแปลง $ location จะแสดงในแถบที่อยู่ของเบราว์เซอร์" ต่อไปในภายหลังเพื่อพูดว่า "ไม่ได้ทำอะไรมันไม่ทำให้เกิดการโหลดซ้ำทั้งหน้าเมื่อ URL ของเบราว์เซอร์มีการเปลี่ยนแปลง"

ด้วยเหตุนี้ทำไมไม่เปลี่ยน $ location.path (เนื่องจากวิธีนี้เป็นทั้ง getter และ setter) ด้วยสิ่งต่อไปนี้:

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

เอกสารบันทึกว่าเส้นทางควรเริ่มต้นด้วยการทับ แต่นี้จะเพิ่มถ้ามันมีอะไรขาดหายไป


เมื่อฉันใช้ui-routerและใช้$location.path(URL_PATH)หน้าจะแสดงผลใหม่โดยอัตโนมัติ?!
Kousha

ใช่มันแสดงผลอีกครั้ง ฉันลองใช้ event.preventDefault () บน $ locationChangeStart แล้วก็ไม่ได้ผลเช่นกันนั่นคือหยุดสถานะจากการแสดงผล แต่ก็ป้องกันไม่ให้อัปเดต url
wiherek
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.