﻿# 在 Angular 套版時應注意「空連結」或「多層次選單」的使用

我們許多專案，都會有網頁設計師設計版型，然後再交由前端工程師套版。不過有很多版型，都會留有 `<a href="#"></a>` 這樣的空連結，比較常見的用途都是在「多層次選單」的上層連結。但這樣的連結，在套用至多頁面路由的 SPA 應用程式中時，就會出現問題。由於 SPA 網頁在設計的時候，都會套用 `<base href="...">` 元素，設定網頁中所有連結的基礎位址 (Base URL)，當使用者點擊 `<a href="#">` 這樣的連結時，就會導致網頁重新整理！本篇文章將解說幾個可能的解決方案。

### 避免觸發超連結事件的幾種做法

我們一般比較常見的寫法有幾種：

1.  直接使用 `#` 當作超連結
    
    這種技巧對於靜態版型還可行，許多網頁設計師也很習慣這樣寫，但是當使用者點擊連結後，卻會在目前網址後方加上一個 `#` 符號。一來，我個人相當不喜歡這種作法，因為網址會發生改變；二來，這種寫法會讓 SPA (單一頁面應用程式) 的路由發生異常，所以應該避免。
    
2.  使用特殊的 `javascript:void(0)` 當作超連結
    
    這種寫法通常是前端工程師以及對 UX 特別要求的設計師愛用，原因無他，就是網址列不會出現怪怪的 `#` 符號。這種寫法同時也對 SPA 網頁應用程式相當友善，所以推薦使用。但唯一的缺點，就是比 `#` 多打太多字了！
    
3.  使用特殊的 `javascript:;` 當作超連結
    
    這種寫法跟 `javascript:void(0)` 如出一轍，但硬是少打了 6 個字元，如果學會他，你這輩子可能有機會打超過 1000 次這種連結，就可以少打 6000 個按鍵，不無小補啦。
    

### 透過 Angular 自訂指令元件 (Directive) 實現

身為前端工程師，在 JavaScript 可以控制到的地方，幾乎沒有不可能的事。所以我們當然可以透過 Angular 自訂 Directive 來將許多版型常見的 `<a href="#">` 連結，自動修復成沒有問題的連結，好讓設計師們不用改變長久以來的設計習慣，繼續使用 `#` 當成預設超連結。

你可以先建立一個 `a.directive.ts` 檔案，放在 `/src/app/` 目錄下：

```typescript
import { Directive, HostListener } from '@angular/core';

@Directive({
  selector: 'a[href="#"]'
})
export class ADirective {
  @HostListener('click', ['$event'])
  onClick(e) {
    e.preventDefault();
  }
}
```

接著，再到 `AppModule` 模組中，將上述 `ADirective` 元件類別加入到 `@NgModule` 裝飾器的 `declarations` 屬性中，如下範例：

```typescript
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { HelloComponent } from './hello.component';

import { ADirective } from './a.directive';

@NgModule({
  ...
  declarations: [AppComponent, HelloComponent, ADirective],
  bootstrap: [AppComponent]
  ...
})
export class AppModule { }
```

就這樣簡單兩個步驟，即可讓你所有的 Angular 元件樣板，不用再害怕出現有 `#` 超連結的情況。

**相關連結**

-   [本文章的完整範例專案](https://stackblitz.com/edit/angular-a-directive?file=src%2Fapp%2Fa.directive.ts) (可線上測試執行)
-   [Angular - Attribute Directives](https://angular.io/guide/attribute-directives)
-   [Angular - HostListener](https://angular.io/api/core/HostListener)
-   [HostListener/HostBinding decorators versus host metadata](https://angular.io/guide/styleguide#hostlistenerhostbinding-decorators-versus-host-metadata)
