Hidden back navigation strategy in AngularJS 2 with location strategy

After creating a custom LocationStrategy to disable browser location bar changes, I am now looking to integrate smaller apps into various web pages without affecting the browser's location. While navigation works smoothly with this new strategy, I am facing a challenge in implementing a back()-function for navigation. Calling window.back() is no longer an option and there seems to be no internal method within Angular sources that can achieve this. Perhaps directly calling LocationStrategy.back() could work, but then I need a way to update the router for the current view. Is there an event that can be triggered or another solution to refresh the view?

Below is the current implementation of my custom location strategy:

import { Injectable, Inject, Optional, platform } from 'angular2/core';
import { LocationStrategy, PlatformLocation, APP_BASE_HREF, } from 'angular2/router';
import { joinWithSlash, normalizeQueryParams } from 'angular2/src/router/location_strategy';
import { UrlChangeListener } from 'angular2/src/router/location/platform_location';
import { isPresent } from 'angular2/src/facade/lang';

@Injectable()
export class HiddenLocationStrategy extends LocationStrategy {
    private _baseHref: string = '';
    private pathHistory: string[] = [];
    private poppedPathHistory: string[] = [];
    constructor(private _platformLocation: PlatformLocation,
        @Optional() @Inject(APP_BASE_HREF) _baseHref?: string) {
        super();
        if (isPresent(_baseHref)) {
            this._baseHref = _baseHref;
        }
    }

    onPopState(fn: UrlChangeListener): void {
    }

    getBaseHref(): string { return this._baseHref }

    path(): string {
        return this.pathHistory.length > 0 ? this.pathHistory[this.pathHistory.length - 1] : '';
    }

    prepareExternalUrl(internal: string): string {
        var url = joinWithSlash(this._baseHref, internal);
        return url;
    }

    pushState(state: any, title: string, path: string, queryParams: string) {
        this.pathHistory.push(path);
    }

    replaceState(state: any, title: string, path: string, queryParams: string) {
    }

    forward(): void { this.pathHistory.push(this.poppedPathHistory.pop()); }

    back(): void { this.poppedPathHistory.push(this.pathHistory.pop()); }
}

Answer №2

To implement the solution, we utilize the history.pushState API by incorporating logic within the pushState method to track an incrementing identifier (along with other desired state information). Further logic is added in the onPopState method to determine whether the popped state signifies a backward or forward navigation. Several of PlatformLocation's methods are then encapsulated within this implementation.

import { APP_BASE_HREF, LocationStrategy, PlatformLocation, Location, LocationChangeListener } from '@angular/common';
import { Inject, Injectable, Optional } from '@angular/core';
import { isPresent } from '@angular/common/src/facade/lang';


export interface HistoryState {
    state: any;
    title: string;
    path: string;
}

@Injectable()
export class HiddenLocationStrategy extends LocationStrategy {
    private baseHref: string = '';
    private pathHistory: HistoryState[] = [];
    private poppedPathHistory: HistoryState[] = [];

    constructor(
        private platformLocation: PlatformLocation,
        @Optional() @Inject(APP_BASE_HREF) baseHref?: string
    ) {
        super();

        if (isPresent(baseHref)) {
            this.baseHref = baseHref;
        }
    }

    onPopState(fn: LocationChangeListener): void {
        this.platformLocation.onPopState((ev: PopStateEvent) => {
            let backward = this.pathHistory.find((item) => item.state.uid === ev.state.uid);
            let forward = this.poppedPathHistory.find((item) => item.state.uid === ev.state.uid);

            if (backward) {
                this.navigateBack();
            } else if (forward) {
                this.navigateForward();
            }

            fn(ev);
        });
        //this.platformLocation.onHashChange(fn);
    }

    getBaseHref(): string {
        return this.baseHref;
    }

    path(): string {
        return this.pathHistory.length > 0
            ? this.pathHistory[this.pathHistory.length - 1].path
            : '';
    }

    prepareExternalUrl(internal: string): string {
        return Location.joinWithSlash(this.baseHref, internal);
    }

    pushState(state: any, title: string, path: string, queryParams: string) {
        state = Object.assign({}, state, {
            uid: (new Date()).valueOf()
        });

        this.pathHistory.push({
            state: state,
            title: title,
            path: path
        });

        this.platformLocation.pushState(state, title, this.prepareExternalUrl(''));
    }

    replaceState(state: any, title: string, path: string, queryParams: string) {
        this.platformLocation.replaceState(state, title, path);
    }

    forward(): void {
        this.platformLocation.forward();
    }

    back(): void {
        this.platformLocation.back();
    }

    private navigateForward() {
        this.pathHistory.push(this.poppedPathHistory.pop());
    }

