IT

AngularJS ui-router 상태 시스템에서 뒤로 버튼을 사용하여 뒤로 버튼을 사용합니까?

lottoking 2020. 9. 16. 07:57
반응형

AngularJS ui-router 상태 시스템에서 뒤로 버튼을 사용하여 뒤로 버튼을 사용합니까?


ui-router를 사용하여 angularjs 단일 페이지 응용 프로그램을 구현했습니다 .

원래는 고유 한 한 URL을 사용하여 각 주를 많이 보았지만 이로 인해 GUID 압축 URL이 만들어졌습니다.

그래서 이제 내 사이트를 훨씬 더 간단한 상태 머신으로 정의했습니다. 상태는 URL이 거의 없으나 다음과 같이 필요에 따라 전환됩니다.

중첩 상태 정의

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을 사용하지 않기 때문에 뒤로 버튼이 작동하지 않습니다.

깔끔한 ui-router 상태 머신을 유지하면서 뒤로 버튼 기능을 활성화 버튼을 사용합니까?


노트

변형 사용을 제안하는 답변 $window.history.back()은 모두 다음 질문의 중요한 부분을 놓쳤습니다 . 기록이 점프 할 때 응용 프로그램의 상태올바른 상태 위치로 복원하는 방법 (뒤로 / 앞으로 / 새로 고침). 그것을 염두에두고; 계속 읽어주세요.


예, 순수 ui-router상태 머신 을 실행하는 동안 브라우저를 뒤로 / 앞으로 (기록) 새로 고칠 수 약간의 작업이 필요합니다.

몇 가지 구성 요소가 필요합니다.

  • 고유 한 URL . 브라우저는 URL 생성을 사용할 때만 뒤로 / 앞으로 버튼을 활성화 할 때 방문한 상태에서 고유 한 URL을해야합니다. 이 URL은 상태 정보를 포함 할 필요가 없습니다.

  • 세션 서비스 . 생성 된 각 URL은 특정 상태와 상관 관계가 있으므로 url-state 쌍을 저장하는 방법이 각도 앱이 뒤로 / 앞으로 또는 새로 고침 클릭으로 다시 시작된 후 상태 정보를 검색 할 수 있습니다.

  • 국가 역사 . 고유 URL로 키가 지정된 ui-router 상태의 간단한 사전입니다. HTML5에 의존 할 수있는 권한 HTML5 History API를 사용할 수 있고 저처럼 할 수없는 경우 몇 줄의 코드로 직접 구현할 수 있습니다 (아래 참조).

  • 위치 서비스 . 마지막으로, 코드에 의해 내부적으로 트리거되는 ui-router 상태 변경과 일반적으로 사용자가 브라우저 버튼을 클릭하거나 브라우저 바에 항목을 입력 할 때 발생하는 트리거 일반 브라우저 URL 변경을 관리 할 수 ​​있습니다. 무엇이 무엇을 유발했는지 혼동하기 때문에 약간의 까다로울 수 있습니다.

다음은 각 요구 사항의 구현입니다. 모든 것을 세 가지 서비스로 묶었습니다.

세션 서비스

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

이것은 javascript 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고유 생성 URL에 의해 키잉 과거 상태의 저장 및 후에 보인다. 실제로 사전 스타일 객체에 대한 편의 래퍼입니다.

주 위치 서비스

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이 이벤트를 처리 :

  • locationChange . 일반적으로 뒤로 / 앞으로 / 새로 고침 버튼을 누르거나 앱이 처음 시작되거나 사용자가 URL을 입력 할 때 브라우저가 변경 될 때 호출됩니다. 현재 location.url에 대한 상태가에 존재하면 StateHistoryServiceui-router의 .url을 통해 상태를 복원하는 데 사용 $state.go됩니다.

  • stateChange . 내부적으로 상태를 실행할 때 호출됩니다. 현재 상태의 이름과 매개 변수는 StateHistoryService생성 된 URL에 의해 입력 된 키에 저장 됩니다. 이 생성 된 URL은 원하는 모든 것이 될 수 있고 사람이 읽을 수있는 방식으로 이미지 할 수도 있습니다. 제 경우 생성되는 순서 변수와 guid에서 파생 된 무작위로 된 숫자 시퀀스를 사용하고 있습니다 (guid 생성기 스 니펫은 foot 참조). 생성 된 URL은 브라우저 표시 줄에 표시되며, 결정적으로를 사용하여 브라우저의 내부 기록 스택에 추가 @location.url url됩니다. 앞으로 / 뒤로 버튼을 활성화하는 브라우저의 기록 스택에 URL을 추가합니다.

