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

Angular 2 BehaviorSubject filtering technique

I am attempting to filter a BehaviorSubject in Angular 2. The commissions object in the service is populated with data from an HTTP request. However, since it is initialized as "null", I am encountering an error at the line of code containing the filter me ...

Can a personalized component be displayed as a tooltip?

I find the existing tooltip design too complicated to work with. Are there any alternatives such as creating a new component to use instead? ...

What is the reason for TypeScript not displaying a type mismatch error in this particular instance?

After starting to use React with TypeScript, I defined my types as follows: type CardInfo = { cardIndex: null | number; title: string; note: string; _id: string; from: string; cardId: string; }; type ContentType = { title: string; note: st ...

Generate an object in Typescript that includes a dynamic property calculated from an input parameter

Is there a way to achieve the following function in TypeScript? function f<T extends 'a' | 'b'>(t : T): {[t]: ...} { return {[t]: ...} } This code is intended to make f('a') have type {'a': ...} and similarl ...

The connection status of socket.io is always inactive

At this moment, here is what I have. There are three different attempts within the constructor comments. Currently, when the frontend launches, it continuously tries to connect with the backend in socket.io. The backend does receive the connection, but th ...

Encountering an unexpected token while trying to use createUserWithEmailAndPassword in firebase/auth with Next.js and TypeScript left Jest puzzled

I have been working on integrating Firebase authentication into my Next.js project (using TypeScript), but it appears that there are configuration issues with Firebase causing my Jest tests to fail. Here are the key configuration files: jest.config.js : ...

What is the best way to organize objects based on their timestamps?

I am faced with the task of merging two arrays of objects into a single array based on their timestamps. One array contains exact second timestamps, while the other consists of hourly ranges. My goal is to incorporate the 'humidity' values from t ...

What is the best way to implement function chaining in TypeScript?

I'm interested in implementing function chaining in typescript. Let's consider a sample class: export class NumberOperator { private num; constructor(initialNum) { this.num = initialNum; } public add(inc = 1) { this.num += inc ...

Is Angular CLI incorrectly flagging circular dependencies for nested Material Dialogs?

My Angular 8 project incorporates a service class that manages the creation of dialog components using Angular Material. These dialogs are based on different component types, and the service class is designed to handle their rendering. Below is a simplifie ...

Dynamic value for href in div using Angular

Implementing a dynamic submenu using Angular is my current project. At the moment, I have set the href attribute with hardcoding as shown below: <ng-template #preSelectionMenuItem let-preSelections="preSelections"> <div class=' ...

Tips for implementing a real-time search feature in Angular

I require assistance. I am attempting to perform a live search using the following code: when text is entered into an input, I want my targetListOptions array, which is used in a select dropdown, to update accordingly. The code runs without errors, but not ...

Enhance your Vuex action types in Typescript by adding new actions or extending existing

I'm new to Typescript and I'm exploring ways to add specific type structure to all Actions declared in Vue store without repeating them in every Vuex module file. For instance, instead of manually defining types for each action in every store fi ...

Using ngClass with template literals to dynamically alter its value

Is there a way to dynamically change the classes applied to a div using variables in Angular? In this scenario, I am attempting to modify a class based on the value of a string variable called color by using string interpolation. However, this approach i ...

I am receiving null values for my environment variables

Seeking assistance with my angular 12 + node 14 project. Despite researching extensively, I keep encountering an undefined error when trying to utilize environment variables. I have placed a .env file in the same folder as my login.component.ts since my r ...

Test for comparing objects partially using Jasmine Array

Is there a specific method in jasmine for checking if an array partially matches another array by comparing objects? Considering that the arrays could potentially contain large amounts of data from test files, is there a way to avoid comparing each indivi ...

When using React MUI Autocomplete, make sure to handle the error that occurs when trying to filter options using the

I am trying to implement an autocomplete search bar that makes a custom call to the backend to search through a list of tickers. <Autocomplete multiple id="checkboxes-tags-demo" options={watchlis ...

How can I update a property within an object in a sequential manner, similar to taking turns in a game, using React.js?

I am currently working on a ReactJs project where I am creating a game, but I have encountered an issue. I need to alternate turns between players and generate a random number between 1 and 10 for each player, storing this random number inside their respec ...

Ways to address the issue arising from the utilization of the "as" keyword

Every time I encounter this issue - why must I always provide all the details? type Document = Record<string, any> type FilteredDocument<T extends Document> = {[key in keyof T as T[key] extends (()=>void) ? never : key]: T[key]} const ...

Using TypeScript and React: Implementing interfaces based on props conditions

I am currently designing a component that must either receive a prop named text or children, but not both or neither. ✓ Allow <NotificationBar text="Demo"/> <NotificationBar>Demo</NotificationBar> ✗ Disallow <NotificationBar/&g ...

There was an error in Angular at core.js:6150 stating that the object is not iterable due to a

I am facing an issue in displaying the First Name of a user in an HTML file getFirstName(id: any){ this.users = this.afs.collection('users', ref => ref.where("uid", "==", id)).valueChanges(); this.users.subscribe(users => { ...