मुझे AngularJS ui-रूटर राज्य मशीन के साथ काम करने के लिए बैक बटन कैसे मिलता है?


87

मैंने ui- राउटर का उपयोग करके एक कोणीय एकल पृष्ठ अनुप्रयोग लागू किया है ।

मूल रूप से मैंने प्रत्येक राज्य की पहचान एक अलग url का उपयोग करके की थी, जो कि इसे अमित्र के लिए बनाया गया था, GUID पैक्ड यूआरएल था।

इसलिए मैंने अब अपनी साइट को बहुत सरल स्टेट-मशीन के रूप में परिभाषित किया है। राज्यों की पहचान urls द्वारा नहीं की जाती है, लेकिन उन्हें आवश्यकतानुसार परिवर्तित कर दिया जाता है:

नेस्टेड स्टेट्स को परिभाषित करें

angular
.module 'app', ['ui.router']
.config ($stateProvider) ->
    $stateProvider
    .state 'main', 
        templateUrl: 'main.html'
        controller: 'mainCtrl'
        params: ['locationId']

    .state 'folder', 
        templateUrl: 'folder.html'
        parent: 'main'
        controller: 'folderCtrl'
        resolve:
            folder:(apiService) -> apiService.get '#base/folder/#locationId'

एक परिभाषित राज्य में संक्रमण

#The ui-sref attrib transitions to the 'folder' state

a(ui-sref="folder({locationId:'{{folder.Id}}'})")
    | {{ folder.Name }}

यह प्रणाली बहुत अच्छी तरह से काम करती है और मुझे इसके स्वच्छ वाक्य-विन्यास से प्यार है। हालाँकि, जैसा कि मैं url का उपयोग नहीं कर रहा हूँ, बैक बटन काम नहीं करता है।

मैं अपने नीट यूआई-राउटर राज्य-मशीन को कैसे रख सकता हूं, लेकिन बैक बटन कार्यक्षमता को सक्षम कर सकता हूं?


1
"राज्यों को यूआरएल द्वारा पहचाना नहीं जाता है" - और आपकी समस्या मुझे संदेह है। बैक बटन कोड से बहुत सुरक्षित है (अन्यथा लोग इसे ओवरराइड करेंगे, जिससे समस्याएं पैदा होंगी)। क्यों न कोणीय को बेहतर यूआरएल बनाने दिया जाए, जैसे एसओ करता है (ठीक है वे कोणीय का उपयोग नहीं कर सकते हैं, लेकिन उनका यूआरएल स्कीम चित्रण है)?
जेकलूम

इसके अलावा, यह सवाल मदद कर सकता है: stackoverflow.com/questions/13499040/…
jcollum

इसके अलावा, चूंकि आप URL का उपयोग नहीं कर रहे हैं, इसका मतलब यह नहीं है कि Z को प्राप्त करने के लिए लोगों को इसे प्राप्त करने के लिए थ्रू स्टेट X और Y पर क्लिक करना होगा? यह कष्टप्रद हो सकता है।
jcollum

क्या यह अलग-अलग पारमों के साथ राज्य जाएगा? @ जेकलूम
विजयविष्णु

मुझे पता नहीं है, यह बहुत पहले था
जेसीलूम

जवाबों:


78

ध्यान दें

उत्तर के बदलावों का उपयोग $window.history.back()करने वाले सभी उत्तर , प्रश्न का एक महत्वपूर्ण हिस्सा याद करते हैं: आवेदन की स्थिति को सही स्थिति में कैसे पुनर्स्थापित करना है क्योंकि इतिहास कूदता है (पीछे / आगे / ताज़ा)। ये ध्यान रखते हुए; कृपया, पर पढ़ें।


हाँ, ब्राउज़र को वापस / फॉरवर्ड (इतिहास) करना संभव है और शुद्ध ui-routerस्टेट-मशीन को चलाने के दौरान रिफ्रेश करना संभव है, लेकिन इसमें कुछ करने में समय लगता है।

आपको कई घटकों की आवश्यकता है:

  • अनोखा यूआरएल । जब आप url बदलते हैं, तो ब्राउज़र केवल बैक / फॉरवर्ड बटन को सक्षम करता है, इसलिए आपको प्रति यूआरएल स्टेट में एक अद्वितीय यूआरएल उत्पन्न करना होगा। हालांकि इन url में किसी भी राज्य की जानकारी शामिल नहीं है।

  • एक सत्र सेवा । प्रत्येक उत्पन्न url को एक विशेष स्थिति में सहसंबद्ध किया जाता है, ताकि आपको अपने url-state जोड़े को संग्रहीत करने के तरीके की आवश्यकता हो ताकि आप अपने कोणीय ऐप को वापस / अग्रेषित या ताज़ा क्लिक द्वारा पुनः आरंभ करने के बाद राज्य की जानकारी प्राप्त कर सकें।

  • एक राज्य का इतिहास । यूआई-राउटर का एक सरल शब्दकोष अद्वितीय url द्वारा कुंजीबद्ध है। यदि आप एचटीएमएल 5 पर भरोसा कर सकते हैं तो आप एचटीएमएल 5 हिस्ट्री एपीआई का उपयोग कर सकते हैं , लेकिन यदि, मेरी तरह, तो आप इसे कोड की कुछ पंक्तियों में स्वयं लागू नहीं कर सकते हैं (नीचे देखें)।

  • एक स्थान सेवा । अंत में, आपको अपने कोड द्वारा आंतरिक रूप से ट्रिगर किए गए दोनों यूआई-राउटर राज्य परिवर्तनों को प्रबंधित करने में सक्षम होना चाहिए, और सामान्य ब्राउज़र यूआरएल परिवर्तन आमतौर पर ब्राउज़र बटन पर क्लिक करने या ब्राउज़र बार में सामान टाइप करने से ट्रिगर होते हैं। यह सब थोड़ा मुश्किल हो सकता है क्योंकि यह उलझन में होना आसान है कि क्या ट्रिगर हुआ।

यहाँ इन आवश्यकताओं में से प्रत्येक का मेरा कार्यान्वयन है। मैंने सब कुछ तीन सेवाओं में बांधा है:

सत्र सेवा

class SessionService

    setStorage:(key, value) ->
        json =  if value is undefined then null else JSON.stringify value
        sessionStorage.setItem key, json

    getStorage:(key)->
        JSON.parse sessionStorage.getItem key

    clear: ->
        @setStorage(key, null) for key of sessionStorage

    stateHistory:(value=null) ->
        @accessor 'stateHistory', value

    # other properties goes here

    accessor:(name, value)->
        return @getStorage name unless value?
        @setStorage name, value

angular
.module 'app.Services'
.service 'sessionService', SessionService

यह जावास्क्रिप्ट sessionStorageऑब्जेक्ट के लिए एक आवरण है । मैंने यहाँ स्पष्टता के लिए इसे काटा है। इसकी पूरी व्याख्या के लिए कृपया देखें: मैं एक AngularJS सिंगल पेज एप्लिकेशन के साथ ताज़ा पृष्ठ को कैसे संभालूँ

राज्य का इतिहास सेवा

class StateHistoryService
    @$inject:['sessionService']
    constructor:(@sessionService) ->

    set:(key, state)->
        history = @sessionService.stateHistory() ? {}
        history[key] = state
        @sessionService.stateHistory history

    get:(key)->
        @sessionService.stateHistory()?[key]

angular
.module 'app.Services'
.service 'stateHistoryService', StateHistoryService

StateHistoryServiceभंडारण और उत्पन्न, अद्वितीय यूआरएल द्वारा keyed ऐतिहासिक राज्यों की बहाली के बाद दिखता है। शब्दकोश शैली वस्तु के लिए यह वास्तव में सिर्फ एक सुविधा आवरण है।

राज्य स्थान सेवा

class StateLocationService
    preventCall:[]
    @$inject:['$location','$state', 'stateHistoryService']
    constructor:(@location, @state, @stateHistoryService) ->

    locationChange: ->
        return if @preventCall.pop('locationChange')?
        entry = @stateHistoryService.get @location.url()
        return unless entry?
        @preventCall.push 'stateChange'
        @state.go entry.name, entry.params, {location:false}

    stateChange: ->
        return if @preventCall.pop('stateChange')?
        entry = {name: @state.current.name, params: @state.params}
        #generate your site specific, unique url here
        url = "/#{@state.params.subscriptionUrl}/#{Math.guid().substr(0,8)}"
        @stateHistoryService.set url, entry
        @preventCall.push 'locationChange'
        @location.url url

