Angular 2 - Check current active route “name”

2019-02-02 02:19发布

I am having an Angular 2 application with several nested children view. But it will be displayed on the same page though several router-outlet.

const routes: Routes = [
    {
        path: 'queue/:date/:offset/:type',
        component: BundleListComponent,
        resolve: {
            response: BundleListResolve
        },
        children: [
            {
                path: ':bundleId', component: PointListComponent,
                resolve: {
                    response: PointListResolve
                },
                children: [
                    {
                        path: ':pointId', component: TaskListComponent,
                        resolve: {
                            response: TaskListResolve
                        },
                        children: [
                            {
                                path: ':taskId', component: TaskDetailComponent,
                                resolve: {
                                    response: TaskDetailResolve
                                }
                            },
                            { path: '', component: EmptyComponent }
                        ]
                    },
                    { path: '', component: EmptyComponent }
                ]
            },
            { path: '', component: EmptyComponent }
        ]

    },
    {
        path: 'queue',
        component: QueueRedirectComponent
    }
}

So basically I can travel through the list of route

  • /queue
  • /queue/:date/:offset/:type
  • /queue/:date/:offset/:type/:bundleId
  • /queue/:date/:offset/:type/:bundleId/:pointId
  • /queue/:date/:offset/:type/:bundleId/:pointId/:taskId

For example

#/queue/2017-01-05/480/20/57f4c26507b36e3684007b52/1/57fb0abb07b36e39d8e88df8/1

Imagine you have a page with some element:

  1. One UI portion showed a movie list
  2. The other portion shows a movie detail when clicking into an item in a movie list but display on the same page.
  3. Another portion to show a character detail when clicking into character name on movie detail, and also show on the same page.
  4. etc...

Basically, I can still click into movie list while I am viewing a character detail.

Searching for defining the name for each route but seem all the answer report this feature has already removed from Angular 2 final. In Angular 1 using UI Router, I can define the name for each route and can get the route very easily with built-in function state.is(ROUTE_NAME).

So what I am doing now is base on the window.location to get the URL and splitting this string by / to get the number of parameters. But It's more hard-coded one.

Any experience on doing the same? How I can distinguish which route is currently active?

5条回答
劳资没心,怎么记你
2楼-- · 2019-02-02 02:45

my answer is similar, but did in a different way, so I think it's good to post

What is different: I don't need to change anything on my routes, I did a service to track the deeper ActivatedRoute (inside or firstChild...firstChild)

create the service

import { Injectable } from '@angular/core';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';

@Injectable()
export class ActivatedRouteService {
  private _deeperActivatedRoute: ActivatedRoute;
  get deeperActivatedRoute(): ActivatedRoute {
    return this._deeperActivatedRoute;
  }

  constructor(private router: Router, private route: ActivatedRoute) {}

  init(): void {
    this.router.events.subscribe(event => {
      if (event instanceof NavigationEnd) {
        // Traverse the active route tree
        let activatedChild = this.route.firstChild;
        if (activatedChild != null) {
          let nextActivatedChild;
          while (nextActivatedChild != null) {
            nextActivatedChild = activatedChild.firstChild;
            if (nextActivatedChild != null) {
              activatedChild = activatedChild.firstChild;
            }
          }
        }

        this._deeperActivatedRoute = activatedChild || this.route;
      }
    });
  }
}

