How can I conditionally use either [href] or [routerLink] on an anchor?

后端 未结 5 1737
暖寄归人
暖寄归人 2021-01-01 11:56

I need for the same anchor link to be pointed conditionally locally or to external resource. I tried



        
相关标签:
5条回答
  • 2021-01-01 12:03

    After a lot of investigation, I implemented a different approach to this issue, since my <a href>...</a>contains code inside (a clickable div for example). For instance, I don't want to use ngIf because that forces me to duplicate the div's inside code. So this is my solution:

    Component HTML

    <div>
       <a [href]="getRouterLink()" >
         <div class="section">
         <!-- stuff inside the div -->
         </div>   
       </a>
    </div>
    

    Component JS

    Component({
    selector: 'app-home-section',
    templateUrl: './home-section.component.html',
    styleUrls: ['./home-section.component.scss']
    })
    export class HomeSectionComponent implements OnInit {
    
    @Input() link: string;
    
    constructor(private router: Router) { }
    
    ngOnInit() {
    }
    
    isRouterLink() {
    
      if (this.link) {
        let firstLinkChar = this.link.charAt(0);
        let isSlash = firstLinkChar == '/';
        return isSlash;
      }
    
      return false;
    }
    
    getRouterLink() {
    
      let url = this.isRouterLink() ? window.location.origin + this.link : 'http://' + this.link;  
    return url;
     }
    }
    

    This was the only way to make it work simplier, because even I put the "www.example.com" directly to the href (with or without the [ ]), it always append the base url. It's not pretty, but is functional.

    0 讨论(0)
  • 2021-01-01 12:04

    The simplest way would be to use *ngIf / else:

    <ng-container *ngIf="outside; else internalBlock">
      <a [href]="externalUrl">External</a>
    </ng-container>
    
    <ng-template #internalBlock>
      <a [routerLink]="['/route', id]">Internal</a>
    </ng-template>
    

    EDIT#1: (Ugly workaround)

    Since you don't want to use *ngIf (I still don't understand why), you can do this:

    Template:

    <a href="javascript:void(0)" (click)="handleClick(outside, '/route', id, externalUrl)">Link</a>
    

    Component:

    handleClick(outside: boolean, internalUrl: string, internalId: string, externalUrl: string): void {
      if (outside) {
        window.location.href = externalUrl;
        // You can also use Location class of Angular
      } else {
        this.router.navigate([`${internalUrl}/${internalId}`]);
      }
    }
    
    0 讨论(0)
  • 2021-01-01 12:04

    For my use case, the content inside the <a> tags have some nested html within it so i have to create a custom componet for this.

    Basically what the component does is

    • use content transclusion
    • use ngTemplateOutlet with context
    • conditionally display either <a> tag with either routerLink directive or href.

    • route-or-redirect.component.ts
    import {
      Component,
      ContentChild,
      Directive,
      Input,
      OnInit,
      TemplateRef,
    } from '@angular/core';
    import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
    
    export interface Bookmark {
      id: string;
      title: string;
      imgUrl: string;
    }
    
    @Directive({
      selector: '[appLinkContent]',
    })
    export class RouteOrRedirectLinkContentDirective {}
    
    @Component({
      selector: 'app-router-or-redirect',
      templateUrl: './router-or-redirect.component.html',
      styleUrls: ['./router-or-redirect.component.scss'],
    })
    export class RouteOrRedirectComponent implements OnInit {
      @Input() route = '/';
      @Input() set externalLink(link: string) {
        this.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(link);
      }
      @Input() redirect = false;
      @Input() bookmark!: Bookmark;
      @ContentChild(RouteOrRedirectLinkContentDirective, { read: TemplateRef })
      linkContent: TemplateRef<RouteOrRedirectLinkContentDirective> | undefined;
    
      safeUrl: SafeResourceUrl | undefined;
    
      constructor(private sanitizer: DomSanitizer) {}
    
      ngOnInit(): void {}
    }
    
    • route-or-redirect.component.html
    <a [routerLink]="route" *ngIf="!redirect; else redirectLink">
      <ng-container
        *ngTemplateOutlet="linkContent; context: { $implicit: bookmark }"
      ></ng-container>
    </a>
    
    <ng-template #redirectLink>
      <a [attr.href]="safeUrl">
        <ng-container
          *ngTemplateOutlet="linkContent; context: { $implicit: bookmark }"
        ></ng-container>
      </a>
    </ng-template>
    
    • route-or-redirect.component.ts
    import { Component, OnInit, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
    import { async, ComponentFixture, TestBed } from '@angular/core/testing';
    import { By } from '@angular/platform-browser';
    import { RouterLinkWithHref, RouterModule } from '@angular/router';
    import { RouterTestingModule } from '@angular/router/testing';
    
    import {
      Bookmark,
      RouteOrRedirectComponent,
      RouteOrRedirectLinkContentDirective,
    } from './router-or-redirect.component';
    
    @Component({
      selector: 'app-test',
      template: `
        <app-router-or-redirect class="default-no-content"></app-router-or-redirect>
    
        <app-router-or-redirect class="default-with-content">
          <div *appLinkContent>test link</div>
        </app-router-or-redirect>
    
        <app-router-or-redirect
          class="use-route"
          route="/test"
          externalLink="localhost:4200"
        >
          <div *appLinkContent>test link</div>
        </app-router-or-redirect>
    
        <app-router-or-redirect
          class="use-redirect"
          route="/test"
          externalLink="localhost:4200"
          [redirect]="true"
        >
          <div *appLinkContent>test link</div>
        </app-router-or-redirect>
    
        <app-router-or-redirect
          class="link-with-context"
          route="/test"
          externalLink="localhost:4200"
          [redirect]="true"
          [bookmark]="bookmark"
        >
          <div *appLinkContent="let bookmark">
            <img [attr.src]="bookmark.imgUrl" />
            <h3>{{ bookmark.title }}</h3>
          </div>
        </app-router-or-redirect>
      `,
      styles: [],
    })
    export class TestRouterOrRedirectComponent implements OnInit {
      bookmark: Bookmark = {
        id: '1',
        title: 'Test Link',
        imgUrl: 'https://placeimg.com/640/480/any',
      };
    
      constructor() {}
    
      ngOnInit(): void {}
    }
    
    describe('RouterOrRedirectComponent', () => {
      let component: TestRouterOrRedirectComponent;
      let fixture: ComponentFixture<TestRouterOrRedirectComponent>;
    
      let defaultWithNoContent: RouteOrRedirectComponent;
      let defaultWithContent: RouteOrRedirectComponent;
      let useRoute: RouteOrRedirectComponent;
      let useRedirect: RouteOrRedirectComponent;
    
      beforeEach(async(() => {
        TestBed.configureTestingModule({
          schemas: [CUSTOM_ELEMENTS_SCHEMA],
          imports: [RouterModule, RouterTestingModule],
          declarations: [
            TestRouterOrRedirectComponent,
            RouteOrRedirectComponent,
            RouteOrRedirectLinkContentDirective,
          ],
        }).compileComponents();
      }));
    
      beforeEach(() => {
        fixture = TestBed.createComponent(TestRouterOrRedirectComponent);
        component = fixture.componentInstance;
    
        defaultWithNoContent = fixture.debugElement.children[0].componentInstance;
        defaultWithContent = fixture.debugElement.children[1].componentInstance;
        useRoute = fixture.debugElement.children[2].componentInstance;
        useRedirect = fixture.debugElement.children[3].componentInstance;
    
        fixture.detectChanges();
      });
    
      it('should create', () => {
        expect(component).toBeTruthy();
      });
    
      it('should default to link with route point to /', () => {
        const link = fixture.debugElement
          .query(By.css('.default-no-content'))
          .query(By.directive(RouterLinkWithHref));
    
        expect(link).toBeTruthy();
        expect(link.attributes.href).toBe('/');
      });
    
      it('should default to link with content and route points to /', () => {
        const link = fixture.debugElement
          .query(By.css('.default-with-content'))
          .query(By.directive(RouterLinkWithHref));
    
        expect(link.attributes.href).toBe('/');
        expect(link.nativeElement.textContent).toBe('test link');
      });
    
      it('should use routerLink for <a> tag if "redirect" binding is not specified', () => {
        const link = fixture.debugElement
          .query(By.css('.use-route'))
          .query(By.directive(RouterLinkWithHref));
    
        expect(link.attributes.href).toBe('/test');
        expect(link.nativeElement.textContent).toBe('test link');
      });
    
      it('should default "redirect" binding to false', () => {
        expect(useRoute.redirect).toBe(false);
      });
    
      it('should use href for <a> tag if "redirect" is true', () => {
        const link = fixture.debugElement
          .query(By.css('.use-redirect'))
          .query(By.css('a'));
    
        expect(useRedirect.redirect).toBe(true);
        expect(link.query(By.directive(RouterLinkWithHref))).toBeNull();
    
        expect(link.nativeElement.href).toBe('localhost:4200');
        expect(link.nativeElement.textContent).toBe('test link');
        expect(useRoute.redirect).toBe(false);
      });
    
      it('should use the bound value as the link template context', () => {
        const link = fixture.debugElement
          .query(By.css('.link-with-context'))
          .query(By.css('a'));
    
        expect(link.query(By.css('h3')).nativeElement.textContent).toContain(
          component.bookmark.title
        );
        expect(
          link.query(By.css('img')).nativeElement.getAttribute('src')
        ).toContain(component.bookmark.imgUrl);
        expect(useRoute.redirect).toBe(false);
      });
    });
    
    0 讨论(0)
  • 2021-01-01 12:15

    You can access routerLink instance by injecting RouterLinkWithHref in the directive.

    Directive:

    import { ElementRef, Optional, Input, Directive, OnChanges } from '@angular/core';
    import { RouterLinkWithHref } from '@angular/router';
    
    @Directive({
      selector: '[externalLink]'
    })
    export class ExternalLinkDirective implements OnChanges {
    
      @Input() externalLink: string;
    
      constructor(
        private el: ElementRef,
        @Optional() private link: RouterLinkWithHref
      ) {}
    
      ngOnChanges(){
    
        if (!this.link || !this.externalLink) {
          return;
        }
        this.el.nativeElement.href=this.link.href=this.externalLink;
    
        // Replace onClick
        this.link.onClick = (...args: any[]) => {
          return true;
        }
    
      }
    
    }
    

    Usage:

    <!-- Ignore router link and use external link -->
    <a routerLink="/some/path" externalLink="https://google.com">Link</a>
    

    0 讨论(0)
  • 2021-01-01 12:23

    For a conditional href, prepending on attr. before the href worked for me using null as a value, like this:

    [attr.href]="!item.subMenu ? item.url : null"
    
    0 讨论(0)
提交回复
热议问题