7

Task - Create a reusable button/anchor tag attribute selected component for an Angular library, with as much of the logic for the behavior tied up in the component itself not on the HTML markup.

HTML markup should be as clean as possible ideally

<a routerLink="" attributeSelectorForComponent></a>

Issue - Trying to prevent routerLink from firing when [attr.disabled] is present on the anchor tag, with a click listener.

  @HostListener('click', ['$event']) onMouseClick(event: Event) {
    event.stopImmediatePropagation();
    event.preventDefault()
  }

Removed the disabled logic from the equation for simplicity, routerLink is still fired regardless.

Proposed solutions - How can I conditionally disable the routerLink attribute? Doesn't really help address my issue, disabling pointer-events will only disable mouse events not keyboard events, and also prevents me from being able to remove the pointer-events: none with a mouseover, click ect requiring what would be a relatively convoluted solution to detect the disable attribute and remove the css accordingly, and in general seems like more a hacky solution than a correct one.

Guerric P
  • 30,447
  • 6
  • 48
  • 86
Munerz
  • 1,052
  • 1
  • 13
  • 26

3 Answers3

3

There is no solution that would lead to this markup <a routerLink="" attributeSelectorForComponent></a>.

The solution you tried won't work because stopImmediatePropagation is meant to prevent the event from bubbling, and preventDefault is meant to prevent the default browser behavior for this type of event (e.g: submit event will trigger a POST request). In either case, Angular will be notified of the event and will react accordingly.

A clean solution would have been possible if the RouterLink directive had a exportAs attribute. In that case, it would have been possible to control the RouterLink from a custom directive. Unfortunately, that is not the case (RouterLink source)

The only option left is to extend the RouterLinkWithHref directive like this:

@Directive({
  selector: "a[myRouterLink],area[myRouterLink]"
})
export class MyDirective extends RouterLinkWithHref implements OnChanges {
  @Input()
  myRouterLink;

  @Input()
  myDisabled;

  constructor(
    private myRouter: Router,
    private myRoute: ActivatedRoute,
    private myLocationStrategy: LocationStrategy,
    private host: ElementRef
  ) {
    super(myRouter, myRoute, myLocationStrategy);
  }

  ngOnChanges() {
    if (this.myDisabled) {
      this.host.nativeElement.setAttribute("disabled", "disabled");
      this.routerLink = null;
    } else {
      this.host.nativeElement.removeAttribute("disabled");
      this.routerLink = this.myRouterLink;
    }
  }
}

This gives the following markup: <a myRouterLink="a" [myDisabled]="disabled"></a>

Note that for a full working solution, you would have to also extend RouterLink

You can try the demo here: https://stackblitz.com/edit/angular-p2w4ff

Guerric P
  • 30,447
  • 6
  • 48
  • 86
0

Im not sure I fully grok what the question is asking. But you can decide whether the routerLink directive is rendered based on the disabled attribute.

In the template:

<a [attr.routerLink]="isDisabled ? null : ''" attributeSelectorForComponent></a>

Where is disable is the current state of the disabled attribute on the element. When the value is null, the routerLink directive will not be rendered on the element.

In your component file:

get isDisabled() {
 return ... // logic for getting element disabled state
}
C.OG
  • 6,236
  • 3
  • 20
  • 38
0

Following from Twisting Nethers suggestion, I've made what I believe to be a simpler directive that handles the problem.

import { Directive, Input, HostListener } from '@angular/core';
import { Router } from '@angular/router';

@Directive({
    selector: 'a[appNameRouterLink]'
})
export class DisableRouterDirective {
    @Input() appNameRouterLink: string;
    @Input() disabled: boolean;

    @HostListener('click')
    disableOrEnableRouterNavigation() {
        if (this.disabled && this.disabled === true) {
            return;
        } else {
            this.router.navigate([this.appNameRouterLink]);
        }
    }

    constructor(
        private router: Router
    ) {}

}

<a [appNameRouterLink]="'someRoute'" [disabled]="true">True</a>

Again feel like a bit of a work around but I'd say it effectively suppresses the routerLink from firing without a huge amount of write up, while keeping it within a directive so it's not tied down to one component.

Munerz
  • 1,052
  • 1
  • 13
  • 26