In den letzten Jahren sind Single Page Applications (SPAs) immer beliebter geworden. Eine SPA ist eine Website, die aus nur einer Seite besteht. Diese eine Seite fungiert als Container für eine JavaScript-Anwendung. Das JavaScript ist dafür verantwortlich, den Inhalt zu beschaffen und ihn innerhalb des Containers darzustellen. Der Inhalt wird typischerweise von einem Webservice bezogen und RESTful APIs sind in vielen Situationen die erste Wahl. Der Teil der Anwendung, aus dem die SPA besteht, wird gemeinhin als Client oder Front-End bezeichnet, während der Teil, der für die REST-API verantwortlich ist, als Server oder Back-End bezeichnet wird. In diesem Tutorial werden Sie eine einfache Angular-Single-Page-App mit einem REST-Backend entwickeln, die auf Node und Express basiert.
Sie werden Angular verwenden, da es dem MVC-Muster folgt und die View sauber von den Models trennt. Es ist einfach, HTML-Templates zu erstellen, die dynamisch mit Daten gefüllt werden und automatisch aktualisiert werden, wenn sich die Daten ändern. Ich habe dieses Framework lieben gelernt, weil es sehr mächtig ist, eine riesige Community und eine hervorragende Dokumentation hat.
Für den Server werden Sie Node mit Express verwenden. Express ist ein Framework, das es einfach macht, REST-APIs zu erstellen, indem es erlaubt, Code zu definieren, der für verschiedene Anfragen auf dem Server läuft. Zusätzliche Dienste können global oder abhängig von der Anfrage eingesteckt werden. Es gibt eine Reihe von Frameworks, die auf Express aufsetzen und die Aufgabe automatisieren, Ihre Datenbankmodelle in eine API zu verwandeln. In diesem Tutorial wird keines davon verwendet, um den Fokus zu wahren.
Angular fördert die Verwendung von TypeScript. TypeScript fügt JavaScript Typisierungsinformationen hinzu und ist meiner Meinung nach die Zukunft der Entwicklung umfangreicher Anwendungen in JavaScript. Aus diesem Grund werden Sie sowohl den Client als auch den Server mit TypeScript entwickeln.
Hier sind die Bibliotheken, die Sie für den Client und den Server verwenden werden:
- Angular: Das Framework, mit dem die Client-Anwendung erstellt wird
- Okta for Authorisation: Ein Plugin, das die Single-Sign-On-Autorisierung mit Okta verwaltet, sowohl auf dem Client als auch auf dem Server
- Angular Material: Ein Angular-Plugin, das Out-of-the-Box Material Design bietet
- Node: Der eigentliche Server, auf dem der JavaScript-Code läuft
- Express: Eine Routing-Bibliothek zum Beantworten von Server-Anfragen und Erstellen von REST-APIs
- TypeORM: Eine Datenbank-ORM-Bibliothek für TypeScript
Starten Sie Ihre einfache Angular-Client-Anwendung
Lassen Sie uns mit der Implementierung eines einfachen Clients mit Angular beginnen. Das Ziel ist es, einen Produktkatalog zu entwickeln, mit dem Sie Produkte, deren Preise und Lagerbestände verwalten können. Am Ende dieses Abschnitts werden Sie eine einfache Anwendung haben, die aus einer oberen Leiste und zwei Ansichten, Home und Products, besteht. Die Ansicht „Produkte“ hat noch keinen Inhalt und ist nicht passwortgeschützt. Dies wird in den folgenden Abschnitten behandelt.
Zu Beginn müssen Sie Angular installieren. Ich gehe davon aus, dass Sie Node bereits auf Ihrem System installiert haben und Sie den Befehl npm
verwenden können. Geben Sie den folgenden Befehl in ein Terminal ein.
npm install -g @angular/[email protected]
Abhängig von Ihrem System müssen Sie diesen Befehl möglicherweise mit sudo
ausführen, da er das Paket global installiert. Das angular-cli
-Paket stellt den ng
-Befehl zur Verfügung, der zur Verwaltung von Angular-Anwendungen verwendet wird. Nach der Installation wechseln Sie in ein Verzeichnis Ihrer Wahl und erstellen Ihre erste Angular-Anwendung mit folgendem Befehl.
ng new MyAngularClient
Wenn Sie Angular 7 verwenden, erhalten Sie zwei Abfragen. Die erste fragt Sie, ob Sie das Routing einbinden wollen. Beantworten Sie diese mit Ja. Die zweite Abfrage bezieht sich auf die Art der Stylesheets, die Sie verwenden möchten. Belassen Sie es bei den Standard-CSS.
ng new
erstellt ein neues Verzeichnis namens MyAngularClient
und füllt es mit einem Anwendungsskelett. Nehmen wir uns ein wenig Zeit, um einige der Dateien zu betrachten, die der vorherige Befehl erstellt hat. Im src
-Verzeichnis der App finden Sie eine Datei index.html
, die die Hauptseite der Anwendung darstellt. Sie enthält nicht viel und spielt lediglich die Rolle eines Containers. Sie sehen auch eine style.css
Datei. Diese enthält das globale Stylesheet, das in der gesamten Anwendung angewendet wird. Wenn Sie durch die Ordner blättern, fällt Ihnen vielleicht ein Verzeichnis src/app
auf, das fünf Dateien enthält.
app-routing.module.tsapp.component.cssapp.component.htmlapp.component.tsapp.component.spec.tsapp.module.ts
Diese Dateien definieren die Hauptanwendungskomponente, die in das index.html
eingefügt wird. Hier eine kurze Beschreibung der einzelnen Dateien:
- Die
app.component.css
-Datei enthält die Stylesheets der Haupt-app
-Komponente. Stile können lokal für jede Komponente definiert werden -
app.component.html
enthält das HTML-Template der Komponente -
app.component.ts
Datei enthält den Code zur Steuerung der Ansicht -
app.module.ts
definiert, welche Module Ihre App verwenden wird -
app-routing.module.ts
wird eingerichtet, um die Routen für Ihre Anwendung zu definieren -
app.component.spec.ts
enthält ein Skelett für Unit-Tests derapp
Komponente
Ich werde in diesem Tutorial nicht auf das Testen eingehen, aber in realen Anwendungen sollten Sie von dieser Funktion Gebrauch machen. Bevor Sie loslegen können, müssen Sie noch ein paar Pakete installieren. Diese werden Ihnen helfen, schnell ein schön gestaltetes responsives Layout zu erstellen. Navigieren Sie in das Basisverzeichnis des Clients, MyAngularClient
, und geben Sie den folgenden Befehl ein.
npm i @angular/[email protected] @angular/[email protected] @angular/[email protected] @angular/[email protected]
Die Bibliotheken @angular/material
und @angular/cdk
stellen Komponenten bereit, die auf Googles Material Design basieren, @angular/animations
sorgt für weiche Übergänge, und @angular/flex-layout
gibt Ihnen die Werkzeuge an die Hand, um Ihr Design responsive zu gestalten.
Als Nächstes erstellen Sie die HTML-Vorlage für die Komponente app
. Öffnen Sie src/app/app.component.html
und ersetzen Sie den Inhalt durch den folgenden.
<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>
Das mat-toolbar
enthält die Material-Design-Symbolleiste, während router-outlet
der Container ist, der vom Router gefüllt wird. Die app.component.ts
-Datei sollte so bearbeitet werden, dass sie folgendes enthält.
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() { }}
Dies ist der Controller für die app
-Komponente. Sie können sehen, dass er eine Eigenschaft namens isAuthenticated
zusammen mit zwei Methoden login
und logout
enthält. Im Moment machen diese noch nichts. Sie werden im nächsten Abschnitt implementiert, der die Benutzerauthentifizierung mit Okta behandelt. Definieren Sie nun alle Module, die Sie verwenden werden. Ersetzen Sie den Inhalt von app.module.ts
durch den folgenden 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 { }
Beachten Sie alle Material Design Module. Die @angular/material
-Bibliothek erfordert, dass Sie für jeden Komponententyp, den Sie in Ihrer App verwenden möchten, ein Modul importieren. Beginnend mit Angular 7 enthält das Standard-Anwendungsskelett eine separate Datei namens app-routing.module.ts
. Bearbeiten Sie diese, um die folgenden Routen zu deklarieren.
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 { }
Dies definiert zwei Routen, die dem Root-Pfad und dem products
Pfad entsprechen. Außerdem werden das HomeComponent
und das ProductsComponent
an diese Routen angehängt. Erstellen Sie nun diese Komponenten. Geben Sie im Basisverzeichnis des Angular-Clients die folgenden Befehle ein.
ng generate component Productsng generate component Home
Damit wird html
erstellt, css
ts
, und spec.ts
Dateien für jede Komponente. Es aktualisiert auch app.module.ts
, um die neuen Komponenten zu deklarieren. Öffnen Sie home.component.html
im Verzeichnis src/app/home
und fügen Sie den folgenden Inhalt ein.
<div class="hero"> <div> <h1>Hello World</h1> <p class="lead">This is the homepage of your Angular app</p> </div></div>
Fügen Sie auch etwas Styling in die home.component.css
-Datei ein.
.hero { text-align: center; height: 90vh; display: flex; flex-direction: column; justify-content: center; font-family: sans-serif;}
Lassen Sie das ProductsComponent
erst einmal leer. Dies wird implementiert, sobald Sie den Back-End-REST-Server erstellt haben und ihn mit Daten füllen können. Damit alles schön aussieht, bleiben nur noch zwei kleine Aufgaben. Kopieren Sie die folgenden Stile 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;}
Zu guter Letzt, um die Material Design Icons zu rendern, fügen Sie eine Zeile innerhalb der <head>
Tags der index.html
Datei ein.
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
Sie sind nun bereit, den Angular-Server zu starten und zu sehen, was Sie bis jetzt erreicht haben. Geben Sie im Basisverzeichnis der Client-App den folgenden Befehl ein.
ng serve
Dann öffnen Sie Ihren Browser und navigieren Sie zu http://localhost:4200
.
Authentifizierung zu Ihrer Node + Angular App hinzufügen
Wenn Sie schon einmal eine Webanwendung von Grund auf entwickelt haben, wissen Sie, wie viel Arbeit es ist, nur damit sich Benutzer registrieren, verifizieren, anmelden und abmelden können. Mit Okta kann dieser Prozess stark vereinfacht werden. Für den Anfang benötigen Sie ein Entwickler-Konto bei Okta.
Navigieren Sie in Ihrem Browser zu developer.okta.com und klicken Sie auf Create Free Account und geben Sie Ihre Daten ein.
Nachdem Sie fertig sind, werden Sie zu Ihrem Entwickler-Dashboard weitergeleitet. Klicken Sie auf die Schaltfläche Anwendung hinzufügen, um eine neue Anwendung zu erstellen.
Starten Sie mit der Erstellung einer neuen Single Page Application. Wählen Sie Single Page App und klicken Sie auf Next.
Auf der nächsten Seite müssen Sie die Standardeinstellungen bearbeiten. Stellen Sie sicher, dass die Portnummer 4200 lautet. Dies ist der Standardport für Angular-Anwendungen.
Das war’s. Sie sollten nun eine Client-ID sehen, die Sie in Ihren TypeScript-Code einfügen müssen.
Um die Authentifizierung in den Client zu implementieren, installieren Sie die Okta-Bibliothek für Angular.
npm install @okta/[email protected] --save-exact
In app.module.ts
importieren Sie die OktaAuthModule
.
import { OktaAuthModule } from '@okta/okta-angular';
In der Liste der imports
des app
-Moduls hinzufügen:
OktaAuthModule.initAuth({ issuer: 'https://{yourOktaDomain}/oauth2/default', redirectUri: 'http://localhost:4200/implicit/callback', clientId: '{YourClientId}'})
Hier sollte yourOktaDomain
durch die Entwicklungsdomäne ersetzt werden, die Sie in Ihrem Browser sehen, wenn Sie zu Ihrem Okta-Dashboard navigieren. YourClientId
muss durch die Client-ID ersetzt werden, die Sie bei der Registrierung Ihrer Anwendung erhalten haben. Der obige Code macht das Okta-Authentifizierungsmodul in Ihrer Anwendung verfügbar. Verwenden Sie es in app.component.ts
, und importieren Sie den Dienst.
import { OktaAuthService } from '@okta/okta-angular';
Modifizieren Sie den Konstruktor, um den Dienst zu injizieren und ihn zu abonnieren.
constructor(public oktaAuth: OktaAuthService) { this.oktaAuth.$authenticationState.subscribe( (isAuthenticated: boolean) => this.isAuthenticated = isAuthenticated );}
Jetzt werden alle Änderungen im Authentifizierungsstatus in der isAuthenticated
-Eigenschaft wiedergegeben. Sie müssen sie noch initialisieren, wenn die Komponente geladen wird. Erstellen Sie eine ngOnInit
-Methode und fügen Sie implements OnInit
zu Ihrer Klassendefinition hinzu
import { Component, OnInit } from '@angular/core';...export class AppComponent implements OnInit { ... async ngOnInit() { this.isAuthenticated = await this.oktaAuth.isAuthenticated(); }}
Zu guter Letzt, implementieren Sie die Methode login
und logout
, um auf die Benutzeroberfläche zu reagieren und den Benutzer an- oder abzumelden.
login() { this.oktaAuth.loginRedirect();}logout() { this.oktaAuth.logout('/');}
Im Routing-Modul müssen Sie die Route registrieren, die für die Login-Anfrage verwendet werden soll. Öffnen Sie app-routing.module.ts
und importieren Sie OktaCallbackComponent
und OktaAuthGuard
.
import { OktaCallbackComponent, OktaAuthGuard } from '@okta/okta-angular';
Eine weitere Route zum routes
-Array hinzufügen.
{ path: 'implicit/callback', component: OktaCallbackComponent}
Damit kann sich der Benutzer über die Schaltfläche Login anmelden. Um die Products
-Route vor unberechtigtem Zugriff zu schützen, fügen Sie die folgende Zeile zur products
-Route hinzu.
{ path: 'products', component: ProductsComponent, canActivate: }
Das ist alles. Wenn nun ein Benutzer versucht, auf die Ansicht „Produkte“ zuzugreifen, wird er auf die Okta-Anmeldeseite umgeleitet. Nach der Anmeldung wird der Benutzer zurück zur Ansicht „Produkte“ umgeleitet.
Implementieren einer Node REST API
Der nächste Schritt ist die Implementierung eines Servers auf Basis von Node und Express, der Produktinformationen speichert. Dieser wird eine Reihe kleinerer Bibliotheken verwenden, um Ihnen das Leben leichter zu machen. Für die Entwicklung in TypeScript benötigen Sie typescript
und tsc
. Für die Datenbankabstraktionsschicht werden Sie TypeORM verwenden. Dies ist eine praktische Bibliothek, die Verhalten in TypeScript-Klassen injiziert und diese in Datenbankmodelle umwandelt. Legen Sie ein neues Verzeichnis an, das Ihre Serveranwendung enthält, und führen Sie darin den folgenden Befehl aus.
npm init
Beantworten Sie alle Fragen und führen Sie dann aus:
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]
Ich werde nicht auf alle diese Bibliotheken im Detail eingehen, aber Sie werden sehen, dass @okta/jwt-verifier
verwendet wird, um JSON-Web-Token zu verifizieren und zu authentifizieren.
Um TypeScript zum Laufen zu bringen, erstellen Sie eine Datei tsconfig.json
und fügen den folgenden Inhalt ein.
{ "compilerOptions": { "target": "es6", "module": "commonjs", "outDir": "dist", "experimentalDecorators": true, "emitDecoratorMetadata": true }, "include": , "exclude": }
Der erste Schritt bei der Erstellung des Servers besteht darin, ein Datenbankmodell für das Produkt zu erstellen. Mit TypeORM ist das ganz einfach. Legen Sie ein Unterverzeichnis src
an und erstellen Sie darin eine Datei model.ts
. Fügen Sie den folgenden Inhalt ein.
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;}
Die TypeORM-Annotationen verwandeln die Klassendefinition in ein Datenbankmodell. Ich liebe das TypeORM-Projekt wegen seiner Benutzerfreundlichkeit und der großen Vielfalt an SQL- und NoSQL-Datenbankkonnektoren, die unterstützt werden. Ich schlage vor, dass Sie sich die Dokumentation unter https://github.com/typeorm/typeorm ansehen.
Sie benötigen außerdem Zugang zu einem Repository des Produkts. Fügen Sie außerdem in der Datei model.ts
Folgendes hinzu.
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);}
Beachten Sie, dass hier der Einfachheit halber SQLite verwendet wird. In einem realen Szenario sollten Sie dies durch einen Datenbank-Connector Ihrer Wahl ersetzen.
Nächstes erstellen Sie eine Datei namens product.ts
. Diese Datei wird die Logik für alle Routen für die CRUD-Operationen auf Produkte enthalten.
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); }});
Diese Datei ist etwas umfangreich, enthält aber nichts Überraschendes. Product
Objekte werden erstellt und in der Datenbank gespeichert oder aus ihr gelöscht.
Wenden wir uns wieder der Authentifizierung zu. Sie werden sicherstellen wollen, dass nur authentifizierte Benutzer auf den Dienst zugreifen können. Erstellen Sie eine Datei mit dem Namen auth.ts
und fügen Sie das Folgende ein.
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); }}
Gleich wie in der Client-Anwendung sollte yourOktaDomain
durch die Entwicklungsdomäne und YourClientId
durch die Client-ID Ihrer Anwendung ersetzt werden. Die oktaJwtVerifier
-Instanz nimmt ein JWT-Token und authentifiziert es. Wenn sie erfolgreich ist, werden die Benutzerkennung und die E-Mail in req.user
gespeichert. Andernfalls antwortet der Server mit einem Statuscode 401. Das letzte Stück zur Fertigstellung des Servers ist der Haupteinstiegspunkt, der den Server tatsächlich startet und die Middleware registriert, die Sie bisher definiert haben. Erstellen Sie eine Datei server.ts
mit dem folgenden Inhalt.
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');});
Um das TypeScript zu kompilieren, führen Sie den Befehl
npx tsc
Dann, wenn Sie den Server starten möchten, führen Sie einfach aus:
node dist/server.js
Fertigen Sie Ihren Angular-Client
Nun, da der Server fertig ist, können wir den Client fertigstellen. Der erste Schritt besteht darin, eine Klasse zu erstellen, die die Produktdaten enthält. Diese Klasse ähnelt der Klasse Product
in der Server-Anwendung, jedoch ohne die TypeORM-Annotationen. Sie wird in einer Datei mit dem Namen product.ts
enthalten sein.
export class Product { id?: string; name: string; sku: string; description: string; price: number; stock: number;}
Speichern Sie diese Datei in demselben Verzeichnis wie die products
-Komponente. Am besten kapseln Sie den Zugriff auf die REST-API in einem eigenen Dienst. Erstellen Sie einen Products
-Dienst, indem Sie den folgenden Befehl ausführen.
ng generate service products/Products
Damit wird eine Datei namens product.service.ts
im Verzeichnis src/app/products
erstellt. Füllen Sie es mit dem folgenden Inhalt.
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}`); }}
Das ProductsService
enthält eine öffentliche Methode für jede Route der REST-API. Die HTTP-Anfrage wird in einer eigenen Methode gekapselt. Beachten Sie, dass die Anfrage immer ein Bearer
-Token enthält, das aus dem OktaAuthService
stammt. Dies ist das Token, das vom Server zur Authentifizierung des Benutzers verwendet wird.
Nun kann das ProductsComponent
implementiert werden. Der folgende Code erfüllt diesen Zweck.
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(); }}
Das Layout, in products.component.html
, das das Produkt zeigt, besteht aus zwei Teilen. Der erste Teil verwendet eine mat-table
Komponente, um eine Liste von Produkten anzuzeigen. Der zweite Teil zeigt ein Formular, in dem der Benutzer ein neues oder bestehendes Produkt bearbeiten kann.
<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>
Zuletzt fügen Sie dem Layout noch ein wenig Styling in products.component.css
hinzu.
.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);}
Wenn alles fertig ist, können Sie den Client und den Server hochfahren und Ihre Anwendung testen. Um es noch einmal zu wiederholen: In dem Verzeichnis, in dem sich der Server befindet, führen Sie aus:
node dist/server.js
Und im Client-Verzeichnis führen Sie aus:
ng serve
Ihre Anwendung sollte in etwa wie folgt aussehen
Lernen Sie mehr über Angular, Node und Express
In diesem Tutorial habe ich Sie durch die Entwicklung einer einseitigen Webanwendung mit Angular und Node geführt. Mit nur wenigen Zeilen Code konnten Sie eine Benutzerauthentifizierung für den Client und den Server implementieren. Angular verwendet TypeScript, das eine Obermenge der JavaScript-Sprache ist und Typinformationen hinzufügt. TypeScript sorgt für stabileren Code und deshalb habe ich mich entschieden, auch den Node/Express-Server mit dieser Sprache zu implementieren. Wenn Sie noch nicht mit TypeScript vertraut sind, schauen Sie sich diese großartige Einführung von Todd Motto an. Er hat auch einige gute Artikel über Angular.
Den kompletten Code dieses Tutorials finden Sie auf GitHub.
Wenn Sie bereit sind, mehr über Angular oder Node/Express zu lernen, haben wir einige andere Ressourcen für Sie:
- Einfache Node-Authentifizierung
- Baue eine einfache CRUD-App mit Node und React
- Baue einen einfachen API-Service mit Express und GraphQL
- Angular 6 – Was ist neu und warum ein Upgrade?
- Build a Basic CRUD App with Angular 7 and Spring Boot
Und wie immer würden wir uns freuen, wenn Sie uns für weitere coole Inhalte und Updates aus unserem Team folgen. Sie können uns auf Twitter @oktadev, auf Facebook und LinkedIn finden.