Маршрутизация позволяет сопоставлять запросы к приложению с определенными ресурсами внутри приложения. Рассмотрим, как использовать маршрутизацию Angular в связке с ASP.NET Core. Возьмем, к примеру приложение из прошлой темы:
В частности, в проекте определен контроллер ProductController:
using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Mvc; using HelloAngularApp.Models; namespace HelloAngularApp.Controllers { [ApiController] [Route("api/products")] public class ProductController : Controller { ApplicationContext db; public ProductController(ApplicationContext context) { db = context; if (!db.Products.Any()) { db.Products.Add(new Product { Name = "iPhone X", Company = "Apple", Price = 79900 }); db.Products.Add(new Product { Name = "Galaxy S8", Company = "Samsung", Price = 49900 }); db.Products.Add(new Product { Name = "Pixel 2", Company = "Google", Price = 52900 }); db.SaveChanges(); } } [HttpGet] public IEnumerable>Product< Get() { return db.Products.ToList(); } [HttpGet("{id}")] public Product Get(int id) { Product product = db.Products.FirstOrDefault(x =< x.Id == id); return product; } [HttpPost] public IActionResult Post(Product product) { if (ModelState.IsValid) { db.Products.Add(product); db.SaveChanges(); return Ok(product); } return BadRequest(ModelState); } [HttpPut] public IActionResult Put(Product product) { if (ModelState.IsValid) { db.Update(product); db.SaveChanges(); return Ok(product); } return BadRequest(ModelState); } [HttpDelete("{id}")] public IActionResult Delete(int id) { Product product = db.Products.FirstOrDefault(x =< x.Id == id); if (product != null) { db.Products.Remove(product); db.SaveChanges(); } return Ok(product); } } }
И в папке ClientApp/src/app в файле data.service.ts определен сервис, который отправляет запрос к этому контроллеру:
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Product } from './product'; @Injectable() export class DataService { private url = "/api/products"; constructor(private http: HttpClient) { } getProducts() { return this.http.get(this.url); } createProduct(product: Product) { return this.http.post(this.url, product); } updateProduct(product: Product) { return this.http.put(this.url, product); } deleteProduct(id: number) { return this.http.delete(this.url + '/' + id); } }
Вначале добавим в папку ClientApp/src/app новый файл product-detail.component.ts, который будет выводить данные об одном товаре:
import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { DataService } from './data.service'; import { Product } from './product'; @Component({ templateUrl: './product-detail.component.html', providers: [DataService] }) export class ProductDetailComponent implements OnInit { id: number; product: Product; loaded: boolean = false; constructor(private dataService: DataService, activeRoute: ActivatedRoute) { this.id = Number.parseInt(activeRoute.snapshot.params["id"]); } ngOnInit() { if (this.id) this.dataService.getProduct(this.id) .subscribe((data: Product) => { this.product = data; this.loaded = true; }); } }
Здесь мы предполагаем, что этому компоненту через параметр маршрута будет передаваться идентификатор товара, информацию о котором надо отобразить.
В конструкторе через объект типа ActivatedRoute (activeRoute.snapshot.params["id"]
) мы можем получить значение id товара и сохранить его в переменную.
В данном случае параметр маршрута будет также называться "id".
Затем в методе ngOnInit()
отправляем серверу запрос, в который передаем ранее полученный id, и в качетве ответа от сервера
получаем нужный нам товар. А с помощью дополнительной переменной loaded
мы можем указать, что данные загружены.
Для вывода информации о товаре компонент будет использовать файл product-detail.component.html. Поэтому добавим этот файл в папку ClientApp/src/app и определим в нем следующее содержимое:
<div *ngIf="loaded"> <h2>Модель {{product.id}}</h2> <div> <p><b>Модель:</b> {{product.name}}</p> <p><b>Производитель:</b> {{product.company}}</p> <p><b>Цена:</b> {{product.price}}</p> <p><a routerLink="/" class="nav-link">К списку моделей</a></p> </div> </div>
Если данные загружены с сервера (то есть переменная loaded равна true), то будут выводиться данные о товаре.
В конце файла определена ссылка на корень приложения. Чтобы привязать ссылку к определенному адресу внутри приложения Angular,
у элемента ссылки устанавливается атрибут routerLink
, который указывает на нужный адрес. В данном случае в качестве адреса применяется
"/", то есть корневой адрес приложения, по которому будет выводиться список товаров.
Теперь добавим в папку ClientApp/src/app новый файл product-list.component.ts, который будет собственно и будет представлять список товаров:
import { Component, OnInit } from '@angular/core'; import { DataService } from './data.service'; import { Product } from './product'; @Component({ templateUrl: './product-list.component.html', providers: [DataService] }) export class ProductListComponent implements OnInit { products: Product[]; constructor(private dataService: DataService) { } ngOnInit() { this.dataService.getProducts().subscribe((data: Product[]) => this.products = data); } }
При загрузке компонента будут загружаться данные с сервера с помощью метода this.dataService.getProducts()
.
Для вывода данных определим в той же папке файл product-list.component.html:
<h2>Список моделей</h2> <table class="table table-striped"> <tbody> <tr *ngFor="let p of products"> <td><a [routerLink]="['product', p.id]" class="nav-link">{{p?.name}}</a></td> </tr> </tbody> </table>
Здесь выводится список объектов. Для каждого объекта выводится только значение поля name, которое заключено в ссылку.
Данная ссылка указывает на определенный путь внутри приложения Angular, который определяется с помощью атрибута [routerLink]
.
При этом теперь этот атрибут фактически принимает массив компонентов, из которых будет создаваться путь. Для создания пути передается строка
"product" и id элемента. То есть в итоге получатся пути типа "product/3" или "product/5". Предполагается, что при переходе по таким путям мы
будет попадать на компонент ProductDetailComponent, который будет получать id и выводить информацию о товаре.
В итоге весь проект будет выглядеть следующим образом:
Теперь изменим файл модуля app.module.ts, в котором сопоставим компоненты с определенными маршрутами:
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { HttpClientModule } from '@angular/common/http'; import { Routes, RouterModule } from '@angular/router'; import { AppComponent } from './app.component'; import { ProductListComponent } from './product-list.component'; import { ProductDetailComponent } from './product-detail.component'; // определение маршрутов const appRoutes: Routes = [ { path: '', component: ProductListComponent }, { path: 'product/:id', component: ProductDetailComponent }, { path: '**', redirectTo: '/' } ]; @NgModule({ imports: [BrowserModule, FormsModule, HttpClientModule, RouterModule.forRoot(appRoutes)], declarations: [AppComponent, ProductListComponent, ProductDetailComponent], bootstrap: [AppComponent] }) export class AppModule { }
Прежде всего здесь определены маршруты в виде объекта Route. Первый маршурт сопоставляется с корнем сайта и будет обрабатываться компонентом ProductListComponent:
{ path: '', component: ProductListComponent }
Второй маршрут будет сопоставляться с путем "product/id", где вместо id будет указан идентификатор товара. И такой маршрут будет обрабатываться компонентом ProductDetailComponent:
{ path: 'product/:id', component: ProductDetailComponent }
Для всех остальных путей ("**") будет идти переадресация на корень приложения.
И другой важный момент состоит в импорте всех нужных модулей:
imports: [BrowserModule, FormsModule, HttpClientModule, RouterModule.forRoot(appRoutes)],
и в добавлении всех ранее используемых компонентов:
declarations: [AppComponent, ProductListComponent, ProductDetailComponent],
Теперь изменим определение главного компонента AppComponent в файле app.component.ts:
import { Component} from '@angular/core'; @Component({ selector: 'app', templateUrl: './app.component.html' }) export class AppComponent { }
И определим в файле app.component.html следующий код:
<div class="container body-content"> <router-outlet></router-outlet> </div>
В элемент <router-outlet>
как раз и будет загружаться выбранный для обработки маршрута компонент.
И еще один важный момент, который надо учитывать при работе с маршрутизацией: на веб-странице должен быть указан элемент base:
<base href="/" />
В частности, веб-страница index.html, которая расположена в проекте в папке ClientApp/src и которая загружается по умолчанию, должна содержать этот элемент:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <base href="/" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Angular in ASP.NET Core</title> <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.main.css" /> </head> <body> <app>Загрузка...</app> </body> </html>
Запустим приложение, и по умолчанию будет идти запрос к корню приложения, а значит такой запрос будет обрабатываться компонентом ProductListComponent, который выведет список товаров:
Если мы перейдем по ссылке, которую представляет товар, то такой запрос будет обрабатываться компонентом ProductDetailComponent, и соответственно мы в браузере увидим информацию о товаре: