本篇將介紹 Angular 4 的 Routing 實現 Single Page Application(SPA),以及 Angular 4 跟 ASP.NET Core 的 Routing 共存的方法。
前端 Angular 4 Routing 產生的虛擬 URL,並不是真的存在於 ASP.NET Core 的 Routing,所以重載頁面或從瀏覽器網址輸入,會變成 404 找不到網頁。
程式碼延續之前範例:
ASP.NET Core + Angular 4 教學 - Web API CRUD
1. Angular 4 Routing
我把上次範例中 app.component.*
CRUD 的部分,移出到 contacts.component.*
,並新增 contacts-list.component.*
及 error.component.*
。
這三個 contacts.component.*
、contacts-list.component.*
及 error.component.*
的程式碼會略過不說明,請直接參考底部的附件。
1.1 根路徑
首先我們要設定 Angular 4 Routing 的根路徑,在 wwwroot\index.html 的 <head>
加入下面這行:
設定成 /
的意思是在 Angular 4 切換不同頁面時,都會以 http://{doamin}/
為主,如果你的 Angular 4 進入點是 http://{doamin}/app/
,那 base 就要設定成 /app/
。
1.2 Route Maps
新增 wwwroot\app\route-maps.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| import { Route } from "@angular/router"; import { ContactsComponent } from "./contacts/contacts.component"; import { ContactsListComponent } from "./contacts/contacts-list.component"; import { ErrorComponent } from "./error.component";
interface RouteItem extends Route { menuName?: string; children?: RouteItem[]; }
const routes: RouteItem[] = [ { path: "", redirectTo: "contacts", pathMatch: "full", }, { path: "contacts", menuName: "Contacts", children: [ { path: "", component: ContactsListComponent, }, { path: "create", menuName: "New Contact", component: ContactsComponent, }, { path: ":id", component: ContactsComponent, } ] }, { path: "error", children: [ { path: "", component: ErrorComponent }, { path: ":id", component: ErrorComponent } ] }, { path: "**", redirectTo: "error" } ];
export class RouteMaps { public static getRoutes(): RouteItem[] { return routes; }
public static getComponents(): any[] { let result = this.findComponents(routes).filter((value, index, self) => { return self.indexOf(value) === index; }); return result; }
private static findComponents(routes: Route[]) { let arr: any[] = []; routes.forEach(item => { if (item.component != null) { arr.push(item.component); } if (item.children != null) { this.findComponents(item.children).forEach(com => { arr.push(com); }); } });
arr.filter((value, index, self) => { return self.indexOf(value) === index; }); return arr; } }
|
- RouteItem - 為了擴充 Route 的屬性,要用來產生 Menu,所以自訂了一個 RouteItem 繼承 Angular 本來的 Route。
- RouteMaps
- getRoutes - 取得 routes。其實也可以 export routes,但是我不喜歡,包裝看起來比較舒服。
- getComponents - 取得 routes 中全部的 Component。
- findComponents - 遞迴找出 routes 中全部的 Component,並且過濾掉重複的 Component。
新增 wwwroot\app\shared\menu.component.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { Component, ElementRef } from "@angular/core"; import { Router } from "@angular/router"; import { RouteMaps } from "../route-maps";
@Component({ selector: "menu-comp", template: require("./menu.component.html"), }) export class MenuComponent { menu: any[] = [];
constructor() { this.menu = RouteMaps.getRoutes(); } }
|
新增 wwwroot\app\shared\menu.component.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template ngFor let-item [ngForOf]="menu"> <li *ngIf="item.menuName"> <a [routerLink]="['/',item.path]"> {{item.menuName}} </a> <ul *ngIf="item.children"> <template ngFor let-sub [ngForOf]="item.children"> <li *ngIf="sub.menuName"> <a [routerLink]="sub.path?['/',item.path,sub.path]:['/',item.path]">{{sub.menuName}}</a> </li> </template> </ul> </li> </template>
|
1.4 Import routing
wwwroot\app\main.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; import { HttpModule } from "@angular/http"; import { FormsModule } from "@angular/forms"; import { Router, RouterModule } from "@angular/router"; import { AppComponent } from "./app.component"; import { RouteMaps } from "./route-maps"; import { MenuComponent } from "./shared/menu.component";
@NgModule({ imports: [ BrowserModule, HttpModule, FormsModule, RouterModule.forRoot(RouteMaps.getRoutes()) ], declarations: [ AppComponent, MenuComponent, RouteMaps.getComponents() ], bootstrap: [AppComponent] }) export class AppModule { }
platformBrowserDynamic().bootstrapModule(AppModule);
|
之所以會在 RouteMaps 寫 getComponents,就是不想在 declarations 一個一個輸入。
wwwroot\app\app.component.html
1 2 3 4 5
| <h1>Hello {{name}}</h1>
<menu-comp></menu-comp>
<router-outlet></router-outlet>
|
1.5 執行結果
1.6 重載頁面
此範例 Angular 4 進入點是 index.html,如果在 http://{doamin}/
以外的地方載入,就會變成 404 找不到網頁。
2. ASP.NET Core Routing
為了讓 Angular 4 Routing 可以正常運作,我們要在 Startup.cs 做一點手腳。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json.Serialization; using System.IO;
namespace MyWebsite { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvc().AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver()); }
public void Configure(IApplicationBuilder app) { app.Use(async (context, next) => { await next(); if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value)) { context.Request.Path = "/index.html"; context.Response.StatusCode = 200; await next(); } }); app.UseDefaultFiles(); app.UseStaticFiles(); app.UseMvc(); } } }
|
加入了一個 Middleware,當發生 StatusCode 404,並且 URL 不帶有附檔名時,就導頁到 index.html。
重載頁面
程式碼下載
asp-net-core-angular-web-api-crud