![](/img/trans.png)
[英]How to refresh an angular page with router without window.location.reaload?
[英]How do I replace my angular location without a controller refresh?
假設我有一個用於編輯電子賀卡的Angular應用程序。 創建新的電子賀卡使用類似#/ecard/create
的路徑,編輯現有的電子賀卡使用類似#/ecard/:id
的路徑。 標簽系統讓我們可以打開多個電子賀卡進行編輯。
我們喜歡自動保存功能,例如用戶期望的內容,例如現代網絡郵件或wiki軟件(或StackOverflow本身)。 我們不想在用戶打開創建表單時保存電子賀卡草稿,這會給我們提供大量空白電子賀卡,因此我們會在用戶開始輸入后開始自動保存。
我想在我們的控制器中編寫這樣的代碼(這被簡化為不包括例如錯誤處理或在關閉選項卡時停止自動保存等):
$scope.autosave = function () {
ECardService.autosave($scope.eCard).then(function (response) {
$location.path('/ecard/' + response.id).replace();
$timeout($scope.autosave, AUTOSAVE_INTERVAL);
});
};
$timeout($scope.autosave, AUTOSAVE_INTERVAL);
上面的代碼工作得很好,除了一件事:當位置改變時,我們的控制器重新加載並且視圖重新渲染。 因此,如果用戶在自動保存完成時處於打字過程中,則會出現短暫的閃爍,並且會丟失它們的位置。
我已經考慮了幾種方法來緩解這個問題:
1)改變使用的搜索路徑,並設置路徑reloadOnSearch
到false
在ngRoute
配置。 因此,路徑將從#/ecard?id=create
更改為例如#/ecard/id=123
,因此不會強制重新加載。 問題是我可能打開多個電子賀卡,我確實希望從#/ecard/id=123
更改為#/ecard/id=321
以觸發路由更改並重新加載控制器。 所以這不可行。
2)在這種情況下,不要打擾編輯URL並處理后退按鈕給出奇怪的行為。 這很誘人,但如果用戶打開現有電子賀卡列表並嘗試打開已保存的特定電子賀卡,我們希望標簽系統能夠識別它應該只顯示當前存在的標簽而不是打開新標簽。
理論上我們可以通過更新我們的標簽系統來更聰明地解決這個問題。 它不僅可以檢查路徑,還可以檢查路徑和持久性ID,我們可以存儲在某處。 這會使標簽系統顯得更加復雜,而這似乎對此功能有點過分。
3)僅在用戶未主動編輯時更改URL,例如編寫$scope.userIsIdle()
函數,如果用戶進行任何編輯后至少10秒鍾,則返回true
,然后根據該函數更新路徑。 這個的簡化版本看起來像:
$scope.updatePathWhenSafe = function (path) {
if ($scope.userIsIdle()) {
$location.path(path).replace();
} else {
$timeout(function () {
$scope.updatePathWhenSafe(path);
}, 1000);
}
};
我最終選擇了#3; 它比選項#2簡單得多,但實現和測試要比我想要的復雜得多,特別是一旦我考慮了邊緣情況,例如“當超時觸發時標簽不再是活動標簽怎么辦?” 我希望選項#4成為可能。
4)在Angular之外編輯當前位置和歷史,假設這是必要且可能的。 這將是我的首選解決方案,但我的研究表明,嘗試繞過$location
服務更改路徑或編輯歷史記錄是不安全/可取的。 有沒有一些安全的方法來做到這一點? 如果我只能說“更改當前路徑但不重新加載控制器”,那么事情會變得如此簡單。
選項#4可行/可行嗎? 如果沒有,那么還有更好的方法嗎? 也許一些神奇的“做角度方式,但不知何故不刷新控制器”?
這不是有角度的方式,但它可能很有用。 收到數據后,您可以檢查是否有焦點元素(用戶正在鍵入)。 如果是這樣,那么你需要定義一個在元素失去焦點時執行一次的函數。 如果沒有聚焦元素,則立即更改url。
像這樣:
ECardService.autosave($scope.eCard).then(function (response) {
if($(':focus').length){ //if there is focused element
$(':focus').one('blur', function(){ //
$location.path('/ecard/' + response.id).replace(); //perform once
});
}
else{
$location.path('/ecard/' + response.id).replace();
}
});
當然,這不是最優雅的解決方案,但似乎可以解決您的問題。
如果您有需要跨多個視圖控制器運行的代碼,AngularJS為此類實例提供根范圍。 你可以在這里找到文檔。
但是我建議不要使用實際上有多個視圖的標簽系統。 打開多個項目意味着將它們全部放在您的工作空間中。
您可能需要考慮使用Angular指令的單個視圖來存儲電子賀卡。 這樣,他們每個人都可以擁有自己的范圍,並且可以在實例中使用而無需重新呈現頁面。
他們還可以共享控制器$scope
定義的函數,而不需要app寬的根范圍。 請注意,必須在指令上啟用范圍。 scope: true
查看AngularJS站點以獲取有關此內容的教程和文檔。
似乎你所描述的問題的最佳解決方案是使用像ui-router這樣的狀態機。
使用像這樣的庫,你可以擁有一個具有多個狀態的單頁應用程序(你也可以成為網址的一部分),所以每當狀態發生變化時,你都可以保存你的電子賀卡而你永遠不會有任何狀態可見重新加載,因為您正在處理單個頁面應用程序。
所以我理解路徑想要反映最新版本的id,所以在這種情況下你需要刷新每次保存。
但是,如果路徑是像ecard/latest
這樣的最新版本的別名,那該怎么辦? 這樣您就不必刷新視圖,因為您不必更改路徑,只需在后端實現一些內容,最后將param指向最新版本的id。
事實證明,有一種方法可以完全按照我想要的方式行事,盡管它並沒有被Angular正式祝福。 有人為這個確切的用例打開了一張Angular票: https : //github.com/angular/angular.js/issues/1699
提議的更改作為拉取請求提交並被拒絕: https : //github.com/angular/angular.js/pull/2398
根據原始票證中的注釋,我實現了一個如下所示的解決方法:
app.factory('patchLocationWithSkipReload', function ($location, $route, $rootScope) {
$location.skipReload = function () {
var prevRoute = $route.current;
var unregister = $rootScope.$on('$locationChangeSuccess', function () {
$route.current = prevRoute;
unregister();
});
return $location;
};
});
我基本上能夠(為簡潔省略錯誤處理)說
ECardService.autosave($scope.eCard).then(function (response) {
$location.skipReload().path('/ecard/' + response.id).replace();
$scope.resetAutosaveTimeout();
});
基本測試顯示這很有效!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.