De laatste jaren zijn single page applications (SPA’s) steeds populairder geworden. Een SPA is een website die uit slechts één pagina bestaat. Die ene pagina fungeert als een container voor een JavaScript applicatie. Het JavaScript is verantwoordelijk voor het verkrijgen van de inhoud en het renderen ervan binnen de container. De inhoud wordt meestal verkregen van een webservice en RESTful API’s zijn in veel situaties de eerste keuze geworden. Het deel van de toepassing dat de SPA vormt, staat algemeen bekend als de client of front-end, terwijl het deel dat verantwoordelijk is voor de REST API bekend staat als de server of back-end. In deze tutorial ontwikkel je een eenvoudige Angular single page app met een REST backend, gebaseerd op Node en Express.
Je gebruikt Angular omdat het het MVC pattern volgt en op een nette manier de View van de Models scheidt. Het is eenvoudig om HTML templates te maken die dynamisch worden gevuld met gegevens en automatisch worden bijgewerkt wanneer de gegevens veranderen. Ik ben van dit framework gaan houden omdat het erg krachtig is, een enorme gemeenschap heeft en uitstekende documentatie.
Voor de server zul je Node met Express gebruiken. Express is een framework dat het makkelijk maakt om REST API’s te maken door het mogelijk te maken om code te definiëren die voor verschillende verzoeken op de server draait. Extra diensten kunnen globaal worden toegevoegd, of afhankelijk van het verzoek. Er zijn een aantal frameworks die bovenop Express bouwen en de taak automatiseren om je database modellen in een API om te zetten. Deze tutorial maakt hier geen gebruik van om de focus te behouden.
Angular moedigt het gebruik van TypeScript aan. TypeScript voegt type-informatie toe aan JavaScript en is, naar mijn mening, de toekomst van het ontwikkelen van grootschalige toepassingen in JavaScript. Om deze reden zul je zowel de client als de server ontwikkelen met TypeScript.
Hier volgen de bibliotheken die je zult gebruiken voor de client en de server:
- Angular: Het framework dat gebruikt wordt om de client applicatie te bouwen
- Okta voor Autorisatie: Een plugin die single sign-on autorisatie beheert met behulp van Okta, zowel op de client als op de server
- Angular Material: Een angular plugin die out-of-the-box Material Design biedt
- Node: De eigenlijke server waarop de JavaScript-code wordt uitgevoerd
- Express: Een routing library voor het reageren op serververzoeken en het bouwen van REST API’s
- TypeORM: Een database ORM bibliotheek voor TypeScript
Start je basis Angular Client Applicatie
Laten we beginnen met het implementeren van een basis client met Angular. Het doel is om een product catalogus te ontwikkelen waarmee je producten, hun prijzen, en hun voorraadniveau kunt beheren. Aan het eind van dit hoofdstuk heb je een eenvoudige applicatie bestaande uit een bovenbalk en twee views, Home en Products. De Producten-weergave zal nog geen inhoud hebben en niets zal met een wachtwoord worden beschermd. Dit zal worden behandeld in de volgende secties.
Om te beginnen moet u Angular installeren. Ik ga ervan uit dat u Node al op uw systeem heeft geïnstalleerd en u kunt het npm
commando gebruiken. Typ het volgende commando in een terminal.
npm install -g @angular/[email protected]
Afhankelijk van je systeem, moet je dit commando misschien uitvoeren met sudo
omdat het pakket dan globaal wordt geïnstalleerd. Het angular-cli
pakket levert het ng
commando dat wordt gebruikt om Angular applicaties te beheren. Ga na de installatie naar een map naar keuze en maak uw eerste Angular-toepassing met het volgende commando.
ng new MyAngularClient
Als u Angular 7 gebruikt, krijgt u twee vragen. De eerste vraagt je of je routing wilt opnemen. Antwoord hierop met ja. De tweede vraag gaat over het type stylesheets dat u wilt gebruiken. Laat dit op de standaard CSS staan.
ng new
zal een nieuwe directory aanmaken genaamd MyAngularClient
en deze vullen met een applicatie-skelet. Laten we even de tijd nemen om enkele van de bestanden te bekijken die het vorige commando heeft aangemaakt. In de src
directory van de app, vindt u een bestand index.html
dat de hoofdpagina is van de toepassing. Het bevat niet veel en speelt gewoon de rol van een container. U zult ook een style.css
bestand zien. Dit bevat het globale stijlblad dat in de hele toepassing wordt toegepast. Als u door de mappen bladert, ziet u misschien een directory src/app
die vijf bestanden bevat.
app-routing.module.tsapp.component.cssapp.component.htmlapp.component.tsapp.component.spec.tsapp.module.ts
Deze bestanden definiëren de belangrijkste toepassingscomponent die in de index.html
zal worden ingevoegd. Hier volgt een korte beschrijving van elk van de bestanden:
-
app.component.css
bestand bevat de stylesheets van de belangrijksteapp
component. Stijlen kunnen lokaal voor elke component worden gedefinieerd -
app.component.html
bevat het HTML-sjabloon van de component -
app.component.ts
bestand bevat de code die de view regelt -
app.module.ts
definieert welke modules uw app zal gebruiken -
app-routing.module.ts
is opgezet om de routes voor je applicatie te definiëren -
app.component.spec.ts
bevat een skelet voor unit testing van deapp
component
Ik zal het testen in deze tutorial niet behandelen, maar in het echte leven zou je gebruik moeten maken van deze functie. Voordat je aan de slag kunt, moet je nog een paar pakketten installeren. Deze zullen u helpen om snel een mooi ontworpen responsive layout te maken. Navigeer naar de basismap van de client, MyAngularClient
, en type het volgende commando.
npm i @angular/[email protected] @angular/[email protected] @angular/[email protected] @angular/[email protected]
De @angular/material
en @angular/cdk
bibliotheken bieden componenten die zijn gebaseerd op Google’s Material Design, @angular/animations
wordt gebruikt om vloeiende overgangen te bieden, en @angular/flex-layout
geeft je de tools om je ontwerp responsive te maken.
Volgende, maak het HTML-sjabloon voor de app
component. Open src/app/app.component.html
en vervang de inhoud door het volgende.
<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 ="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>
De mat-toolbar
bevat de werkbalk voor materiaalontwerp, terwijl router-outlet
de container is die door de router zal worden gevuld. Het bestand app.component.ts
moet worden bewerkt zodat het het volgende bevat.
import { Component } from '@angular/core';@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: })export class AppComponent { public title = 'My Angular App'; public isAuthenticated: boolean; constructor() { this.isAuthenticated = false; } login() { } logout() { }}
Dit is de controller voor het app
component. U kunt zien dat het een eigenschap bevat genaamd isAuthenticated
samen met twee methoden login
en logout
. Op dit moment doen deze nog niets. Ze zullen worden geïmplementeerd in de volgende sectie die gebruikersauthenticatie met Okta behandelt. Definieer nu alle modules die je gaat gebruiken. Vervang de inhoud van app.module.ts
door de onderstaande code:
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: , imports: , providers: , bootstrap: })export class AppModule { }
Let op alle material design-modules. De @angular/material
bibliotheek vereist dat u een module importeert voor elk type component dat u in uw app wilt gebruiken. Vanaf Angular 7 bevat het standaard applicatie skelet een apart bestand genaamd app-routing.module.ts
. Bewerk dit bestand om de volgende routes aan te geven.
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 = ;@NgModule({ imports: , exports: })export class AppRoutingModule { }
Dit definieert twee routes die corresponderen met het root-pad en met het products
-pad. Het koppelt ook de HomeComponent
en de ProductsComponent
aan deze routes. Maak deze componenten nu. Typ in de basismap van de Angular client de volgende commando’s.
ng generate component Productsng generate component Home
Dit creëert html
css
ts
, en spec.ts
bestanden voor elk onderdeel. Het werkt ook app.module.ts
bij om de nieuwe componenten aan te geven. Open home.component.html
in de src/app/home
directory en plak de volgende inhoud.
<div class="hero"> <div> <h1>Hello World</h1> <p class="lead">This is the homepage of your Angular app</p> </div></div>
Breng ook wat styling aan in het home.component.css
bestand.
.hero { text-align: center; height: 90vh; display: flex; flex-direction: column; justify-content: center; font-family: sans-serif;}
Laat de ProductsComponent
voor nu leeg. Dit zal worden geïmplementeerd zodra u de back-end REST-server hebt gemaakt en in staat bent om het te vullen met een aantal gegevens. Om alles er mooi uit te laten zien blijven er slechts twee kleine taken over. Kopieer de volgende stijlen in 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;}
Finishing, om de Material Design-pictogrammen te renderen, voegt u één regel toe aan de <head>
-tags van het index.html
-bestand.
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
U bent nu klaar om de Angular server op te starten en te zien wat u tot nu toe hebt bereikt. Typ in de basisdirectory van de client-app het volgende commando.
ng serve
Open vervolgens uw browser en navigeer naar http://localhost:4200
.
Voeg Authenticatie toe aan uw Node + Angular App
Als u ooit webapplicaties vanaf nul heeft ontwikkeld, weet u hoeveel werk het is om gebruikers zich te laten registreren, verifiëren, aanmelden en afmelden bij uw applicatie. Met behulp van Okta kan dit proces sterk worden vereenvoudigd. Om te beginnen hebt u een ontwikkelaarsaccount bij Okta nodig.
In uw browser navigeert u naar developer.okta.com en klik op Create Free Account en voer uw gegevens in.
Als u klaar bent, komt u op uw dashboard voor ontwikkelaars. Klik op de knop Applicatie toevoegen om een nieuwe applicatie te maken.
Start met het maken van een nieuwe applicatie met één pagina. Kies Single Page App en klik op Next.
Op de volgende pagina moet u de standaardinstellingen bewerken. Zorg ervoor dat het poortnummer 4200 is. Dit is de standaardpoort voor Angular-toepassingen.
Dat is alles. U zou nu een Client ID moeten zien die u in uw TypeScript-code moet plakken.
Om authenticatie in de client te implementeren, installeert u de Okta-bibliotheek voor Angular.
npm install @okta/[email protected] --save-exact
In app.module.ts
importeer je de OktaAuthModule
.
import { OktaAuthModule } from '@okta/okta-angular';
In de lijst van imports
van de app
module, voeg toe:
OktaAuthModule.initAuth({ issuer: 'https://{yourOktaDomain}/oauth2/default', redirectUri: 'http://localhost:4200/implicit/callback', clientId: '{YourClientId}'})
Hier yourOktaDomain
moet worden vervangen door het ontwikkelingsdomein dat u in uw browser ziet wanneer u naar uw Okta-dashboard navigeert. YourClientId
moet worden vervangen door de client-ID die u hebt verkregen bij het registreren van uw applicatie. De bovenstaande code maakt de Okta Authenticatie Module beschikbaar in uw applicatie. Gebruik deze in app.component.ts
, en importeer de service.
import { OktaAuthService } from '@okta/okta-angular';
Modificeer de constructor om de service te injecteren en u erop te abonneren.
constructor(public oktaAuth: OktaAuthService) { this.oktaAuth.$authenticationState.subscribe( (isAuthenticated: boolean) => this.isAuthenticated = isAuthenticated );}
Nu worden alle wijzigingen in de authenticatiestatus weergegeven in de isAuthenticated
eigenschap. U moet deze nog initialiseren wanneer de component wordt geladen. Maak een ngOnInit
methode en voeg implements OnInit
aan uw klasse definitie toe
import { Component, OnInit } from '@angular/core';...export class AppComponent implements OnInit { ... async ngOnInit() { this.isAuthenticated = await this.oktaAuth.isAuthenticated(); }}
Eindig, implementeer je de login
en logout
methode om op de gebruikersinterface te reageren en de gebruiker aan of af te melden.
login() { this.oktaAuth.loginRedirect();}logout() { this.oktaAuth.logout('/');}
In de routingmodule moet u de route registreren die zal worden gebruikt voor het inlogverzoek. Open app-routing.module.ts
en importeer OktaCallbackComponent
en OktaAuthGuard
.
import { OktaCallbackComponent, OktaAuthGuard } from '@okta/okta-angular';
Voeg nog een route toe aan de routes
array.
{ path: 'implicit/callback', component: OktaCallbackComponent}
Dit stelt de gebruiker in staat om in te loggen met de knop Aanmelden. Om de Products
-route te beschermen tegen onbevoegde toegang, voegt u de volgende regel toe aan de products
-route.
{ path: 'products', component: ProductsComponent, canActivate: }
Dat is alles wat er over te zeggen valt. Wanneer een gebruiker nu toegang probeert te krijgen tot de Producten-weergave, wordt hij doorgestuurd naar de Okta-inlogpagina. Eenmaal ingelogd, wordt de gebruiker teruggeleid naar de Producten-weergave.
Elementeer een Node REST API
De volgende stap is het implementeren van een server gebaseerd op Node en Express die productinformatie zal opslaan. Dit zal gebruik maken van een aantal kleinere bibliotheken om je leven gemakkelijker te maken. Om in TypeScript te ontwikkelen, hebt u typescript
en tsc
nodig. Voor de database-abstractielaag maak je gebruik van TypeORM. Dit is een handige bibliotheek die gedrag injecteert in TypeScript-klassen en deze omzet in databasemodellen. Maak een nieuwe map voor je serverapplicatie en voer daarin het volgende commando uit.
npm init
Beantwoord alle vragen en voer vervolgens uit:
npm install --save-exact [email protected] @types/[email protected] @okta/[email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] @types/[email protected]
Ik zal niet al deze bibliotheken in detail behandelen, maar je zult zien dat @okta/jwt-verifier
wordt gebruikt om JSON-webtokens te verifiëren en te authenticeren.
Om TypeScript te laten werken, maakt u een bestand tsconfig.json
en plakt u daarin de volgende inhoud.
{ "compilerOptions": { "target": "es6", "module": "commonjs", "outDir": "dist", "experimentalDecorators": true, "emitDecoratorMetadata": true }, "include": , "exclude": }
De eerste stap bij het maken van de server is het maken van een databasemodel voor het product. Met TypeORM is dit heel eenvoudig. Maak een subdirectory src
en maak daarbinnen een bestand model.ts
. Plak de volgende inhoud.
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;}
De TypeORM-annotaties zetten de klassendefinitie om in een databasemodel. Ik ben dol op het TypeORM-project vanwege het gebruiksgemak en de grote verscheidenheid aan ondersteunde SQL- en NoSQL-databasekoppelingen. Ik stel voor dat je de documentatie bekijkt op https://github.com/typeorm/typeorm.
Je zult ook toegang moeten krijgen tot een repository van het product. Voeg ook in het model.ts
bestand het volgende toe.
let connection:Connection;export async function getProductRepository(): Promise<Repository<Product>> { if (connection===undefined) { connection = await createConnection({ type: 'sqlite', database: 'myangularapp', synchronize: true, entities: , }); } return connection.getRepository(Product);}
Merk op dat hier voor de eenvoud SQLite wordt gebruikt. In een real-world scenario moet u dit vervangen door een databaseconnector naar keuze.
Naast maakt u een bestand met de naam product.ts
. Dit bestand bevat de logica voor alle routes voor de CRUD-bewerkingen op producten.
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); }});
Dit bestand is enigszins lang, maar bevat niets verrassends. Product
objecten worden aangemaakt en opgeslagen in of verwijderd uit de database.
Laten we onze aandacht weer richten op de authenticatie. Je wilt er zeker van zijn dat alleen geauthenticeerde gebruikers toegang hebben tot de service. Maak een bestand aan met de naam auth.ts
en plak daarin het volgende.
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); }}
Net als in de clienttoepassing moet yourOktaDomain
worden vervangen door het ontwikkelingsdomein en YourClientId
moet worden vervangen door de client-ID van uw toepassing. De oktaJwtVerifier
instantie neemt een JWT token en verifieert het. Indien succesvol, worden de gebruikers-id en e-mail opgeslagen in req.user
. Zo niet, dan zal de server antwoorden met een 401 status code. Het laatste stuk om de server te vervolledigen is het hoofdingangspunt dat de server start en de middleware registreert die je tot nu toe gedefinieerd hebt. Maak een bestand server.ts
met de volgende inhoud.
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');});
Om de TypeScript te compileren, voert u het commando uit
npx tsc
Dan, als u de server wilt starten, voert u gewoon uit:
node dist/server.js
Voltooi uw Angular-client
Nu de server is voltooid, gaan we de client afmaken. De eerste stap is het creëren van een klasse die de productgegevens bevat. Deze class is vergelijkbaar met de Product
class in de server applicatie maar dan zonder de TypeORM annotaties. Het wordt opgenomen in een bestand met de naam product.ts
.
export class Product { id?: string; name: string; sku: string; description: string; price: number; stock: number;}
Bewaar dit bestand in dezelfde directory als de products
component. Het is het beste om de toegang tot de REST API in een aparte service in te kapselen. Maak een Products
service aan door het onderstaande commando uit te voeren.
ng generate service products/Products
Dit zal een bestand aanmaken met de naam product.service.ts
in de src/app/products
directory. Vul het met de volgende inhoud.
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}`); }}
De ProductsService
bevat een publieke methode voor elke route van de REST API. Het HTTP-verzoek wordt in een aparte methode ingekapseld. Merk op hoe het verzoek altijd een Bearer
token bevat, verkregen uit de OktaAuthService
. Dit is het token dat door de server wordt gebruikt om de gebruiker te authenticeren.
Nu kan de ProductsComponent
worden geïmplementeerd. De volgende code is hiervoor geschikt.
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: })export class ProductsComponent implements OnInit { displayedColumns: string = ; 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(); }}
De lay-out, in products.component.html
, waarin het product wordt getoond, bestaat uit twee delen. Het eerste deel gebruikt een mat-table
component om een lijst met producten weer te geven. Het tweede deel toont een formulier waarin de gebruiker een nieuw of bestaand product kan bewerken.
<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" 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 ="true"></mat-divider> <div> <h2>Selected Product</h2> <label>Name <input type="text" ="selectedProduct.name"> </label> <label>SKU <input type="text" ="selectedProduct.sku"> </label> <label>Description <input type="text" ="selectedProduct.description"> </label> <label>Price <input type="text" ="selectedProduct.price"> </label> <label>Stock Level <input type="text" ="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>
Tot slot voegt u een beetje styling in products.component.css
toe aan de lay-out.
.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);}
Wanneer alles klaar is, kunt u de client en de server opstarten en uw toepassing testen. Om het nog eens te herhalen, voert u in de directory met de server het volgende uit:
node dist/server.js
En in de directory met de client voert u het volgende uit:
ng serve
Uw applicatie zou er ongeveer als volgt uit moeten zien
Lees meer over Angular, Node, en Express
In deze tutorial heb ik je door de ontwikkeling van een webapplicatie met één pagina geleid met behulp van Angular en Node. Met behulp van slechts een paar regels code was je in staat om gebruikersauthenticatie voor de client en de server te implementeren. Angular maakt gebruik van TypeScript, dat een superset is van de JavaScript-taal en type-informatie toevoegt. TypeScript zorgt voor stabielere code en daarom heb ik besloten om ook de Node/Express server met deze taal te implementeren. Als je nog niet bekend bent met TypeScript, bekijk dan deze geweldige introductie door Todd Motto. Hij heeft ook een aantal goede artikelen over Angular.
De volledige code van deze tutorial kun je vinden op GitHub.
Als je meer wilt leren over Angular, of Node/Express, hebben we nog een aantal andere bronnen voor je om te bekijken:
- Simple Node Authentication
- Build a Basic CRUD App with Node and React
- Build a Simple API Service with Express and GraphQL
- Angular 6 – What’s New and Why Upgrade?
- Bouw een eenvoudige CRUD App met Angular 7 en Spring Boot
En zoals altijd, zouden we het leuk vinden als je ons volgt voor meer coole content en updates van ons team. Je kunt ons vinden op Twitter @oktadev, op Facebook, en LinkedIn.