이 기술의 큰 문제 @location.url urlstateChange메서드 를 호출 하면 $locationChangeSuccess이벤트 가 트리거 되어 locationChange메서드를 호출 것입니다. @state.go에서 locationChange동일하게 호출하면 $stateChangeSuccess이벤트 가 트리거 되므로 stateChange메서드 가 트리거됩니다 . 이것은 매우 혼란스럽고 브라우저 기록을 끝없이 엉망으로 만듭니다.

해결은 매우 간단합니다. preventCall스택 ( poppush)으로 사용중인 어레이 를 볼 수 있습니다 . 메서드 중 하나가 호출 될 때마다 다른 메서드가 한 번만 호출되는 것을 방지합니다. 이 기술은 $ 이벤트의 올바른 트리거를 방해하지 않으므로 모든 것을 똑바로 유지합니다.

이제 우리가해야 할 일 HistoryService상태 전환 수명주기의 적절한 시간에 메서드를 호출하는 것 입니다. 이것은 AngularJS Apps 메소드 .run에서 다음과 같이 수행 됩니다.

각도 app.run

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

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

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

Guid 생성

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

이 모든 것이 제자리에 있으면 앞으로 / 뒤로 버튼과 새로 고침 버튼이 예상대로 작동합니다.


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

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

여러 제안을 테스트 한 후 가장 쉬운 방법이 가장 좋은 방법이라는 것을 알았습니다.

앵귤러 ui-router를 사용하고 가장 잘 돌아 가기 위해 버튼이 필요한 경우 다음과 있습니다.

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

또는

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

// 경고는 href를 설정하지 않습니다. 명명 된 경로가 손상됩니다.

설명 : 표준 관리 애플리케이션을 가정하십시오. 개체 검색-> 개체보기-> 개체 편집

각도 솔루션 사용 이 상태에서 :

검색->보기-> 편집

받는 사람 :

검색->보기

이제 브라우저 뒤로 버튼을 클릭하면 다시 표시됩니다.

검색->보기-> 편집

그리고 그것은

그러나 간단한 솔루션 사용

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

에서 :

검색->보기-> 편집

버튼 클릭 후 :

검색->보기

브라우저 뒤로 버튼을 클릭 한 후 :

검색

일관성이 존중됩니다. :-)


가장 간단한 "뒤로"버튼을 찾고 있다면 다음과 같이 지시문을 수 있습니다.

    .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로 사용자를 다시 보냅니다.


브라우저의 뒤로 / 앞으로 버튼 솔루션
동일한 문제가 발생 popstate event하여 $ window 개체 및 ui-router's $state object. popstate 이벤트는 활성 히스토리 항목이 변경 될 때마다 창으로 전달됩니다. 그리고 주소 표시 줄이 새 위치에도 이벤트는 브라우저의 버튼 클릭 트리거되지 않습니다. 그래서,은 상태에서 당신 탐색 한 가정 당신이 명중 할 때 다시 브라우저에,은 다시로 당신해야 우리 경로. 경로는 업데이트 된 업데이트보기는 업데이트되지 않은 현재 가지고있는 모든 항목을 표시합니다 . 이 시도 :
$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 ()에 대한 브라우저 이름을 확인하십시오.


간단한 지시문 "go-back-history"를 사용하여 해결할 수 있습니다. 이것은 또한 이전 기록이없는 경우 창을 닫는 것입니다.

지시 사용

<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-router를 사용하거나 사용하지 않습니다.


뒤로 버튼은 저에게도 작동하지 않았지만 문제는 요소의 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"
    })

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

참고 URL : https://stackoverflow.com/questions/22247294/how-do-i-get-the-back-button-to-work-with-an-angularjs-ui-router-state-machine

반응형