angular
.module 'app.Services'
.service 'stateLocationService', StateLocationService

StateLocationServiceदो घटनाओं संभालती है:

  • स्थान परिवर्तन । यह तब कहा जाता है जब ब्राउज़र स्थान बदल जाता है, आमतौर पर जब पीछे / आगे / ताज़ा बटन दबाया जाता है या जब ऐप पहली बार शुरू होता है या जब उपयोगकर्ता एक यूआरएल में टाइप करता है। यदि वर्तमान स्थान के लिए कोई राज्य .url मौजूद है, StateHistoryServiceतो इसका उपयोग यूआई-राउटर के माध्यम से राज्य को पुनर्स्थापित करने के लिए किया जाता है $state.go

  • राज्य-परिवर्तन । यह तब कहा जाता है जब आप आंतरिक रूप से राज्य को स्थानांतरित करते हैं। वर्तमान राज्य का नाम और पाराम StateHistoryServiceएक जनरेट किए गए url द्वारा कुंजी में संग्रहीत किए जाते हैं । यह उत्पन्न यूआरएल कुछ भी आप चाहते हैं, यह मानव पठनीय तरीके से राज्य की पहचान कर सकता है या नहीं हो सकता है। मेरे मामले में मैं एक स्टेटम प्लस (एक गाइड से प्राप्त अंकों के एक यादृच्छिक रूप से उत्पन्न अनुक्रम का उपयोग कर रहा हूं) (गाइड जनरेटर स्निपेट के लिए पैर देखें)। उत्पन्न यूआरएल को ब्राउज़र बार में प्रदर्शित किया जाता है और, महत्वपूर्ण रूप से, ब्राउज़र के आंतरिक इतिहास स्टैक का उपयोग करके जोड़ा जाता है @location.url url। इसका ब्राउज़र के इतिहास स्टैक में url जोड़ना जो आगे / पीछे बटन को सक्षम करता है।

इस तकनीक के साथ बड़ी समस्या यह है कि विधि @location.url urlमें कॉल stateChangeकरने से $locationChangeSuccessघटना शुरू हो जाएगी और इसलिए locationChangeविधि को कॉल करना होगा । समान रूप से कॉल करने @state.goसे घटना और इस विधि locationChangeको ट्रिगर किया जाएगा । यह बहुत भ्रामक हो जाता है और ब्राउज़र इतिहास को समाप्त नहीं करता है।$stateChangeSuccessstateChange

समाधान बहुत सरल है। आप preventCallसरणी को एक स्टैक ( popऔर push) के रूप में उपयोग किया जा रहा है देख सकते हैं । प्रत्येक बार विधियों में से एक को कहा जाता है, यह दूसरी विधि को केवल एक समय-काल कहा जाता है। यह तकनीक $ घटनाओं के सही ट्रिगर के साथ हस्तक्षेप नहीं करती है और सब कुछ सीधे रखती है।

अब हमें केवल इतना करना है HistoryServiceकि राज्य के परिवर्तनकारी जीवन-चक्र में उचित समय पर विधियों को कॉल करें । यह .runइस तरह से AngularJS Apps विधि में किया जाता है :

कोणीय app.run

angular
.module 'app', ['ui.router']
.run ($rootScope, stateLocationService) ->

    $rootScope.$on '$stateChangeSuccess', (event, toState, toParams) ->
        stateLocationService.stateChange()

    $rootScope.$on '$locationChangeSuccess', ->
        stateLocationService.locationChange()

एक गाइड जनरेट करें

Math.guid = ->
    s4 = -> Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1)
    "#{s4()}#{s4()}-#{s4()}-#{s4()}-#{s4()}-#{s4()}#{s4()}#{s4()}"

इस सब के साथ, आगे / पीछे बटन और ताज़ा बटन सभी अपेक्षित रूप से काम करते हैं।


