В этом посте я создам очень простой Angular module, который даст вашему одностраничному приложению возможность всегда прокручивать страницу вверх.

Сначала давайте разберемся, что вы ожидаете от этого плагина:

Когда я прокручиваю страницу до конца, а затем перехожу к другой, я ожидаю, что позиция прокрутки страницы будет вверху на новой странице.

Когда я прокручиваю страницу назад, я ожидаю, что она запомнит, где я остановился, и прокрутит страницу до этой позиции.

Ладно, начнем, а?

Чтобы мы могли сохранить предыдущую позицию прокрутки, нам нужно хранилище для сохранения позиции, я предпочитаю использовать собственный sessionStorage.

Поэтому я создам сервис по этой причине и назову его SessionStorageService.

@Injectable()
export class SessionStorageService {
    write ( key : string, value : any ) {
        if ( value ) {
            value = JSON.stringify( value );
        }
        sessionStorage.setItem( key, value );
    }

    read<T> ( key : string ) : T {
        let value : string = sessionStorage.getItem( key );

        if ( value && value != "undefined" && value != "null" ) {
            return <T>JSON.parse( value );
        }
    }
}

Довольно просто, у него есть два метода, write и read.

Теперь мы можем приступить к созданию нашего ScrollStore .

@Injectable()
export class ScrollStore {
    constructor ( private router : Router, 
                  private storageService : SessionStorageService ) {
    }
}

Как видите, я внедрил маршрутизатор, потому что использование этого плагина имеет смысл только в том случае, если у вас многостраничное приложение, поэтому вы уже должны быть знакомы с маршрутизатором и импортировать его модуль в свое приложение.

Теперь, когда у меня есть маршрутизатор, я подпишусь на его событие, и каждый раз, когда происходит навигация по странице, я вызываю свою функцию для прокрутки вверх страницы или получения предыдущей позиции:

subscribeToRouter () {
    this.router.events.subscribe( event => {
        if ( event instanceof NavigationStart ) {
            this.saveScrollPos( this.currentUrl );
        }
        if ( event instanceof NavigationEnd ) {
            this.retrieveScrollPos( event );
        }
    } );
}

Как видите, я подписался на события router.events, и каждая навигация будет давать мне событие, которое может быть событием NavigationStart или NavigationEnd.

Есть еще пара других, которые вы можете найти здесь.

Итак, теперь давайте обновим consructor для вызова этой функции в сервисе:

@Injectable()
export class ScrollStore {
    constructor ( private router : Router, 
                  private storageService : SessionStorageService ) {
         this.subscribeToRouter();
    }

Хорошо, теперь давайте посмотрим, что это за два метода:

Когда мы начинаем навигацию, мы всегда хотим сохранить текущую позицию прокрутки с текущим URL-адресом, поэтому, если мы сразу вернемся на эту страницу, мы сможем получить ее и прокрутить до предыдущей позиции.

private saveScrollPos ( url ) {
    this.storageService.write( url, this.scrollTop );
}

Обратите внимание, что я передаю currentUrl в saveScrollPos, давайте посмотрим, что это может быть:

currentUrl — это простой метод получения, который всегда возвращает текущий URL-адрес, который нам даст маршрутизатор:

private get currentUrl () {
    return this.router.url;
}

Теперь, когда мы сохранили текущую позицию прокрутки для текущего URL-адреса, давайте поработаем над извлечением.

Что нам нужно, так это в конце перехода на страницу проверить, есть ли сохраненная позиция прокрутки против нового URL-адреса, и если она есть, прокрутить до нее, в противном случае прокрутить до нуля (вверху страницы).

private retrieveScrollPos ( event : NavigationStart ) {
    let retrievedScrollPos = this.storageService.read( event.url );
    if ( retrievedScrollPos === undefined ) {
        console.log( 'No saved position for ' + event.url + ' scroll to zero instead' );
        this.scrollToZero();
    } else {
        console.log( 'Postion found for ' + event.url + ' scroll to' + retrievedScrollPos );
        this.scrollTo( retrievedScrollPos as number );
    }

}

Что происходит, когда вызывается retrieveScrollPos, я проверяю наш storageService, чтобы увидеть, есть ли позиция, сохраненная по URL-адресу, если есть, я говорю scrollTo(retreivedScrollPos), и если нет, значит, мы должны прокрутить вверх, что я позвони scrollToZero .

Вот эти два метода:

private scrollTo ( retrievedScrollPos : number ) {
    this.scrollTop = retrievedScrollPos;
}
private scrollToZero () {
    this.scrollTop = 0;
}

А вот scrollTop, у которого есть геттер и сеттер для document.body.scrollTop

private get scrollTop () {
    return document.body.scrollTop;
}

private set scrollTop ( number : number ) {
    document.body.scrollTop = number;
}

Вот и все, и вот сервис в сборе:

@Injectable()
export class ScrollStore {
    constructor ( private router : Router, private storageService : SessionStorageService ) {
            this.subscribeToRouter();
    }

    subscribeToRouter () {
        this.router.events.subscribe( event => {
            if ( event instanceof NavigationStart ) {
                this.saveScrollPos( this.currentUrl );
            }
            if ( event instanceof NavigationEnd ) {
                this.retrieveScrollPos( event );
            }
        } );
    }

    private scrollToZero () {
        this.scrollTop = 0;
    }

    private get currentUrl () {
        return this.router.url;
    }

    private get scrollTop () {
        return document.body.scrollTop;
    }

    private set scrollTop ( number : number ) {
        document.body.scrollTop = number;
    }

    private saveScrollPos ( url ) {
        this.storageService.write( url, this.scrollTop );
    }

    private retrieveScrollPos ( event : NavigationStart ) {
        let retrievedScrollPos = this.storageService.read( event.url );
        if ( retrievedScrollPos === undefined ) {
            console.log( 'No saved position for ' + event.url + ' scroll to zero instead' );
            this.scrollToZero();
        } else {
            console.log( 'Postion found for ' + event.url + ' scroll to' + retrievedScrollPos );
            this.scrollTo( retrievedScrollPos as number );
        }

    }

    private scrollTo ( retrievedScrollPos : number ) {
        this.scrollTop = retrievedScrollPos;
    }
}

И последнее, но не менее важное: нам нужно отправить этот сервис внутри модуля, чтобы сделать его подключаемым:

@NgModule( {
    providers : [
        SessionStorageService,
        ScrollStore
    ]
} )
export class ScrollStoreModule {
    constructor ( private scrollStore : ScrollStore ) {

    }
}

Внедрение ScrollStore внутрь ScrollStoreModule является наиболее важной частью здесь, что позволяет создать экземпляр службы и начать работать.

Вот страница github, не стесняйтесь вносить свой вклад.