    private navigateBack() {
        this.poppedPathHistory.push(this.pathHistory.pop());
    }
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Encountering an error when trying to import a node module in an Angular TypeScript file. The module

Currently, I am in the process of developing an Electron application specifically designed for managing Airplay on MacOS. To accomplish this task, I am utilizing Angular and TypeScript to wrap APIs from a unique npm package known as Airplay npm package. ...

The results of Angular CI tests vary between local environment and DevOps platform

I've encountered an issue with my pipeline. While I am aware that my current Karma tests are not working properly, there seems to be a discrepancy between running the tests on my local machine and on DevOps pipelines. karma.conf.ci.js // Configurat ...

Creating a null array of a specific size can easily be accomplished in Typescript

When I use the splice method to add elements to an array at a specified index, I find myself creating a null array first in order to achieve this. If I use an empty array instead, the elements do not get pushed to the specific instance that I intended. Cur ...

Is it possible that I am making a mistake when using the multi-mixin helper, which is causing an unexpected compiler error that I cannot

As I work on creating a multi-mixin helper function that takes in a map of constructors and returns an extended map with new interfaces, I come across some challenges. Let's first look at the basic base classes: class Alpha { alpha: string = &ap ...

Discover the potential of JavaScript's match object and unleash its power through

In the given data source, there is a key called 'isEdit' which has a boolean value. The column value in the data source matches the keys in the tempValues. After comparison, we check if the value of 'isEdit' from the data source is true ...

Clipanion is unable to fulfill requests

I followed the official Clipanion documentation for creating a CLI tool () and even cloned an example from here - https://github.com/i5ting/clipanion-test, but I'm facing issues when trying to execute my commands. It seems like I might be struggling ...

"The Zorro table is filled with items of various types, although unfortunately, the Intellisense is not as accurate as it

Imagine a scenario where you have a basic table set up: <nz-table #table [nzData]="users"> <thead> <tr> <th>Id</th> <th>First Name</th> <th>Last Name</th> </tr> ...

Tips for dealing with strong reference cycles in TypeScript?

I have created a tree-like structure in my implementation, consisting of parent and child objects. The parents contain a list of children while the children have references to their parents, facilitating easy navigation through the tree. I am now contempla ...

directive for a custom loader in ag-grid

I have incorporated ag-grid-angular into my project more than 20 times across different pages. Each time, I use a custom loader before the data is loaded. However, I would like to make this custom loader a directive so that it can be easily reused in all i ...

What is the reason for not hashing the password in this system?

My password hashing code using Argon2 is shown below: import { ForbiddenException, Injectable } from '@nestjs/common'; import { PrismaService } from 'src/prisma/prisma.service'; import { AuthDto } from './dto'; import * as arg ...

"Optimizing Performance: Discovering Effective Data Caching

As a developer, I have created two functions - one called Get to fetch data by id from the database and cache it, and another called POST to update data in the database. However, I am facing an issue where I need to cache after both the get and update oper ...

How can I implement a user notification service using rxjs within Angular?

As a newcomer to reactive programming, I am exploring ways to create an Angular service that can present notifications to the user. Check out what I have accomplished so far: https://stackblitz.com/edit/angular-rxjs-notifications?file=app%2Fapp.component. ...

Receive a notification when the div element stops scrolling

I am attempting to replicate Android's expandable toolbar within an Angular component. My HTML code appears as follows: <div (scroll)="someScroll($event)"> <div class="toolbar"></div> <div class="body"></div> </d ...

Can *ngFor in Angular handle asynchronous rendering?

Within the code snippet provided, the line this.images = response.map( r => r.url).slice(0,10); populates the images array. The data is then rendered in the view using ngFor. Following this, a jQuery function is invoked to initialize an image slider. Ho ...

Can the TypeScript Event class be customized and extended?

Snippet of Code: class CustomEvent extends Event { constructor(name) { super(name); } } var customEvent = new CustomEvent("scroll"); Error Encountered During Runtime: An error occurred: Uncaught TypeError: Failed to construct 'Ev ...

The message from NPM Install claims that "3 packages have been updated", but fails to specify which packages were actually updated

After running npm install @angular/[email protected], I expected only one package to be installed. However, NPM reports: @angular/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="1d7e70726d74717a6f307e71745d2c2d332d33">[e ...

A technique for deactivating reactive forms control within a nested formArray

Is there a way to selectively disable individual controls within the 'fields' group which is nested under this.form.get('groups').controls? I know how to disable an entire group by using this.form.get('groups').controls.disabl ...

Is there a convenient HTML parser that is compatible with Nativescript?

I have tested various libraries like Jquery, Parse5, and JsDom, but unfortunately they are not compatible with nativescript. Jquery relies on the DOM, while Parse5 and JsDom require Node.js which is currently not supported by nativescript. I am in need of ...

Invoke a custom AWS CodeBuild project in CodePipeline using a variety of parameters

Imagine having a CodePipeline with 2 stages structured like this: new codepipeline.Pipeline(this, name + "Pipeline", { pipelineName: this.projectName + "-" + name, crossAccountKeys: false, stages: [{ stageName: &apos ...

Executing functions in TypeScript

I am facing an issue while trying to call a function through the click event in my template. The error message I receive is "get is not a function". Can someone help me identify where the problem lies? This is my template: <button class="btn btn-prima ...