1
ऊपर दिए गए सत्र सेवा के उदाहरण में, मुझे लगता है कि एक्सेसर : विधि का उपयोग किया जाना चाहिए @setStorageऔर @getStorageइसके बजाय '@ प्राप्त / सेट करें'।
पीटर व्हिटफ़ील्ड

3
इस उदाहरण के लिए किस भाषा का उपयोग किया जाता है। ऐसा प्रतीत नहीं होता कि कोणीय है जिससे मैं परिचित हूं।
गहरा

भाषा जावास्क्रिप्ट थी, वाक्य रचना कॉफिसस्क्रिप्ट थी।
बायोफैक्टल

1
@jlguenego आपके पास एक कार्यात्मक ब्राउज़र इतिहास / बॉलर आगे और पीछे बटन और URL है जिसे आप बुकमार्क कर सकते हैं।
टॉर्स्टन बार्टेल

3
@jlguenego - उत्तर, जो विविधता का उपयोग करने का सुझाव देते $window.history.back()हैं, प्रश्न का एक महत्वपूर्ण हिस्सा छूट गया है। बिंदु आवेदन की स्थिति को सही स्थिति-स्थान पर पुनर्स्थापित करना था क्योंकि इतिहास कूदता है (पीछे / आगे / ताज़ा)। यह आमतौर पर यूआरआई के माध्यम से राज्य डेटा की आपूर्ति करके प्राप्त किया जाएगा। यह सवाल पूछा गया कि यूआरआई राज्य डेटा के बिना (स्पष्ट) राज्य-स्थानों के बीच कैसे कूदना है । इस अड़चन को देखते हुए, यह केवल बैक बटन को दोहराने के लिए पर्याप्त नहीं है क्योंकि यह राज्य-स्थान को फिर से स्थापित करने के लिए यूआरआई राज्य डेटा पर निर्भर करता है।
बायोफ्रेक्टल

46
app.run(['$window', '$rootScope', 
function ($window ,  $rootScope) {
  $rootScope.goBack = function(){
    $window.history.back();
  }
}]);

<a href="#" ng-click="goBack()">Back</a>

2
इसे प्रेम करें! ... योग्य ... स्पष्टता के $window.history.backलिए जादू कर रहा है $rootScope... नहीं तो वापस जाओ अगर आप चाहते हैं अपने नावबार निर्देशकीय दायरे के लिए बाध्य किया जा सकता है।
बेंजामिन कॉन्टेंट

@BenjaminConant उन लोगों के लिए जो इसे लागू करने का तरीका नहीं जानते हैं, आप बस $window.history.back();एक समारोह में बुलाए जाने के लिए कहते हैं ng-click
चकेड़ा

सही rootScope केवल फ़ंक्शन को किसी भी टेम्पलेट में सुलभ बनाने के लिए है
Guillaume Massé

चिकन डिनर।
कोड़ी

23

विभिन्न प्रस्तावों का परीक्षण करने के बाद, मैंने पाया कि सबसे आसान तरीका अक्सर सबसे अच्छा होता है।

यदि आप कोणीय यूआई-राउटर का उपयोग करते हैं और आपको वापस जाने के लिए एक बटन की आवश्यकता है तो यह है:

<button onclick="history.back()">Back</button>

या

<a onclick="history.back()>Back</a>

// चेतावनी ने href सेट नहीं किया है या रास्ता टूट जाएगा।

स्पष्टीकरण: मान लीजिए कि एक मानक प्रबंधन अनुप्रयोग है। खोज ऑब्जेक्ट -> ऑब्जेक्ट देखें -> ऑब्जेक्ट संपादित करें

इस राज्य से कोणीय समाधान का उपयोग करना :

खोज -> दृश्य -> ​​संपादित करें

सेवा :

खोज -> देखें

ठीक है कि हम क्या चाहते थे सिवाय इसके कि अगर अब आप ब्राउज़र बैक बटन पर क्लिक करते हैं तो आप फिर से वहीं होंगे:

खोज -> दृश्य -> ​​संपादित करें

और यह तर्कसंगत नहीं है

