Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 215
malifa Avatar asked Apr 23 '16 17:04

malifa


3 Answers

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.

like image 152
Matteo Tosi Avatar answered Oct 22 '22 14:10

Matteo Tosi


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>
like image 4
Poul Kruijt Avatar answered Oct 22 '22 13:10

Poul Kruijt


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);
    }

}
like image 2
Ankit Singh Avatar answered Oct 22 '22 15:10

Ankit Singh