Changing the default name of "router-link-active" class by writing a custom directive that adds new class
Asked Answered
C

2

6

I would like to use Semantic UI in my Angular2 application. The problem is that I can't find a router setting that changes the default name of "router-link-active" class. I need it to be called just "active" to make menus display properly.

As I understand, such setting doesn't exist. I've seen it in Vue.JS so I expect it to be there too. Is it a good idea to ask developers to fix this?

So. We need to write a custom directive that adds "active" class to all DOM elements with "router-link-active" class, but I've got some problems here too.

There is a similar question but the answer is too complicated and didn't work for me. So I have read some documentation and decided to do something better like this:

commons.ts:

@Directive({
    selector: '.router-link-active',
    host: {'[class.active]': 'trueConst'} //just 'true' could also work I think...
})
export class ActiveRouterLinkClass {
    trueConst: boolean = true; //...if 'true' works we don't need this
}

Then I've imported ActiveRouterLinkClass into my main.component.ts and added it to the component's directives list. Unfortunately, now I have this error: "EXCEPTION: Unexpected directive value 'undefined' on the View of component 'Main'". Please, explain what I did wrong!

Crenelate answered 11/4, 2016 at 0:3 Comment(0)
T
4

Angular doesn't apply directives or components to selectors that are dynamically applied. If the class .router-link-active is added to a link is dynamic and therefore won't work.

What you can do instead is using a more generic selector like [routerLink] and than read whether .router-link-active is set using an @Input() and set the desired class using host binding.

@Directive({
  selector: '[routerLink]')
export class RouterLinkReplaceClass {
  // add class `my-active` when `myActiveClass` is `true`
  @HostBinding('class.my-active') 

  // read `router-link-active` class state
  @Input('class.router-link-active') 

  myActiveClass: bool = false;
}

Plunker example

See also In Angular 2 how do I assign a custom class to an active router link?

update

because myActiveClass isn't updated when the router-link-active class is added/removed I modified the directive to get the information about the active route the same way as the RouterLink directive:

import {ROUTER_DIRECTIVES, RouteConfig, Router, Instruction} from 'angular2/router';

@Directive({
  selector: '[routerLink]'
})
export class RouterLinkReplaceClass {

  //@Input('class.router-link-active')
  // myActiveClass: boolean = false;
  private _navigationInstruction: Instruction;
  @Input('routerLink')
  private _routeParams: any[];

  constructor(private _router: Router) {
    // we need to update the link whenever a route changes to account for aux routes
    this._router.subscribe((_) => this._updateLink());
  }

  private _updateLink(): void {
    this._navigationInstruction = this._router.generate(this._routeParams);
  }

  @HostBinding('class.my-active')
  get isRouteActive(): boolean {
    return this._navigationInstruction ? this._router.isRouteActive(this._navigationInstruction) : null;
  }
}
Trigger answered 11/4, 2016 at 4:21 Comment(5)
Thank you for such a quick answer! But your example looks a bit artificial because you set "router-link-active" manually. I've edited it to show what I'd like to get: plnkr.co/edit/UU0mFn?p=preview I expect that current active link will have both yellow background and red border but something goes wrong again. Is it possible at all?Crenelate
I tried to keep it simple. Thanks for your Plunker link. It's weird that the @Input() is not updated when the class is set by the router. Looks like a bug to me. I'm still investigating.Fixate
I can't believe changing a class name could be so difficult... Great work! But there is more to come.. I have one router-outlet inside another (plnkr.co/edit/pEAWUP?p=preview). I tried to leave or delete RouterLinkReplaceClass in Some's directives array but it doesn't help. Could you see it once again please?Crenelate
@GünterZöchbauer I've added updated answer to work with rc1. I'll delete it when you update yours, as credits go to you.Resonator
No worries. Thanks for the hint. No need to delete.Fixate
S
2

Based on Günter Zöchbauer's answer, after breaking changes in angular2 rc1, this is working for me now.

import { Directive, Input, HostBinding, OnInit } from '@angular/core';
import { Router, RouteSegment, UrlTree } from '@angular/router';

@Directive({
    selector: '[routerLink]'
})
export class RouterActiveClass implements OnInit {
    private currentUrl: UrlTree;
    @Input('routerLink') private routerLink: any[];

    constructor(private routeSegment: RouteSegment, private router: Router) {
        this.router.changes.subscribe(() => this.updateCurrentUrl());
    }

    private updateCurrentUrl(): void {
        this.currentUrl = this.router.createUrlTree(this.routerLink, this.routeSegment);
    }

    @HostBinding('class.active')
    get isRouteActive(): boolean {
        return this.currentUrl ? this.router.urlTree.contains(this.currentUrl) : null;
    }

    ngOnInit() {
        this.updateCurrentUrl();
    }
}
Spiegelman answered 15/5, 2016 at 12:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.