हालांकि सरल समाधान का उपयोग कर

<a onclick="history.back()"> Back </a>

से:

खोज -> दृश्य -> ​​संपादित करें

बटन पर क्लिक करने के बाद:

खोज -> देखें

ब्राउज़र बैक बटन पर क्लिक करने के बाद:

खोज

संगति का सम्मान किया जाता है। :-)


7

यदि आप सबसे आसान "बैक" बटन ढूंढ रहे हैं, तो आप एक निर्देश सेट कर सकते हैं:

    .directive('back', function factory($window) {
      return {
        restrict   : 'E',
        replace    : true,
        transclude : true,
        templateUrl: 'wherever your template is located',
        link: function (scope, element, attrs) {
          scope.navBack = function() {
            $window.history.back();
          };
        }
      };
    });

ध्यान रखें कि यह एक काफी अनजाने में "वापस" बटन है क्योंकि यह ब्राउज़र के इतिहास का उपयोग कर रहा है। यदि आप इसे अपने लैंडिंग पृष्ठ पर शामिल करते हैं, तो यह उपयोगकर्ता को आपके द्वारा लैंडिंग से पहले आए किसी भी url पर वापस भेज देगा।


3

ब्राउज़र का बैक / फ़ॉरवर्ड बटन सॉल्यूशन
मुझे उसी समस्या का सामना करना पड़ा और मैंने इसे popstate event$ विंडो ऑब्जेक्ट से उपयोग करके हल किया ui-router's $state object। जब भी सक्रिय इतिहास प्रविष्टि में बदलाव होता है, तो हर बार एक पॉपस्टेट घटना को खिड़की में भेज दिया जाता है। और घटनाओं ब्राउज़र की बटन पर क्लिक ट्रिगर नहीं है, भले ही पता पट्टी नए स्थान को इंगित करता है कर रहे हैं। तो, यह सोचते हैं आप राज्यों से नेविगेट किया करने के लिए फिर से, जब आप हिट ब्राउज़र पर, तुम वापस करने के लिए किया जाना चाहिए मार्ग। पथ अद्यतन किया गया है लेकिन दृश्य नहीं है और अभी भी जो कुछ भी आपके पास है उसे प्रदर्शित करता है । इसे इस्तेमाल करे:
$stateChangeSuccess$locationChangeSuccess
mainfoldermainbackfoldermain

angular
.module 'app', ['ui.router']
.run($state, $window) {

     $window.onpopstate = function(event) {

        var stateName = $state.current.name,
            pathname = $window.location.pathname.split('/')[1],
            routeParams = {};  // i.e.- $state.params

        console.log($state.current.name, pathname); // 'main', 'folder'

        if ($state.current.name.indexOf(pathname) === -1) {
            // Optionally set option.notify to false if you don't want 
            // to retrigger another $stateChangeStart event
            $state.go(
              $state.current.name, 
              routeParams,
              {reload:true, notify: false}
            );
        }
    };
}

बैक / फॉरवर्ड बटन को उसके बाद आसानी से काम करना चाहिए।

नोट: window.onpopstate () के लिए ब्राउज़र संगतता सुनिश्चित करें


3

एक सरल निर्देश "गो-बैक-हिस्ट्री" का उपयोग करके हल किया जा सकता है, यह पिछले इतिहास के न होने की स्थिति में भी विंडो बंद कर रहा है।

प्रत्यक्ष उपयोग

<a data-go-back-history>Previous State</a>

कोणीय निर्देशन की घोषणा

.directive('goBackHistory', ['$window', function ($window) {
    return {
        restrict: 'A',
        link: function (scope, elm, attrs) {
            elm.on('click', function ($event) {
                $event.stopPropagation();
                if ($window.history.length) {
                    $window.history.back();
                } else {
                    $window.close();  
                }
            });
        }
    };
}])

नोट: ui- राउटर का उपयोग कर काम कर रहा है या नहीं।


0

बैक बटन मेरे लिए भी काम नहीं कर रहा था, लेकिन मुझे लगा कि समस्या यह है कि htmlमेरे मुख्य पृष्ठ के अंदर, ui-viewतत्व में सामग्री थी ।

अर्थात

