ngx-back-button
Version:
A library for handling a proper angular back button capability
200 lines (192 loc) • 8.2 kB
JavaScript
import * as i0 from '@angular/core';
import { InjectionToken, inject, signal, Injectable, HostListener, Input, Directive, makeEnvironmentProviders, provideEnvironmentInitializer } from '@angular/core';
import { Location } from '@angular/common';
import { Router, NavigationEnd } from '@angular/router';
import { filter, skip } from 'rxjs';
const NgxBackButtonServiceProvider = new InjectionToken('NgxBackButtonServiceConfig');
class NgxBackButtonService {
#router;
#location;
#rootConfig;
constructor() {
this.#router = inject(Router);
this.#location = inject(Location);
this.#rootConfig = inject(NgxBackButtonServiceProvider, { optional: true });
this._history = [];
this._navigatingBack = false;
this._$nextBackNavigationPath = signal(this._getFallBackNavigationPath(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "_$nextBackNavigationPath" }] : /* istanbul ignore next */ []));
this.$nextBackNavigationPath = this._$nextBackNavigationPath.asReadonly();
this.#router.events
.pipe(filter((e) => e instanceof NavigationEnd), skip(1))
.subscribe((event) => {
if (!this._navigatingBack)
this._history.push(event.urlAfterRedirects);
this._updateNextBackNavigationPath();
this._navigatingBack = false;
});
}
getHistory() {
return this._history;
}
/**
* @param fallback
* @param config Optional configuration to override the root configuration (typically from child routes)
* @return Boolean: True === Had an history to go back to
*/
back(fallback, config) {
this._navigatingBack = true;
const record = this._history.pop();
this._updateNextBackNavigationPath(fallback, config);
if (this._history.length > 0) {
this.#location.back();
return true;
}
else {
this._navigatingBack = false; // Give an element to go back to on next navigation
try {
window.history.replaceState(null, '', this._getFallBackNavigationPath(fallback, config));
}
catch (error) {
console.error('NgxBackButton: ' + error);
}
window.history.pushState(null, '', record ?? this.#router.url);
this.#location.back();
return false;
}
}
_getFallBackNavigationPath(fallback, config) {
const effectiveConfig = config || this.#rootConfig;
const rootUrl = effectiveConfig?.rootUrl || '';
const fallbackPrefix = effectiveConfig?.fallbackPrefix || '';
const fallbackPath = fallback || rootUrl;
if (fallbackPath.startsWith('../')) {
return this._resolveRelativeFallbackNavigationPath(fallbackPath);
}
return fallbackPrefix + fallbackPath;
}
_resolveRelativeFallbackNavigationPath(fallback) {
const suffixIndex = fallback.search(/[?#]/);
const path = suffixIndex === -1 ? fallback : fallback.slice(0, suffixIndex);
const suffix = suffixIndex === -1 ? '' : fallback.slice(suffixIndex);
const fallbackSegments = path.split('/');
let parentSegmentCount = 0;
while (fallbackSegments[0] === '..') {
fallbackSegments.shift();
parentSegmentCount++;
}
const currentPath = this.#router.url.split(/[?#]/, 1)[0];
const currentSegments = currentPath.split('/').filter(Boolean);
const retainedSegments = currentSegments.slice(0, Math.max(0, currentSegments.length - parentSegmentCount));
const appendedSegments = fallbackSegments.filter((segment) => segment && segment !== '.');
return `/${[...retainedSegments, ...appendedSegments].join('/')}${suffix}`;
}
_updateNextBackNavigationPath(fallback, config) {
const previousHistoryEntry = this._history[this._history.length - 2];
this._$nextBackNavigationPath.set(previousHistoryEntry ?? this._getFallBackNavigationPath(fallback, config));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: NgxBackButtonService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: NgxBackButtonService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: NgxBackButtonService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: () => [] });
class NgxBackButtonDirective {
#ngxBackButtonService = inject(NgxBackButtonService);
#config = inject(NgxBackButtonServiceProvider, { optional: true });
onClick() {
this.#ngxBackButtonService.back(this.ngxBackButton, this.#config);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: NgxBackButtonDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.7", type: NgxBackButtonDirective, isStandalone: true, selector: "[ngxBackButton]", inputs: { ngxBackButton: "ngxBackButton" }, host: { listeners: { "click": "onClick()" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: NgxBackButtonDirective, decorators: [{
type: Directive,
args: [{
selector: '[ngxBackButton]',
}]
}], propDecorators: { ngxBackButton: [{
type: Input
}], onClick: [{
type: HostListener,
args: ['click']
}] } });
/**
* Internal function to create environment providers for NgxBackButton configuration.
* @internal
*/
function createNgxBackButtonProviders(config) {
return makeEnvironmentProviders([
{
provide: NgxBackButtonServiceProvider,
useValue: config || {},
},
provideEnvironmentInitializer(() => inject(NgxBackButtonService)),
]);
}
/**
* Provides the NgxBackButton service with the given configuration.
* Use this in your root application providers.
*
* When both root and child configurations are provided, the child configuration
* will override the root configuration for routes under that path, following
* Angular's hierarchical dependency injection.
*
* @param config Configuration for the NgxBackButton service
* @returns Environment providers for NgxBackButton
*
* @example
* ```typescript
* bootstrapApplication(AppComponent, {
* providers: [
* provideNgxBackButton({
* rootUrl: '/home',
* fallbackPrefix: '/tabs'
* })
* ]
* })
* ```
*/
function provideNgxBackButton(config) {
return createNgxBackButtonProviders(config);
}
/**
* Provides child-level configuration for NgxBackButton service.
* Use this in lazy-loaded route providers to override the root configuration.
*
* This configuration will override the root configuration for all components
* and directives within the route's component tree, following Angular's
* hierarchical dependency injection.
*
* @param config Configuration for the NgxBackButton service at child level
* @returns Environment providers for NgxBackButton child configuration
*
* @example
* ```typescript
* export const routes: Routes = [
* {
* path: 'child',
* providers: [
* provideNgxBackButtonChild({
* rootUrl: '/login'
* })
* ],
* loadComponent: () => import('./child.component')
* }
* ]
* ```
*/
function provideNgxBackButtonChild(config) {
return createNgxBackButtonProviders(config);
}
/*
* Public API Surface of ngx-back-button
*/
/**
* Generated bundle index. Do not edit.
*/
export { NgxBackButtonDirective, NgxBackButtonService, NgxBackButtonServiceProvider, provideNgxBackButton, provideNgxBackButtonChild };
//# sourceMappingURL=ngx-back-button.mjs.map