then in app.component.ts I start the service (just to ensure it's always tracking)

export class AppComponent {
  constructor(private activatedRouteService: ActivatedRouteService) {
    this.activatedRouteService.init();
  }
}

and finally, take your route wherever service you are:

export class ForbiddenInterceptor implements HttpInterceptor {
  constructor(private activatedRouteService: ActivatedRouteService) { }

  doYourStuff(): void {
       //you'll have the correct activatedRoute here
       this.activatedRouteService.deeperActivatedRoute;
  }
}

answering the question, you can just take the deeperActivatedRoute and check normally the snapshop.url, just as you would do in a component

查看更多
forever°为你锁心
3楼-- · 2019-02-02 02:46

I believe there is an issue with Scott's answer where he uses the ActivatedRoute inside the constructor of the service. This route won't get updated.

I thought of another solution which might peek your interest. It again comes down on using the data property on the routes, but now using another resolve service:

You are going to need a RouterConfig like this, where for each route you add the state: StateResolve and a data object containing the state name:

const routes: RouterConfig = [{
    path: 'queue/:date/:offset/:type',
    component: BundleListComponent,
    resolve: {
       response: BundleListResolve,
       state: StateResolve
    },
    data: {
       state: 'Bundle'
    },
    ...
]

don't forget to add the StateResolve service to the providers array

Your StateResolve service will look something like this:

@Injectable()
export class StateResolve implements Resolve<string> {

   constructor(private stateService: StateService) {}

   resolve(route: ActivatedRouteSnapshot): string {
       let state: string = route.data['state']
       this.stateService.setState(state);
       return state;
   }
}

Obviously you will need a StateService which has the setState method, but I guess from here it's pretty self-explanatory.

Perhaps using a resolve guard is a bit eccentric, but if you think about it, you are trying to resolve data before you show the route. In this case, the state inside the data variable, so it does make sense to use the Resolve to access the data property

查看更多
叛逆
4楼-- · 2019-02-02 02:48

Create a service called ActiveState which will subscribe to changes to the router, using router.events.subscribe:

import {Injectable} from "@angular/core";
import {Router, ActivatedRoute, NavigationEnd} from "@angular/router";

@Injectable()
export class ActiveState {

  public name : string;

  constructor(router : Router, route : ActivatedRoute)
  {
    router.events.subscribe(event => {
      if(event instanceof NavigationEnd){

        // Traverse the active route tree
        var snapshot = route.snapshot;
        var activated = route.firstChild;
        if(activated != null) {
          while (activated != null) {
            snapshot = activated.snapshot;
            activated = activated.firstChild;
          }
        }

        // Try finding the 'stateName' from the data
        this.name = snapshot.data['stateName'] || "unnamed";
      }
    });
  }

  is(name : string) : boolean
  {
    return this.name === name;
  }
}

Then on your route we add a simple value on the data param of the route called stateName for each state we want to name:

const routes: Routes = [
{
    path: 'queue/:date/:offset/:type',
    component: BundleListComponent,
    resolve: { response: BundleListResolve }
    data: { stateName: 'Bundle' },
    children: [
        {
            path: ':bundleId', component: PointListComponent,
            resolve: { response: PointListResolve },
            data: { stateName: 'Point'}
        }
    ...

Then when you inject state : ActiveState you can simple test the value state.is("Point")

查看更多
5楼-- · 2019-02-02 02:48
  • I would create a simple service that tracks the active state.
  • This service can be injected where needed to get or set the active state.
  • You are already using Resolvers, so you could set the state identifier in there.

Create a Service called ActiveState:

import {Injectable} from "@angular/core";
import {Observable} from "rxjs";

@Injectable()
export class ActiveState {

  public current : Observable<string>;
  private observer : any;

  constructor()
  {
    // Create an observable (it can be subscribed to)
    this.current = new Observable(observer => {
      this.observer = observer;
      observer.next('Unknown'); // The default unknown state
    });
  }

  setActive(name : string) : void
  {
    this.observer.next(name);
  }
}

In your resolvers such as PointListResolve ... TaskListResolve etc.

import {Resolve, ActivatedRouteSnapshot} from "@angular/router";
import {Injectable} from "@angular/core";
import {Observable} from "rxjs";
import {ActiveState} from "services/ActiveState.service"; 

@Injectable()
export class PointListResolver implements Resolve<any> {

  // Inject the ActiveState in your constructor
  constructor(private state : ActiveState) {}

  resolve(route: ActivatedRouteSnapshot): Observable<any> {
    // Set the active state name
    this.state.setActive("Point"); // We are here: /queue/:date/:offset/:type/:bundleId/:pointId

    // Do your regular resolve functionality (if you don't need to resolve, this blank resolver of an empty object will work)
    // ...
    return Observable.of({});
  }
}

So in the other resolvers update the this.state.setActive("") value as required.


Then to determine which state you are in, inject ActiveState where you want to use the current state, such as in a @Component, i.e.

import {Component, OnDestroy} from '@angular/core';
import {ActiveState} from "services/ActiveState.service";

@Component({
  selector: 'my-current-state-component',
  template: `The current state is: {{stateName}}`,
})
export class MyCurrentStateComponent implements OnDestroy {

  public stateName : string;
  private sub : Subscription;

  // Inject in ActiveState
  constructor(private state : ActiveState)
  {
    // Subscribe to the current State
    this.sub = state.current.subscribe(name => {
      this.stateName = name;

      // Other logic to perform when the active route name changes
      ...
    });
  }

  ngOnDestroy()
  {
     this.sub.unsubscribe();
  }
}

Notes:

  • Don't forget to register your ActiveState service as a Provider in:

    @NgModule({
      ...
      providers:[ActiveState]
      ...
    })
    export class AppModule { }
    
  • Simpler - Non-Observable Version I've used an Observable<string> so changes to the active state can be subscribed to, but this could be simplified to just be a string if you don't want that functionality:

    import {Injectable} from "@angular/core";
    
    @Injectable()
    export class ActiveState {
    
      public current : string;
    
      setActive(name : string) : void
      {
        this.current = name;
      }
    
      is(name : string) : boolean
      {
        return name == this.current;
      }
    }
    

    Then when you inject state : ActiveState you can simple test the value state.is("Point")

I hope that's useful.

查看更多
看我几分像从前
6楼-- · 2019-02-02 03:02

name was removed quite some time ago from routes, but routes allow to add arbitrary data

const routes: RouterConfig = [
    {
        path: '',
        redirectTo: '/login',
        pathMatch: 'full',
    },
    {
        path: 'login',
        component: LoginComponent,
        data: {title: 'Login'}
    },
    {
        path: 'home',
        component: HomeComponent,
        data: {title: 'Home'}
    },
    {
        path: 'wepays',
        component: WePaysComponent,
        data: {title: 'WePays'}
    }
];

This code constructs a title from the names of all route segments. This could probably be simplified for your use case.

export class AppComponent { 
  constructor(titleService:Title, router:Router, activatedRoute:ActivatedRoute) {
    router.events.subscribe(event => {
      if(event instanceof NavigationEnd) {
        var title = this.getTitle(router.routerState, router.routerState.root).join('-');
        console.log('title', title);
        titleService.setTitle(title);
      }
    });
  }

  // collect that title data properties from all child routes
  // there might be a better way but this worked for me
  getTitle(state, parent) {
    var data = [];
    if(parent && parent.snapshot.data && parent.snapshot.data.title) {
      data.push(parent.snapshot.data.title);
    }

    if(state && parent) {
      data.push(... this.getTitle(state, state.firstChild(parent)));
    }
    return data;
  }
}

See also Changing the page title using the Angular 2 new router

查看更多
登录 后发表回答