<div ui-view>
     <h1> Hey Kids! </h1>
     <!-- More content -->
</div>

इसलिए मैंने सामग्री को एक नई .htmlफ़ाइल में स्थानांतरित कर दिया , और इसे .jsमार्गों के साथ फ़ाइल में एक टेम्पलेट के रूप में चिह्नित किया ।

अर्थात

   .state("parent.mystuff", {
        url: "/mystuff",
        controller: 'myStuffCtrl',
        templateUrl: "myStuff.html"
    })

-1

history.back()और पिछली स्थिति पर स्विच करें अक्सर वह प्रभाव दें जो आप चाहते हैं। उदाहरण के लिए, यदि आपके पास टैब के साथ फॉर्म है और प्रत्येक टैब की अपनी स्थिति है, तो यह केवल पिछले टैब को स्विच किया गया है, फॉर्म से वापस नहीं। नेस्टेड स्टेट्स के मामले में, आपको आमतौर पर उन पेरेंट स्टेट्स के बारे में सोचना चाहिए, जिन्हें आप रोलबैक करना चाहते हैं।

यह निर्देश समस्या हल करता है

angular.module('app', ['ui-router-back'])

<span ui-back='defaultState'> Go back </span>

यह बताता है कि बटन प्रदर्शित होने से पहले सक्रिय था । वैकल्पिक defaultStateराज्य का नाम है जिसका उपयोग स्मृति में कोई पिछली स्थिति नहीं होने पर किया जाता है। इसके अलावा यह स्क्रॉल स्थिति को पुनर्स्थापित करता है

कोड

class UiBackData {
    fromStateName: string;
    fromParams: any;
    fromStateScroll: number;
}

interface IRootScope1 extends ng.IScope {
    uiBackData: UiBackData;
}

class UiBackDirective implements ng.IDirective {
    uiBackDataSave: UiBackData;

    constructor(private $state: angular.ui.IStateService,
        private $rootScope: IRootScope1,
        private $timeout: ng.ITimeoutService) {
    }

    link: ng.IDirectiveLinkFn = (scope, element, attrs) => {
        this.uiBackDataSave = angular.copy(this.$rootScope.uiBackData);

        function parseStateRef(ref, current) {
            var preparsed = ref.match(/^\s*({[^}]*})\s*$/), parsed;
            if (preparsed) ref = current + '(' + preparsed[1] + ')';
            parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
            if (!parsed || parsed.length !== 4)
                throw new Error("Invalid state ref '" + ref + "'");
            let paramExpr = parsed[3] || null;
            let copy = angular.copy(scope.$eval(paramExpr));
            return { state: parsed[1], paramExpr: copy };
        }

        element.on('click', (e) => {
            e.preventDefault();

            if (this.uiBackDataSave.fromStateName)
                this.$state.go(this.uiBackDataSave.fromStateName, this.uiBackDataSave.fromParams)
                    .then(state => {
                        // Override ui-router autoscroll 
                        this.$timeout(() => {
                            $(window).scrollTop(this.uiBackDataSave.fromStateScroll);
                        }, 500, false);
                    });
            else {
                var r = parseStateRef((<any>attrs).uiBack, this.$state.current);
                this.$state.go(r.state, r.paramExpr);
            }
        });
    };

    public static factory(): ng.IDirectiveFactory {
        const directive = ($state, $rootScope, $timeout) =>
            new UiBackDirective($state, $rootScope, $timeout);
        directive.$inject = ['$state', '$rootScope', '$timeout'];
        return directive;
    }
}

angular.module('ui-router-back')
    .directive('uiBack', UiBackDirective.factory())
    .run(['$rootScope',
        ($rootScope: IRootScope1) => {

            $rootScope.$on('$stateChangeSuccess',
                (event, toState, toParams, fromState, fromParams) => {
                    if ($rootScope.uiBackData == null)
                        $rootScope.uiBackData = new UiBackData();
                    $rootScope.uiBackData.fromStateName = fromState.name;
                    $rootScope.uiBackData.fromStateScroll = $(window).scrollTop();
                    $rootScope.uiBackData.fromParams = fromParams;
                });
        }]);
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.