スタック+一方向チェーンはAnglar 11訪問者のブラウズ足跡を実現します.


アプリケーションでは、足跡をブラウズする必要があります.ナビゲーションバーの後退を実現し、ログイン後のジャンプは、404ページで、前のページに戻ります.ブラウズする時(バック動作でない場合)、データをスタックに押し込んで、バックするとスタックトップがイジェクトされます.一方向チェーンでデータを記憶し、ngx-webstorge-serviceでデータをクライアントに記憶する.データの結証は以下の通りです
//    
export interface TrackItem {
  //      
  previous: string;
  //      
  value: string;
}
A:保存
import { ChangeDetectionStrategy, Component, OnInit} from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styles: [``],
  changeDetection: ChangeDetectionStrategy.Default
})
export class AppComponent implements OnInit {
  constructor(
    private router: Router,
    private footMark: FootmarkTrackService) {
    this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe((event: any) => {
      //console.log('[App]prev url:', event.url);
      this.footMark.save(event.url);
    });
  }
}
Footmark TrackServiceのコードを添付します.
B:ナビの後退
後退は命令により実現され、a要素のクラス定義に含まれる限り、historyBack、404マッピングのテンプレート例:
 のページ
命令の定義は以下の通りです
import { Directive, HostListener } from '@angular/core';
import { Params, Router } from '@angular/router';
@Directive({
  selector: 'a.historyBack'
})
export class HistoryBackDirective {
  private currentURL: string;

  constructor(private router: Router, private footMark: FootmarkTrackService) {
    this.currentURL = router.url;
  }

  @HostListener('click', ['$event.target'])
  public backHistory($event: Event): void {
    let previousURL: string | null = this.footMark.getPrevious();
    let data: { path: string, queryParams: Params } = this.footMark.processURL(previousURL || '/home');
    this.router.navigate([data.path], { queryParams: data.queryParams });
  }
}
C:登録時にソースを取得する:Referer
import { Component, OnInit } from '@angular/core';
import { Params, Router } from '@angular/router';
@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styles: [``]
})
export class LoginComponent implements OnInit {
  public member: { names: string, pswd: string, redirect: string } = {
    names: '',
    pswd: '',
    redirect: ''
  };
  private previousUrl!: string | null;
  
  constructor(private router: Router, private footMark: FootmarkTrackService) {}

  ngOnInit(): void {
      this.previousUrl = this.footMark.getReferer();
  }
  //          
  private storeMember(): void {
     //ETC
     //      
     this.processRedirectURL(this.member.redirect || this.previousUrl);
  }
  private processRedirectURL(argDedirectURL: string | null): void {
    //     
    let redirectURL: string = argDedirectURL || '/home';
    let data: { path: string, queryParams: Params } = this.footMark.processURL(redirectURL);
    this.router.navigate([data.path], { queryParams: data.queryParams });
  }
}
D:Footmark TrackService
import { Injectable, Inject } from '@angular/core';
import { Params } from '@angular/router';
import { StorageService, SESSION_STORAGE } from 'ngx-webstorage-service';
@Injectable({
  providedIn: 'root'
})
export class FootmarkTrackService {
  private ftKey: string = 'ftStack';

  //            
  //  : 1)    .    ,    ; 2)       referer 
  constructor(@Inject(SESSION_STORAGE) private storage: StorageService) { }

  /**
   *   /  
   * @param url 
   */
  public save(url: string): void {
    let data: TrackItem[] = [];
    let lastEle: TrackItem | undefined = undefined;
    if (this.exist()) {
      data = this.get();
      lastEle = data[data.length - 1];
    }
    //           
    let previousURL: string = lastEle?.value ?? '';
    if (previousURL === url) { //      ;
      return;
    }
    let pr: TrackItem = { previous: previousURL, value: url };
    data.push(pr);
    this.storage.set(this.ftKey, data);
  }

  /**
   *       
   * :/member/login(|register|offline); :/404
   * @param url 
   * @returns
   */
  private isIgnoreURL(url: string): boolean {
    return url.startsWith('/member/login') || url.startsWith('/member/register') || url.startsWith('/member/offline') || url.startsWith('/404');
  }

  /**
   *         /     
   * @returns
   */
  private exist(): boolean {
    return this.storage.has(this.ftKey);
  }

  /**
   * (2)       Referer
   *   :LoginComponent.ngOnInit     ;  constructor            
   * @returns
   */
  public getReferer(): string | null {
    if (!this.exist()) {
      return null;
    }
    //
    let data: TrackItem[] = this.get();
    //  
    let lastEle: TrackItem | undefined = data[data.length - 1];
    return lastEle?.previous ?? null;
  }

  /**
   *        
   * @returns
   */
  private get(): TrackItem[] {
    return this.storage.get(this.ftKey);
  }

  /**
   * (1)       
   *   :        ,  :1>A->login, 2>login->A         A,   A(2>)           .getPreviousRef       
   * @returns
   */
  public getPrevious(): string | null {
    if (!this.exist()) {
      return null;
    }
    let data: TrackItem[] = this.get();
    //  
    let result: string | null = null;
    do {
      let lastEle: TrackItem | undefined = data.pop();
      if (lastEle && typeof (lastEle.previous) !== 'undefined') {
        result = lastEle.previous;
        if (this.isIgnoreURL(result)) {
          result = null;
        }
      }
    } while (result === null);
    //   
    this.storage.set(this.ftKey, data);
    return result;
  }

  /**
   * (1)               
   *   getPrevious                   
   * @param refUrl 
   * @returns
   */
  public getPreviousRef(refUrl: string): string | null {
    if (!this.exist()) {
      return null;
    }
    let data: TrackItem[] = this.get();
    //          
    let lastShowIndex: number = -1;
    for (let i: number = data.length - 1; i >= 0; i--) {
      if (data[i].previous === refUrl) {
        lastShowIndex = i;
        break;
      }
    }
    //    ,       
    if(lastShowIndex > 0){
      data = data.slice(0, lastShowIndex);
    }
    //     
    let lastEle: TrackItem | undefined = data.pop();
    let result: string | null = lastEle?.previous ?? null;
    //             
    if (result !== null && this.isIgnoreURL(result)) {
      result = data.pop()?.previous ?? null;
    }
    //   
    this.storage.set(this.ftKey, data);
    return result;
  }

  //  redirect
  public processURL(redirectURL: string): { path: string, queryParams: Params } {
    let p: any;
    let qs: Params = {};
    if (redirectURL.indexOf('?') == -1) {
      p = redirectURL;
    } else {
      p = redirectURL.substring(0, redirectURL.indexOf('?'));
      let queryString = redirectURL.substring(redirectURL.indexOf('?') + 1);
      if (queryString) {
        let segment: string[] = queryString.split('&');
        segment.forEach(ele => {
          let kv: string[] = ele.split('=');
          if (kv.length == 2) {
            qs[kv[0]] = kv[1];
          }
        });
      }
    }

    return { path: p, queryParams: qs };
  }
}
//    
export interface TrackItem {
  //      
  previous: string;
  //      
  value: string;
}
図: