简体   繁体   中英

How to manipulate a component on specific routes in Angular2

I have a simple TopbarComponent which basically adds a bootstrapish navbar at the top of my view.

Since 90% of my templates should have this directive included, i want to handle it through my app.component which looks like this:

import ...;

@Component({
    selector: 'my-app',
    templateUrl: 'app/app.component.html',
    directives: [ROUTER_DIRECTIVES, TopbarComponent, ...],
    providers: [ROUTER_PROVIDERS, ...]
})

@RouteConfig([
{
    path: '/login',
    name: 'Login',
    component: LoginComponent
},
{
    path: '/dashboard',
    name: 'Dashboard',
    component: DashboardComponent,
    useAsDefault: true
}
])

with its template looking like this:

<my-topbar></my-topbar>

<div class="container-fluid">
    <div class="row">
        <router-outlet></router-outlet>
    </div>
</div>

Now i want use ngIf (or any other way except of hiding it with css) to hide the topbar on several routes, like /login for example. I tried several approaches using @CanAcitvate() or implementing OnActivate in my LoginComponent (as well as trying it from my AppComponent ) but with no effects (having problems to even get the functions to fire). The closest i got was using the Router directly in my AppComponent like this:

export class AppComponent implements OnInit{
    showTopbar:boolean;

    constructor(private _router:Router) {}

    ngOnInit():any {
        this.showTopbar = this._router.lastNavigationAttempt != '/login';
    }
}

and in my app.component.html i changed the directive to <my-topbar *ngIf="showTopbar"></my-topbar>

But this only works on initial load of my app, cause ngOnInit isn't fired on each state change. Is there a similar method i can use (and just can't find) or am i moving in the wrong direction here?


Edit:

At the moment, the answer of PierreDuc doesn't work for me, but i tried a similar approach using location.path() like this:

constructor(private _location:Location) {}

private get _hideTopbar() : boolean {
    switch(this._location.path()){
        case '/register':
        case '/login':
            return true;
        default:
            return false;
    }
};

Too bad that i can't use the data property in the @RouteConfig in this approach. It would feel better.

I've crashed my head on a similar problem for a while. The solution that i've found could be seen as tricky at first, but can resolve many similar problems of this type for me in the future.

Basically, you have to make the router-outlet emit an event on route change, having your AppComponent listening to that custom event.

The first step is to create a custom router-outlet:

@Directive({
selector: 'custom-router-outlet'
})
export class CustomRouterOutlet extends RouterOutlet {
    private parentRouter:Router;

    constructor(_elementRef: ElementRef,
            _loader: DynamicComponentLoader,
            _parentRouter: Router,
            @Attribute('name') nameAttr: string) {
    super(_elementRef, _loader, _parentRouter, nameAttr);
    this.parentRouter = _parentRouter;
    }

    activate(nextInstruction: ComponentInstruction): Promise<any> {
        //***YOUR CUSTOM LOGIC HERE***
        return super.activate(nextInstruction);
    }
}

This is a very basic implementation of a custom router-outlet. Your logic have to be implemented in the activate method, called on each route change. In this method you can check the data property in your route.

The main problem is that, right now, Directives can't emit events... They accept only inputs, no outputs can be fired...

I've found a workaround to this problem: creating a Service for communication between Components and Directives using EventEmitter :

@Injectable()
export class PubsubService {
    private _pubSubber: EventEmitter<any> = new EventEmitter();

    routeisChanging(obj:any) {
        this._pubSubber.emit(obj);
    }

    onRouteChanged() {
        return this._pubSubber;
    }
}

The AppComponent subscribes to the onRouteChanged event:

subscription:any;
constructor(private _pubsubService:PubsubService) {
    this.subscription = this._pubsubService.onRouteChanged().subscribe(data => {
        /**YOUR CUSTOM LOGIC HERE*/});
}

The CustomRouterOutlet fires the event in the activate method when needed:

activate(nextInstruction: ComponentInstruction): Promise<any> {
    //***YOUR CUSTOM LOGIC HERE***
    this._pubsubService.routeisChanging(nextInstruction.routeData.data['hideTopbar']);
    return super.activate(nextInstruction);
}

In this way you can easily implement a communication between the router and the AppComponent , with any logic.

You have to remember to inject the communication Service in the RootComponent of your app.

Not sure if it is the right way, but you can add any data in the data parameter of the @RouteConfig() object. For instance you can place in the RouteDefinition of Login an object with a hideTopbar setting. You would only have to place this in a route if it should be set to true:

warning, untested code ahead :)

@RouteConfig([{
    path: '/login',
    name: 'Login',
    component: LoginComponent,
    data : {hideTopbar : true}
},
//...
])

You can access this data in the AppComponent class like:

export class AppComponent {

    private get _hideTopbar() : boolean {
       return this._data.get('hideTopbar');
    };

    constructor(private _data:RouteData) {}

}

And change the AppComponent template to:

<my-topbar *ngIf="!_hideTopbar"></my-topbar>

<div class="container-fluid">
    <div class="row">
        <router-outlet></router-outlet>
    </div>
</div>

Almost there, try this

Template

<my-topbar *ngIf="shouldShowTopbar()">
</my-topbar>

App Component

export class AppComponent implements OnInit{

    hideWhen: Array<string> = ['Login', 'Help', 'SomeOtherRoute'];
       // put all the route names where you want it hidden in above array

    constructor(private _router:Router) {}

    shouldShowTopbar() {
     return (hideWhen.indexOf(this._router.currentInstruction.component.routeName) > -1);
    }

}

I has a similar problem. Maybe this is not the best way. But I solved it this way:

I created a new component. This component handles all the routes that need the navbar. The other routes are handled by app.component .

Here is some code to understand:

app.component

@RouteConfig([
{
    path: '/...',
    name: '...',
    component: MyComponentWhoAddsNavbar,
    useAsDefault: true
},
{   path: '/login',
    name: 'Login',
    component: LoginComponent
}
])


@Component({
selector: 'my-app',
template: '<router-outlet></router-outlet>', 
})

export class AppComponent {}

Here the MyComponentWhoAddsNavbar

@RouteConfig([
    put your routes here who need a navbar
])

@Component({
    selector: '...',
    template: `
        <navbar></navbar>
        <router-outlet></router-outlet> 
    `,

 })

export class MyComponentWhoAddsNavbar{} 

I'm working in

"@angular/common": "~2.1.0"

The way that I solved this issue was to created an application-wide data object global and a service for it GlobalService . Any component can subscribe() to all or part of the global object. For your purposes, you should create a boolean property isTopBar: true . You can subscribe your <topBar> component to it and use that value to toggle it.

isTopBar: boolean;

ngOnInit(){
  this._globalService.getIsTopBar().subscribe(isTopBar => this.isTopBar = isTopBar)
}

<my-topbar *ngIf="isTopBar"></my-topbar>

A big advantage that I've found to this kind of 'GlobalService' is that I can save a lot of state information to my backend. It's less useful in you example, but imaging that you have an internal set of tab; you can save which tab the user last selected and reload to that view when they revisit the page at a later date.

This is probably not the right way to do it at all, but it worked for me. It's not changing the visibility depending on the route itself, but on the Component shown in that route.

I was already using a service to feed the Navigation Bar with all the sections, and so on, so I thought it could also tell the navigation bar when to appear or not.

I basically created a subject in this service determining if it should be visible or not, with a series of methods to change this visibility.

@Injectable()
export class NavigationService {

  mobileNavbarIsVisible = true;
  mobileNavbarIsVisibleChanged = new Subject<boolean>();

  constructor() { }

  getSections() {
    return this.sections;
  }

  getMobileNavbarIsVisible() {
    return this.mobileNavbarIsVisible;
  }

  hideMobileNavbar() {
    this.mobileNavbarIsVisible = false;
    this.mobileNavbarIsVisibleChanged.next(this.mobileNavbarIsVisible);
  }

  showMobileNavbar() {
    this.mobileNavbarIsVisible = true;
    this.mobileNavbarIsVisibleChanged.next(this.mobileNavbarIsVisible);
  }

}

Then, I call those methods in the components that affect visibility -in my case, everytime I edit an item in a list of items, I want the navigation bar to disappear-. I call the hide method when this component is initiated, and the show method when it is destroyed.

@Component({
  selector: 'app-item-edit',
  templateUrl: './item-edit.component.html',
  styleUrls: ['./item-edit.component.css']
})
export class ItemEditComponent implements OnInit, OnDestroy {

  constructor(private navigationService: NavigationService) {}
  ngOnInit() {
    this.navigationService.hideMobileNavbar();
  }
  ngOnDestroy() {
    this.navigationService.showMobileNavbar();
  }
}

Then, my NavbarComponent is listening to that subject, and simply appears or not depending on that value:

@Component({
  selector: 'app-mobile-navbar',
  templateUrl: './mobile-navbar.component.html',
  styleUrls: ['./mobile-navbar.component.css']
})
export class MobileNavbarComponent implements OnInit {
  isVisible = true;
  ngOnInit() {
    this.isVisible = this.navigationService.getMobileNavbarIsVisible();
    this.navigationService.mobileNavbarIsVIsibleChanged.subscribe(
      (visibilityValue) => this.isVisible = visibilityValue
    );
  }
}

As I said, it's most likely not the right or the most efficient way to do it, but it works if you only want to hide the navigation bar when other component is initiated, because they simply use the Navigation Service as a way of communicating.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM