可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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:
- One UI portion showed a movie list
- The other portion shows a movie detail when clicking into an item in a movie list but display on the same page.
- Another portion to show a character detail when clicking into character name on movie detail, and also show on the same page.
- 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?
回答1:
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
回答2:
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")
回答3:
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
回答4:
- 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.
回答5:
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