node angular
本文最初发布在Okta开发人员博客上 。 感谢您支持使SitePoint成为可能的合作伙伴。
近年来,单页应用程序(SPA)变得越来越流行。 SPA是仅由一个页面组成的网站。 该单独页面充当JavaScript应用程序的容器。 JavaScript负责获取内容并将其呈现在容器中。 内容通常是从Web服务获得的,而RESTful API已成为许多情况下的首选。 组成SPA的应用程序部分通常称为客户端或前端 ,而负责REST API的部分称为服务器或后端 。 在本教程中,您将基于Node和Express开发一个具有REST后端的简单Angular单页应用程序。
您将使用Angular,因为它遵循MVC模式并将视图与模型完全分开。 创建HTML模板很简单,该模板可以动态填充数据并在数据更改时自动更新。 我之所以喜欢这个框架,是因为它非常强大,拥有庞大的社区和出色的文档。
对于服务器,您将使用带有Express的Node。 Express是一个框架,通过允许定义针对服务器上不同请求运行的代码,可以轻松创建REST API。 可以在全局或根据请求插入其他服务。 在Express的基础上,有许多框架可以自动完成将数据库模型转换为API的任务。 本教程将不会利用这些内容中的任何内容来保持专注。
Angular鼓励使用TypeScript。 TypeScript将类型信息添加到JavaScript中,我认为这是在JavaScript中开发大规模应用程序的未来。 因此,您将使用TypeScript开发客户端和服务器。
以下是您将用于客户端和服务器的库:
- Angular:用于构建客户端应用程序的框架
- Okta授权:用于使用Okta在客户端和服务器上管理单点登录授权的插件
- Angular Material:一个角度插件,提供开箱即用的Material Design
- 节点:运行JavaScript代码的实际服务器
- Express:路由库,用于响应服务器请求并构建REST API
- TypeORM: TypeScript的数据库ORM库
启动您的基本Angular Client应用程序
让我们开始使用Angular实现一个基本客户端。 目的是开发产品目录,使您可以管理产品,其价格和库存水平。 在本节的最后,您将有一个简单的应用程序,包括一个顶部栏和两个视图,即“首页”和“产品”。 产品视图将尚未包含任何内容,并且没有任何内容将受到密码保护。 以下各节将对此进行介绍。
首先,您需要安装Angular。 我将假定您已经在系统上安装了Node,并且可以使用npm
命令。 在终端中键入以下命令。
npm install -g @angular/cli@7.0.2
根据您的系统,您可能需要使用sudo
运行此命令,因为它将在全局安装该软件包。 angular-cli
软件包提供了用于管理Angular应用程序的ng
命令。 安装完成后,转到您选择的目录,并使用以下命令创建第一个Angular应用程序。
ng new MyAngularClient
使用Angular 7,这将提示您两个查询。 第一个询问您是否要包括路由。 对此回答是 。 第二个查询与您要使用的样式表的类型有关。 将此保留为默认CSS 。
ng new
将创建一个名为MyAngularClient
的新目录,并使用应用程序框架填充该目录。 我们花一些时间来查看上一个命令创建的一些文件。 在应用程序的src
目录中,您将找到文件index.html
,它是应用程序的主页。 它包含的内容并不多,仅扮演了容器的角色。 您还将看到一个style.css
文件。 这包含在整个应用程序中应用的全局样式表。 如果浏览文件夹,可能会注意到目录src/app
包含五个文件。
app-routing.module.ts
app.component.css
app.component.html
app.component.ts
app.component.spec.ts
app.module.ts
这些文件定义了将插入index.html
的主要应用程序组件。 这是每个文件的简短描述:
-
app.component.css
文件包含主要app
组件的样式表。 可以在本地为每个组件定义样式 -
app.component.html
包含组件HTML模板 -
app.component.ts
文件包含控制视图的代码 -
app.module.ts
定义您的应用将使用哪些模块 - 设置
app-routing.module.ts
来定义您的应用程序的路由 -
app.component.spec.ts
包含用于对app
组件进行单元测试的框架
我不会在本教程中讨论测试,但是在实际应用中,您应该使用此功能。 在开始之前,您将需要安装更多软件包。 这些将帮助您快速创建设计良好的响应式布局。 导航到客户端的基本目录MyAngularClient
,然后键入以下命令。
npm i @angular/material@7.0.2 @angular/cdk@7.0.2 @angular/animations@7.0.1 @angular/flex-layout@7.0.0-beta.19
@angular/material
和@angular/cdk
库提供了基于Google材质设计的组件, @angular/animations
用于提供平滑的过渡, @angular/flex-layout
为您提供了使您的设计具有响应性的工具。
接下来,为app
组件创建HTML模板。 打开src/app/app.component.html
并将内容替换为以下内容。
<mat-toolbar color="primary" class="expanded-toolbar">
<button mat-button routerLink="/">{{title}}</button>
<div fxLayout="row" fxShow="false" fxShow.gt-sm>
<button mat-button routerLink="/"><mat-icon>home</mat-icon></button>
<button mat-button routerLink="/products">Products</button>
<button mat-button *ngIf="!isAuthenticated" (click)="login()"> Login </button>
<button mat-button *ngIf="isAuthenticated" (click)="logout()"> Logout </button>
</div>
<button mat-button [mat-menu-trigger-for]="menu" fxHide="false" fxHide.gt-sm>
<mat-icon>menu</mat-icon>
</button>
</mat-toolbar>
<mat-menu x-position="before" #menu="matMenu">
<button mat-menu-item routerLink="/"><mat-icon>home</mat-icon> Home</button>
<button mat-menu-item routerLink="/products">Products</button>;
<button mat-menu-item *ngIf="!isAuthenticated" (click)="login()"> Login </button>
<button mat-menu-item *ngIf="isAuthenticated" (click)="logout()"> Logout </button>
</mat-menu>
<router-outlet></router-outlet>
mat-toolbar
包含材质设计工具栏,而router-outlet
是将由铣刨机填充的容器。 app.component.ts
文件进行编辑以包含以下内容。
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
public title = 'My Angular App';
public isAuthenticated: boolean;
constructor() {
this.isAuthenticated = false;
}
login() {
}
logout() {
}
}
这是app
组件的控制器。 您可以看到它包含一个名为isAuthenticated
的属性以及两个方法login
和logout
。 目前这些都不做。 它们将在下一部分中实现,该部分涵盖使用Okta进行用户身份验证。 现在定义您将要使用的所有模块。 用以下代码替换app.module.ts
的内容:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FlexLayoutModule } from '@angular/flex-layout';
import {
MatButtonModule,
MatDividerModule,
MatIconModule,
MatMenuModule,
MatProgressSpinnerModule,
MatTableModule,
MatToolbarModule
} from '@angular/material';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
AppRoutingModule,
BrowserModule,
BrowserAnimationsModule,
HttpClientModule,
FlexLayoutModule,
MatToolbarModule,
MatMenuModule,
MatIconModule,
MatButtonModule,
MatTableModule,
MatDividerModule,
MatProgressSpinnerModule,
FormsModule,
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
注意所有的材料设计模块。 @angular/material
库要求您为希望在应用程序中使用的每种类型的组件导入模块。 从Angular 7开始,默认应用程序框架包含一个名为app-routing.module.ts
的单独文件。 编辑此内容以声明以下路线。
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ProductsComponent } from './products/products.component';
import { HomeComponent } from './home/home.component';
const routes: Routes = [
{
path: '',
component: HomeComponent
},
{
path: 'products',
component: ProductsComponent
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
这定义了两个与根路径和products
路径相对应的路由。 它还将HomeComponent
和ProductsComponent
附加到这些路由。 现在创建这些组件。 在Angular客户端的基本目录中,键入以下命令。
ng generate component Products
ng generate component Home
免费学习PHP!
全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。
原价$ 11.95 您的完全免费
这将为每个组件创建html
, css
, ts
和spec.ts
文件。 它还会更新app.module.ts
以声明新组件。 在src/app/home
目录中打开home.component.html
并粘贴以下内容。
<div class="hero">
<div>
<h1>Hello World</h1>
<p class="lead">This is the homepage of your Angular app</p>
</div>
</div>
在home.component.css
文件中也包括一些样式。
.hero {
text-align: center;
height: 90vh;
display: flex;
flex-direction: column;
justify-content: center;
font-family: sans-serif;
}
现在将ProductsComponent
保留为空。 一旦创建了后端REST服务器并能够用一些数据填充它,它将被实现。 为了使一切看起来漂亮,仅剩下两个小任务。 将以下样式复制到src/style.css
@import "~@angular/material/prebuilt-themes/deeppurple-amber.css";
body {
margin: 0;
font-family: sans-serif;
}
.expanded-toolbar {
justify-content: space-between;
}
h1 {
text-align: center;
}
最后,为了渲染Material Design Icons,在index.html
文件的<head>
标签内添加一行。
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
现在您可以启动Angular服务器,并查看到目前为止所取得的成就。 在客户端应用程序的基本目录中,键入以下命令。
ng serve
然后打开浏览器并导航到http://localhost:4200
。
向您的节点+ Angular应用添加身份验证
如果您曾经从头开发过Web应用程序,您将知道只需要允许用户注册,验证,登录和注销应用程序就需要进行多少工作。 使用Okta,此过程可以大大简化。 首先,您需要使用Okta的开发者帐户。
在浏览器中,导航到developer.okta.com ,然后单击创建免费帐户,然后输入您的详细信息。
完成后,您将进入开发人员仪表板。 单击添加应用程序按钮以创建一个新的应用程序。
首先创建一个新的单页应用程序。 选择单页应用程序 ,然后单击下一步 。
在下一页上,您将需要编辑默认设置。 确保端口号是4200。这是Angular应用程序的默认端口。
而已。 现在,您应该看到一个客户端ID ,您需要将其粘贴到TypeScript代码中。
要在客户端中实现身份验证,请安装Angular的Okta库。
npm install @okta/okta-angular@1.0.7 --save-exact
在app.module.ts
导入OktaAuthModule
。
import { OktaAuthModule } from '@okta/okta-angular';
在app
模块的imports
列表中,添加:
OktaAuthModule.initAuth({
issuer: 'https://{yourOktaDomain}/oauth2/default',
redirectUri: 'http://localhost:4200/implicit/callback',
clientId: '{YourClientId}'
})
在这里, yourOktaDomain
应该替换为导航到Okta仪表板时在浏览器中看到的开发域。 YourClientId
必须替换为注册应用程序时获得的客户端ID。 上面的代码使Okta身份验证模块在您的应用程序中可用。 在app.component.ts
使用它,然后导入服务。
import { OktaAuthService } from '@okta/okta-angular';
修改构造函数以注入服务并订阅它。
constructor(public oktaAuth: OktaAuthService) {
this.oktaAuth.$authenticationState.subscribe(
(isAuthenticated: boolean) => this.isAuthenticated = isAuthenticated
);
}
现在,身份验证状态的任何更改都将反映在isAuthenticated
属性中。 加载组件后,您仍然需要对其进行初始化。 创建一个ngOnInit
方法并将implements OnInit
添加到您的类定义中
import { Component, OnInit } from '@angular/core';
...
export class AppComponent implements OnInit {
...
async ngOnInit() {
this.isAuthenticated = await this.oktaAuth.isAuthenticated();
}
}
最后,实现login
和logout
方法以对用户界面做出React,并使用户登录或注销。
login() {
this.oktaAuth.loginRedirect();
}
logout() {
this.oktaAuth.logout('/');
}
在路由模块中,您需要注册将用于登录请求的路由。 打开app-routing.module.ts
并导入OktaCallbackComponent
和OktaAuthGuard
。
import { OktaCallbackComponent, OktaAuthGuard } from '@okta/okta-angular';
将另一个路由添加到routes
数组。
{
path: 'implicit/callback',
component: OktaCallbackComponent
}
这将允许用户使用“ 登录”按钮登录 。 为了保护Products
免受未经授权的访问路线,添加以下行到products
路线。
{
path: 'products',
component: ProductsComponent,
canActivate: [OktaAuthGuard]
}
这里的所有都是它的。 现在,当用户尝试访问“产品”视图时,他们将被重定向到Okta登录页面。 登录后,用户将被重定向回“产品”视图。
实施节点REST API
下一步是实现基于Node and Express的服务器,该服务器将存储产品信息。 这将使用许多较小的库来简化您的生活。 要使用TypeScript开发,您需要typescript
和tsc
。 对于数据库抽象层,将使用TypeORM 。 这是一个方便的库,可将行为注入TypeScript类并将其转换为数据库模型。 创建一个包含服务器应用程序的新目录,然后在其中运行以下命令。
npm init
回答所有问题,然后运行:
npm install --save-exact express@4.16.4 @types/express@4.16.0 @okta/jwt-verifier@0.0.14 express-bearer-token@2.2.0 tsc@1.20150623.0 typescript@3.1.3 typeorm@0.2.8 sqlite3@4.0.3 cors@2.8.4 @types/cors@2.8.4
我不会详细介绍所有这些库,但是您会看到@okta/jwt-verifier
用于验证JSON Web令牌并对其进行身份验证。
为了使TypeScript起作用,请创建文件tsconfig.json
并粘贴以下内容。
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "dist",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules"
]
}
创建服务器的第一步是为产品创建数据库模型。 使用TypeORM,这很简单。 创建一个子目录src
并在其中创建文件model.ts
。 粘贴以下内容。
import {Entity, PrimaryGeneratedColumn, Column, createConnection, Connection, Repository} from 'typeorm';
@Entity()
export class Product {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
sku: string;
@Column('text')
description: string;
@Column()
price: number;
@Column()
stock: number;
}
TypeORM批注将类定义转换为数据库模型。 我喜欢TypeORM项目,因为它易于使用,并且支持各种SQL和NoSQL数据库连接器。 我建议您在https://github.com/typeorm/typeorm上查看文档。
您还需要访问产品存储库。 同样在model.ts
文件中添加以下内容。
let connection:Connection;
export async function getProductRepository(): Promise<Repository<Product>> {
if (connection===undefined) {
connection = await createConnection({
type: 'sqlite',
database: 'myangularapp',
synchronize: true,
entities: [
Product
],
});
}
return connection.getRepository(Product);
}
请注意,这里为了简单起见使用SQLite。 在实际情况下,应使用您选择的数据库连接器替换它。
接下来,创建一个名为product.ts
的文件。 该文件将包含产品上CRUD操作的所有路由的逻辑。
import { NextFunction, Request, Response, Router } from 'express';
import { getProductRepository, Product } from './model';
export const router: Router = Router();
router.get('/product', async function (req: Request, res: Response, next: NextFunction) {
try {
const repository = await getProductRepository();
const allProducts = await repository.find();
res.send(allProducts);
}
catch (err) {
return next(err);
}
});
router.get('/product/:id', async function (req: Request, res: Response, next: NextFunction) {
try {
const repository = await getProductRepository();
const product = await repository.find({id: req.params.id});
res.send(product);
}
catch (err) {
return next(err);
}
});
router.post('/product', async function (req: Request, res: Response, next: NextFunction) {
try {
const repository = await getProductRepository();
const product = new Product();
product.name = req.body.name;
product.sku = req.body.sku;
product.description = req.body.description;
product.price = Number.parseFloat(req.body.price);
product.stock = Number.parseInt(req.body.stock);
const result = await repository.save(product);
res.send(result);
}
catch (err) {
return next(err);
}
});
router.post('/product/:id', async function (req: Request, res: Response, next: NextFunction) {
try {
const repository = await getProductRepository();
const product = await repository.findOne({id: req.params.id});
product.name = req.body.name;
product.sku = req.body.sku;
product.description = req.body.description;
product.price = Number.parseFloat(req.body.price);
product.stock = Number.parseInt(req.body.stock);
const result = await repository.save(product);
res.send(result);
}
catch (err) {
return next(err);
}
});
router.delete('/product/:id', async function (req: Request, res: Response, next: NextFunction) {
try {
const repository = await getProductRepository();
await repository.delete({id: req.params.id});
res.send('OK');
}
catch (err) {
return next(err);
}
});
该文件有些冗长,但不包含任何令人惊讶的内容。 Product
对象已创建并保存到数据库中或从数据库中删除。
让我们再次将注意力转向身份验证。 您将要确保只有经过身份验证的用户才能访问该服务。 创建一个名为auth.ts
的文件,然后粘贴以下内容。
import { Request, Response, NextFunction} from 'express';
const OktaJwtVerifier = require('@okta/jwt-verifier');
const oktaJwtVerifier = new OktaJwtVerifier({
clientId: '{YourClientId}',
issuer: 'https://{yourOktaDomain}/oauth2/default'
});
export async function oktaAuth(req:Request, res:Response, next:NextFunction) {
try {
const token = (req as any).token;
if (!token) {
return res.status(401).send('Not Authorised');
}
const jwt = await oktaJwtVerifier.verifyAccessToken(token);
req.user = {
uid: jwt.claims.uid,
email: jwt.claims.sub
};
next();
}
catch (err) {
return res.status(401).send(err.message);
}
}
就像在客户端应用程序中yourOktaDomain
应将yourOktaDomain
替换为开发域,并将YourClientId
替换为应用程序客户端ID。 oktaJwtVerifier
实例获取JWT令牌并进行身份验证。 如果成功,则用户ID和电子邮件将存储在req.user
。 否则,服务器将以401状态代码响应。 完成服务器的最后一步是实际启动服务器并注册到目前为止定义的中间件的主要入口点。 创建具有以下内容的文件server.ts
。
import * as express from 'express';
import * as cors from 'cors';
import * as bodyParser from 'body-parser';
const bearerToken = require('express-bearer-token');
import {router as productRouter} from './product'
import {oktaAuth} from './auth'
const app = express()
.use(cors())
.use(bodyParser.json())
.use(bearerToken())
.use(oktaAuth)
.use(productRouter);
app.listen(4201, (err) => {
if (err) {
return console.log(err);
}
return console.log('My Node App listening on port 4201');
});
要编译TypeScript,请运行以下命令
npx tsc
然后,如果要启动服务器,只需运行:
node dist/server.js
完成您的Angular客户
现在服务器已经完成,让我们结束客户端。 第一步是创建一个包含产品数据的类。 该类类似于服务器应用程序中的Product
类,但没有TypeORM批注。 它将包含在一个名为product.ts
的文件中。
export class Product {
id?: string;
name: string;
sku: string;
description: string;
price: number;
stock: number;
}
将此文件保存在与products
组件相同的目录中。 最好将对REST API的访问封装在单独的服务中。 通过运行以下命令来创建Products
服务。
ng generate service products/Products
这将在src/app/products
目录中创建一个名为product.service.ts
的文件。 填写以下内容。
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { OktaAuthService } from '@okta/okta-angular';
import { Product } from './product';
const baseUrl = 'http://localhost:4201';
@Injectable({
providedIn: 'root'
})
export class ProductsService {
constructor(public oktaAuth: OktaAuthService, private http: HttpClient) {
}
private async request(method: string, url: string, data?: any) {
const token = await this.oktaAuth.getAccessToken();
console.log('request ' + JSON.stringify(data));
const result = this.http.request(method, url, {
body: data,
responseType: 'json',
observe: 'body',
headers: {
Authorization: `Bearer ${token}`
}
});
return new Promise<any>((resolve, reject) => {
result.subscribe(resolve as any, reject as any);
});
}
getProducts() {
return this.request('get', `${baseUrl}/product`);
}
getProduct(id: string) {
return this.request('get', `${baseUrl}/product/${id}`);
}
createProduct(product: Product) {
console.log('createProduct ' + JSON.stringify(product));
return this.request('post', `${baseUrl}/product`, product);
}
updateProduct(product: Product) {
console.log('updateProduct ' + JSON.stringify(product));
return this.request('post', `${baseUrl}/product/${product.id}`, product);
}
deleteProduct(id: string) {
return this.request('delete', `${baseUrl}/product/${id}`);
}
}
ProductsService
为REST API的每个路由包含一个公共方法。 HTTP请求封装在单独的方法中。 请注意,请求如何始终包含从OktaAuthService
获得的Bearer
令牌。 这是服务器用来验证用户身份的令牌。
现在可以实现ProductsComponent
。 以下代码可以解决问题。
import { Component, OnInit } from '@angular/core';
import { MatTableDataSource } from '@angular/material';
import { ProductsService } from './products.service';
import { Product } from './product';
@Component({
selector: 'app-products',
templateUrl: './products.component.html',
styleUrls: ['./products.component.css']
})
export class ProductsComponent implements OnInit {
displayedColumns: string[] = ['name', 'sku', 'description', 'price', 'stock', 'edit', 'delete'];
dataSource = new MatTableDataSource<any>();
selectedProduct: Product = new Product();
loading = false;
constructor(public productService: ProductsService) {
}
ngOnInit() {
this.refresh();
}
async refresh() {
this.loading = true;
const data = await this.productService.getProducts();
this.dataSource.data = data;
this.loading = false;
}
async updateProduct() {
if (this.selectedProduct.id !== undefined) {
await this.productService.updateProduct(this.selectedProduct);
} else {
await this.productService.createProduct(this.selectedProduct);
}
this.selectedProduct = new Product();
await this.refresh();
}
editProduct(product: Product) {
this.selectedProduct = product;
}
clearProduct() {
this.selectedProduct = new Product();
}
async deleteProduct(product: Product) {
this.loading = true;
if (confirm(`Are you sure you want to delete the product ${product.name}. This cannot be undone.`)) {
this.productService.deleteProduct(product.id);
}
await this.refresh();
}
}
展示产品的布局,在products.component.html
,由两部分组成。 第一部分使用mat-table
组件显示产品列表。 第二部分显示用户可以编辑新产品或现有产品的表单。
<h1 class="h1">Product Inventory</h1>
<div fxLayout="row" fxLayout.xs="column" fxLayoutAlign="space-between stretch" class="products">
<table mat-table fxFlex="100%" fxFlex.gt-sm="66%" [dataSource]="dataSource" class="mat-elevation-z1">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Name</th>
<td mat-cell *matCellDef="let product"> {{product.name}}</td>
</ng-container>
<ng-container matColumnDef="sku">
<th mat-header-cell *matHeaderCellDef> SKU</th>
<td mat-cell *matCellDef="let product"> {{product.sku}}</td>
</ng-container>
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef> Description</th>
<td mat-cell *matCellDef="let product"> {{product.description}}</td>
</ng-container>
<ng-container matColumnDef="price">
<th mat-header-cell *matHeaderCellDef> Price</th>
<td mat-cell *matCellDef="let product"> {{product.price}}</td>
</ng-container>
<ng-container matColumnDef="stock">
<th mat-header-cell *matHeaderCellDef> Stock Level</th>
<td mat-cell *matCellDef="let product"> {{product.stock}}</td>
</ng-container>
<ng-container matColumnDef="edit">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let product">
<button mat-icon-button (click)="editProduct(product)">
<mat-icon>edit</mat-icon>
</button>
</td>
</ng-container>
<ng-container matColumnDef="delete">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let product">
<button mat-icon-button (click)="deleteProduct(product)">
<mat-icon>delete</mat-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-divider fxShow="false" fxShow.gt-sm [vertical]="true"></mat-divider>
<div>
<h2>Selected Product</h2>
<label>Name
<input type="text" [(ngModel)]="selectedProduct.name">
</label>
<label>SKU
<input type="text" [(ngModel)]="selectedProduct.sku">
</label>
<label>Description
<input type="text" [(ngModel)]="selectedProduct.description">
</label>
<label>Price
<input type="text" [(ngModel)]="selectedProduct.price">
</label>
<label>Stock Level
<input type="text" [(ngModel)]="selectedProduct.stock">
</label>
<button mat-flat-button color="primary" (click)="updateProduct()">{{(selectedProduct.id!==undefined)?'Update':'Create'}}</button>
<button mat-flat-button color="primary" (click)="clearProduct()">Clear</button>
</div>
<div class="loading" *ngIf="loading">
<mat-spinner></mat-spinner>
</div>
</div>
最后,在products.component.css
向布局添加一些样式。
.products {
padding: 2rem;
}
label, input {
display: block;
}
label {
margin-bottom: 1rem;
}
.loading {
position: absolute;
display: flex;
justify-content: center;
align-content: center;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.8);
}
完成所有步骤后,您可以启动客户端和服务器并测试您的应用程序。 只是重复一遍,在包含服务器的目录中运行:
node dist/server.js
并在客户端目录中运行:
ng serve
您的应用程序应类似于以下内容
了解有关Angular,Node和Express的更多信息
在本教程中,我指导您使用Angular和Node开发单页Web应用程序。 仅使用几行代码,您就可以对客户端和服务器实施用户身份验证。 Angular使用TypeScript(它是JavaScript语言的超集)并添加类型信息。 TypeScript使代码更稳定,这就是为什么我决定也使用这种语言实现Node / Express服务器的原因。 如果您还不熟悉TypeScript,请查看Todd Motto的出色介绍 。 他在Angular上也有一些不错的文章。
可以在GitHub上找到本教程的完整代码。
如果您准备了解有关Angular或Node / Express的更多信息,我们还有一些其他资源可供您检查:
- 简单节点认证
- 使用Node和React构建一个基本的CRUD应用
- 使用Express和GraphQL构建简单的API服务
- Angular 6 –有什么新功能以及为什么要升级?
- 使用Angular 7和Spring Boot构建基本的CRUD应用
与往常一样,我们很乐意让您关注我们,以获取更酷的内容和我们团队的更新。 您可以在Twitter @ oktadev , Facebook和LinkedIn上找到我们。
翻译自: https://www.sitepoint.com/build-a-basic-crud-app-with-angular-and-node/
node angular