Dieses Tutorial zeigt Ihnen, wie Sie eine "MEAN stack" (app stack) Anwendung mit Abwärtskompatibilität für ältere Browser erstellen können.
Wir werden eine Webanwendung erstellen, die "CRUD"-Operationen (steht für: create, replace, update und delete) ausführt.
Eine "MEAN"-Stack-App hat ein "Backend", das "NodeJS" verwendet, und ein "Frontend", das "Angular" verwendet. "Express" ist ein Server, der den Zugriff auf das "Backend" von außen (das "Frontend") erlaubt. "MongoDB" wird als Datenbank verwendet, bei der es sich um eine NoSQL-Datenbank handelt, die Daten in Textdateien ("JSON"-Format) speichert.
Die Programmiersprache "JavaScript" wird mit dem "NodeJS"-Framework im "Backend" verwendet. Das "Frontend" verwendet auch "TypeScript", das "JavaScript" ähnlich ist und volle Unterstützung von Datentypen bietet.
"TypeScript" hat auch mehr Funktionalität. Mehr Informationen über "TypeScript":
https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html
Anforderungen
Sie müssen MongoDB installieren:
https://www.mongodb.com/download-center/community
MongoDB muss bereits konfiguriert sein. In diesem Tutorial läuft MongoDB auf localhost auf der gleichen Maschine.
Sie müssen NodeJS installieren:
https://nodejs.org/en/download/
Sie müssen Angular CLI installieren:
npm install -g @angular/cli
Jetzt können wir mit der Erstellung unserer App beginnen. Weitere Abhängigkeiten werden später installiert.
Bitte erstellen Sie einen Projektordner für unsere "MEAN"-Stack-App. Wir werden diese Anwendung "mean_crud_example" nennen.
NodeJS - Backend
Wir werden mit dem "Backend" unserer Anwendung beginnen. Sie müssen das "Backend" mit dem "npm"-Paketmanager Ihrer "NodeJS"-Installation einrichten.
Erstellen Sie einen Ordner mit dem Namen "backend".
mkdir backend
Führen Sie diesen Befehl in diesem Ordner aus:
npm init
Sie werden nach der Konfiguration Ihrer "NodeJS"-Anwendung gefragt. Sie können aber die Standardeinstellungen verwenden. Unser "NodeJS"-Anwendungscode wird hauptsächlich in der Datei "server.js" gespeichert, die wir später erstellen werden.
Wir werden "Babel" verwenden, um die Abwärtskompatibilität unserer "Anwendung" sicherzustellen. Aufgrund von "Babel" können wir auch "Javascript ES6"-Code in unserer "NodeJS"-Anwendung verwenden.
Bitte installieren Sie die folgenden Pakete:
npm install --save-dev @babel/core @babel/cli @babel/preset-env babel-watch
npm install --save @babel/polyfill
"Babel" benötigt die ".babelrc"-Konfigurationsdatei, die sich im Stammordner unseres "Backends" befinden sollte.
Die ".babelrc"-Datei muss den folgenden Inhalt haben:
{
"presets": [
"@babel/preset-env"
]
}
Sie können die Funktionalität von Babel in diesem Konfigurationsformular anpassen. Sie können z.B. konfigurieren, welche Browser-Version angestrebt werden soll (Befehl: "Ziele"). Wenn Sie mehr über ".babelrc" oder "babel.config.json" (für große Projekte) wissen wollen, dann gehen Sie bitte zur "Babel"-Dokumentation:
https://babeljs.io/docs/en/config-files
Sie müssen auch Ihre "package.json"-Datei bearbeiten, damit "babel-watch" automatisch startet. Sie müssen Folgendes zu Ihrer "package.json"-Datei hinzufügen:
"scripts": {
"dev": "babel-watch server.js",
"build": "babel src -d build"
}
Dies erzeugt auch den Verknüpfungsbefehl "dev", der mit unserem Programmaufruf "babel-watch" verknüpft ist. Sie können die Verknüpfung nun wie folgt verwenden: "npm run dev".
Wenn Sie diesen Verknüpfungsbefehl ausführen, dann wird jede gespeicherte Änderung unserer Datei "server.js" automatisch in einen abwärtskompatiblen Code umgewandelt (kompatibel für "NodeJS server").
Express Framework - Backend
"Express" wird als Middleware zwischen unserem Backend "NodeJS" und dem Frontend "Angular" eingesetzt. Sie müssen die Abhängigkeit "express" installieren:
npm install express
Die Abhängigkeit "cors" wird installiert, um die Sicherheit der App zu verbessern. "body-parser" wird verwendet, um eingehende "HTTP-Anforderungen" in unserem "Express"-Server zu parsen.
npm install cors body-parser
Diese Abhängigkeit ermöglicht die Konfiguration von CORS (Cross-Origin Resource Sharing), das die externen Domänennamen definiert, die in dieser App verwendet werden können. Hacker können dann keine Skripte von anderen Sites injizieren, da mit "Cors" nur bestimmte hinzugefügte Sites eine Verbindung zu dieser App herstellen können.
Es ist nun an der Zeit, die Datei "server.js" mit folgendem Inhalt zu erstellen:
import express from 'express';
import cors from 'cors';
import mongoose from 'mongoose';
import bodyParser from 'body-parser';
const app = express();
const router = express.Router();
const DATABASE_CONNECTION = "mongodb://127.0.0.1/tasks";
app.use(cors());
app.use(bodyParser.json());
mongoose.connect(DATABASE_CONNECTION);
const connection = mongoose.connection;
connection.once("open", () => {
console.log("Mongoose (MongoDB) database connection was created successfully!");
});
Fügen Sie auch den Code hinzu, der den "Express"-Server einrichtet
app.use('/api', router);
app.listen(4000, () => console.log("Your Express server runs on the port 4000"));
Sie müssen Ihre vollständige "mongodb"-Datenbankserver-Adresse mit dem Datenbanknamen (hier: "tasks") in die Variable "DATABASE_CONNECTION" eintragen. Dieser Code stellt eine Datenbankverbindung zu Ihrem "Mongodb"-Server her. Wenn die Verbindung erfolgreich hergestellt wurde, dann wird die Zeichenfolge ""Mongoose (MongoDB) Datenbankverbindung wurde erfolgreich hergestellt!
Danach lauscht der "Express"-Server auf der Seite "/api" nach den aufgerufenen "Routen" unserer "REST API" im "Express"-Server. Die API wird später erstellt.
Info: Wenn Sie diese Anwendung auf "Heroku" hosten wollen, dann verwenden Sie stattdessen den folgenden Code:
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'public/index.html'));
});
const expressServerPort = process.env.PORT || 5000;
app.listen(expressServerPort, () => console.log("Express Server is currently running on the port " + expressServerPort));
MongoDB - Datenbank Backend
Nun werden wir die Datenbank unserer App konfigurieren. Wenn Sie "mongodb" nicht installiert haben, dann installieren Sie es bitte mit diesem Befehl:
npm install mongoose
In "mongodb" gibt es "Sammlungen", die den Tabellen (in SQL) entsprechen. Eine "Sammlung" enthält Dokumente, die den Zeilen (in SQL) entsprechen.
Eine "Sammlung" in Ihrer "MongoDB"-Datenbank kann durch die Erstellung eines "Schemas" in Ihrer Anwendung hinzugefügt werden. Bitte erstellen Sie den Ordner "model", der die "Schemas" dieser Anwendung enthalten wird.
Erstellen Sie bitte die Datei "Tasks.js" mit folgendem Inhalt:
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
let Tasks = new Schema({
title: {
type: String,
required: [true, "Please enter a task title"]
},
description: {
type: String
},
priority: {
type: Number,
default: 0
}
});
export default mongoose.model('Tasks', Tasks);
Dieses "Schema" hat ein Pflichtfeld "Titel". Wenn das erforderliche Feld leer ist, dann erhalten Sie die definierte Fehlermeldung. Das Feld "Priorität" hat einen Standardwert von 0.
Bitte importieren Sie dieses "Schema" in die Datei "server.js", da wir es für den Zugriff auf Daten in der Datenbank dieser Anwendung verwenden werden:
import Tasks from './model/Tasks';
Erstellen einer REST-API für den Zugriff auf Datenbankdaten
Sie können eine "REST-API" erstellen, indem Sie "Routen" in Ihrer "NodeJS"-Anwendung verwenden. Sie verwenden "HTTP"-Befehle "GET" und "POST" zum Abrufen und Senden von Datenbankdaten. Auf diese "REST-API" wird dann über "Angular Services" zugegriffen, die später im "Frontend"-Teil dieses Tutorials erstellt werden.
"HTTP GET" wird hier verwendet, um eine bestimmte Aufgabe oder bestimmte Aufgaben abzufragen. Aber auch, um eine bestimmte Aufgabe oder Aufgaben zu löschen. Sie übergeben alle Argumente im URL-Link (vollständige Adresse).
Beispiel: "/api/task/4" - "4" ist die Id der Aufgabe.
"HTTP POST" wird verwendet, um eine Aufgabe zu speichern oder zu bearbeiten. Alle Werte und Argumente werden nicht in der URL hinzugefügt. Aber sie werden in den Daten der Website ("HTML-Formular") hinzugefügt.
Der Datenbankaufruf erfolgt im "route"-Befehl. Die importierte Schema-Klasse wird verwendet, um eine "MongoDB"-Funktion aufzurufen. Die "MongoDB"-Funktion erhält eine Callback-Funktion, die aufgerufen wird, wenn Daten aus der Datenbank zurückgeschickt wurden. In diesem Fall wird die asynchrone Programmierung verwendet.
Bitte erstellen Sie die folgenden "Routen" für die "REST API" dieser Anwendung. Fügen Sie die folgenden Codes in Ihre "server.js"-Datei ein:
Abrufen aller Aufgaben
router.route("/tasks").get((req, res) => {
Tasks.find((err, tasks) => {
if (err) {
console.log(err);
} else {
res.json(tasks);
}
});
});
Abrufen einer bestimmten Aufgabe über die Aufgaben-ID:
router.route("/task/:id").get((req, res) => {
Tasks.findById(req.params.id, (err, task) => {
if (err) {
console.log(err);
} else {
res.json(task);
}
});
});
Die Aufgabe kann über die Variable "_id" abgefragt werden, die der Primärschlüssel des gespeicherten Datensatzes (Zeile) ist.
Hinzufügen einer neuen Aufgabe
router.route('/task/add').post((req, res) => {
let newTask = new Tasks(req.body);
newTask.save()
.then(task => {
res.status(200).json("Task was added.");
})
.catch(err => {
res.status(400).json("Error! Task could not be saved.");
console.log(err);
});
});
Die Aufgabe wird im Frontend "Angular" erstellt und dann hier wie in Objekt im "Body" der "HTTP"-Daten übergeben.
Aktualisieren einer Aufgabe
router.route('/task/update/:id').post((req, res) => {
Tasks.findById(req.params.id, (err, task) => {
if (!task)
res.status(400).json("The task that should be updated was not found.");
else {
task.title = req.body.title;
task.description = req.body.description;
task.priority = req.body.priority;
task.save().then(task => {
res.status(200).json("Task was successfully updated.");
}).catch(err => {
res.status(400).json("Error! Task could not be updated.");
console.log(err);
});
}
});
});
Die jeweilige Aufgabe wird mit dem Befehl "findById" aus der Datenbank geladen. Danach wird der Inhalt der geladenen Aufgabe geändert und gespeichert.
Alle Aufgaben löschen
router.route("/tasks/deleteall").get((req, res) => {
Tasks.remove({}, (err) => {
if (err) {
res.status(400).json("Error. Tasks could not be deleted.");
console.log(err);
} else {
res.status(200).json("Tasks were successfully deleted.");
}
});
});
Eine bestimmte Aufgabe löschen
router.route("/task/delete/:id").get((req, res) => {
Tasks.findByIdAndRemove({ _id: req.params.id }, (err, task) => {
if (err) {
res.status(400).json("Error! Task could not be deleted.");
console.log(err);
} else {
res.status(200).json("Task was successfully deleted.");
}
});
});
Dies war die Schaffung der "REST API" dieser App. Sie können sie mit jedem "REST API"-Client wie "Swagger UI" oder "Postman" testen.
Angular - Frontend
Wir werden die Ansicht in "HTML" und den Frontend-Programmcode in "TypeScript" erstellen. Für die Benutzerschnittstelle (Eingabefelder, Tabelle, Stil usw.) wird "Angular Material" verwendet.
Einrichten der Konfiguration von "Angular".
Wir werden "Angular" für das "Frontend" unserer App verwenden. Deshalb werden wir eine eckige App namens "Frontend" erstellen.
ng new frontend
Folgen Sie den Anweisungen und klicken Sie auf "yes", wenn Sie nach "Angular routing" gefragt werden. Bitte wählen Sie "CSS" als Stylesheet aus.
Wechseln Sie nun in den Ordner Ihrer neu erstellten "Angular"-Anwendung.
Wir müssen nun unsere "Angular"-Abhängigkeiten hinzufügen:
ng add @angular/material
Sie werden nach dem Thema Ihres Anwendungsdesigns und einigen Designeinstellungen gefragt.
Erstellen der Komponenten unserer "Angular"-App
Eine "Angular"-Webanwendung muss Komponenten enthalten, die den Inhalt der Webanwendung anzeigen. Zum Beispiel haben Sie eine Komponente, die Ihre Kontaktdaten anzeigt, eine Komponente zur Auflistung Ihrer Aufgaben usw.
Wir werden diese Komponenten erstellen, die für unsere "CRUD"-Anwendung benötigt werden.
ng g c components/view
Komponente, die Aufgaben anzeigt. Dieser Befehl ist eine Abkürzung des Befehls "ng generate component components/view".
ng g c components/create
Komponente, die Aufgaben erstellt.
ng g c components/edit
Komponente, die Aufgaben bearbeitet.
Jetzt müssen wir das "Routing" unserer Anwendung hinzufügen. Dies definiert den Link, der verwendet wird, um auf eine bestimmte "Komponente" in unserer "Angular"-Anwendung zuzugreifen.
Gehen Sie zu Ihrer "app-routing.module.ts"-Datei. Die Datei befindet sich im Pfad "src/app".
Und fügen Sie den folgenden Inhalt hinzu: "app-routing.module.ts":
const routes: Routes = [
{ path: 'view', component: ViewComponent },
{ path: 'edit/:id', component: EditComponent },
{ path: 'list', component: ListComponent },
{ path: '', redirectTo: '/list', pathMatch: 'full'}
];
Sie können "Angular"-Module in der Datei "app.module.ts" importieren.
Wir werden für diese Anwendung das Modul "MatToolbar" verwenden. Das Modul kann mit diesen Befehlen importiert werden:
Fügen Sie diese Import-Anweisung am Anfang dieser Datei hinzu:
import { MatToolbarModule } from '@angular/material/toolbar';
Sie müssen auch das Modul in der Sektion "Import" hinzufügen:
imports: [
...
MatToolbarModule
...
]
Wir müssen auch das Modul "HttpClientModule" hinzufügen, das für den Zugriff auf unsere "REST API" in unserem "Express"-Server benötigt wird.
Bitte fügen Sie das auch in unsere "app.module.ts" ein:
import { HttpClientModule } from '@angular/common/http';
imports: [
...
HttpClientModule
...
]
Komponenten Ihrer Anwendung werden im Tag "<router-outlet>" Ihrer Datei "app.component.html" hinzugefügt.
Wir werden die folgende "app.component.html" erstellen:
<mat-toolbar>
<span class="appTitle">MEAN Stack CRUD Example APP - Tasks</span>
<br>
<br>
</mat-toolbar>
<h4 class="centered-text">This is a demo app.</h4>
<div>
<router-outlet></router-outlet>
</div>
Dies ist die Wurzel Ihres Webapplikations-Frontends und ist auch eine Komponente. Jede Komponente hat eine "html"-Datei und eine "CSS"-Datei, die das Frontend Ihrer Komponente verändern. Aber auch eine ".ts"-Datei, die den Programmiercode (Geschäftslogik) einer Komponente definiert.
Unsere Stamm-"app.component" hat auch eine benutzerdefinierte "css"-Datei. Bitte fügen Sie Folgendes zu Ihrer "app.component.css"-Datei hinzu:
.appTitle{
font-size: 16px;
color: orange;
}
.centered-text{
text-align: center;
font-size: 15px;
font-weight: bold;
margin: 10px;
color: red;
}
Erstellen des "Services" unserer Angular-APP
Jetzt ist es an der Zeit, einen "Angular Service" einzurichten, um auf unsere Datenbankdaten zuzugreifen. Dies kann mit diesem Befehl geschehen:
ng g s Tasks
Dieser Befehl ist eine Abkürzung des Befehls "ng generate service". Wenn Sie den Namen "Tasks" verwendet haben, dann heißt der "Angular Service" "TasksService".
Sie müssen den erstellten "Angular Service" in die Datei "app.module.ts" importieren:
import { TasksService } from './tasks.service';
Fügen Sie auch dies in der Datei "app.module.ts" hinzu:
providers: [TasksService],
Fügen Sie Folgendes zu dem neu erstellten "TasksService" hinzu:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class TasksService {
API_URL = "http://127.0.0.1:4000/api";
constructor(private httpClient: HttpClient) { }
}
"API_URL" hat die vollständige Adresse zu unserem "Express"-Server und "REST API".
Jetzt müssen Sie im "Backend" Erstellungsfunktionen für alle "Routen" hinzufügen, die in unserer "REST API" definiert wurden.
Fügen Sie die folgenden Funktionen zu Ihrem "TasksService" hinzu:
//Call the "REST API" to get a certain task through the task id
getTaskById(id) {
return this.httpClient.get(`${this.API_URL}/task/${id}`);
}
//Returns all tasks in the database
getTasks() {
return this.httpClient.get(`${this.API_URL}/tasks`);
}
//Creates a new task object and send it to the "REST API" to save it
addTask(title, description, priority) {
const newTask = {
title: title,
description: description,
priority: priority
};
return this.httpClient.post(`${this.API_URL}/task/add`, newTask);
}
//Creates a new task object and send task id and task object to the "REST API" to update it
updateTask(id, title, description, priority) {
const updatedTask = {
title: title,
description: description,
priority: priority
};
return this.httpClient.post(`${this.API_URL}/task/update/${id}`, updatedTask)
}
//Delete task - Returns only info or error message
deleteTaskById(id) {
return this.httpClient.get(`${this.API_URL}/task/delete/${id}`);
}
//Delete all tasks - Returns only info or error message
deleteAllTasks() {
return this.httpClient.get(`${this.API_URL}/task/deleteall`);
}
Sie können nun diesen "Angular Service" "Task Services" verwenden, wenn Sie ihn importieren und als Variable in den Konstruktor Ihrer Komponentenklasse einfügen (injizieren).
Dies wird später in diesem Tutorial geschehen.
Implementierung der erstellten "Angular"-Komponenten
Nun haben wir den letzten Teil dieses Tutorials erreicht. Sie müssen nun noch die "Angular Modules" hinzufügen, die wir in unseren "Angular Components" verwenden werden.
Bitte fügen Sie Folgendes in Ihre "app.module.ts"-Datei ein:
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatTableModule } from '@angular/material/table';
import { MatDividerModule } from '@angular/material/divider';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatSliderModule } from '@angular/material/slider';
import { MatPaginatorModule } from '@angular/material/paginator';
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
MatToolbarModule,
FormsModule,
ReactiveFormsModule,
MatFormFieldModule,
MatInputModule,
MatIconModule,
MatButtonModule,
MatCardModule,
MatTableModule,
MatDividerModule,
MatSnackBarModule,
MatSliderModule,
MatPaginatorModule
],
Sie müssen eine "TypeScript"-Klasse für das "MongoDB-Schema" "Aufgaben" erstellen. Diese wird benötigt, um das "MongoDB"-Objekt auf ein "Javascript"-Objekt abzubilden.
Erstellen Sie die Datei "tasks.model.ts" mit folgendem Inhalt:
export interface Tasks {
id: String;
title: String;
description: String;
priority: Number;
}
Nun müssen Sie alle erstellten Komponenten mit den folgenden Inhalten implementieren:
ViewComponent
Diese Komponente zeigt die Aufgaben in einer Tabelle an. Für jede Aufgabe in der Tabelle gibt es eine Schaltfläche zum Bearbeiten und Löschen.
Typescript-Komponentenklasse "view.component.ts":
import { Component, OnInit } from '@angular/core';
import { TasksService } from 'src/app/tasks.service';
import { Router } from '@angular/router';
import { Tasks } from 'src/tasks.model';
import { MatSnackBar } from '@angular/material/snack-bar';
@Component({
selector: 'app-view',
templateUrl: './view.component.html',
styleUrls: ['./view.component.css']
})
export class ViewComponent implements OnInit {
tasks: Tasks[];
tableColumns = ['title', 'description', 'priority', 'edit'];
constructor(private snackBar: MatSnackBar, private tasksService: TasksService, private router: Router) {
this.loadTasks();
}
ngOnInit(): void {
}
loadTasks() {
this.tasksService.getTasks().subscribe((tasksData: Tasks[]) => {
this.tasks = tasksData;
});
}
editTask(id) {
this.router.navigate([`/edit/${id}`]);
}
deleteTask(id) {
this.tasksService.deleteTaskById(id).subscribe(() => {
this.loadTasks();
this.snackBar.open("Task was deleted.", "OK", {
duration: 4000
});
})
}
}
HTML-Datei "view.component.html":
<div>
<br>
<mat-card>
<button mat-raised-button color="primary" routerLink="/create">Add new task</button>
<br><br>
<mat-divider></mat-divider>
<br>
<h3>All Tasks</h3>
<br>
<table mat-table [dataSource]="tasks">
<ng-container matColumnDef="title">
<th mat-header-cell *matHeaderCellDef> Title </th>
<td mat-cell *matCellDef="let element"> {{element.title}} </td>
</ng-container>
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef> Description </th>
<td mat-cell *matCellDef="let element"> {{element.description}} </td>
</ng-container>
<ng-container matColumnDef="priority">
<th mat-header-cell *matHeaderCellDef> Priority </th>
<td mat-cell *matCellDef="let element"> {{element.priority}} </td>
</ng-container>
<ng-container matColumnDef="edit">
<th mat-header-cell *matHeaderCellDef class="mat-column-right"> Actions </th>
<td mat-cell *matCellDef="let element" class="mat-column-right">
<button mat-button color="primary" (click)="editTask(element._id)">Edit</button>
<button mat-button color="warn" (click)="deleteTask(element._id)">DELETE</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="tableColumns"></tr>
<tr mat-row *matRowDef="let row; columns: tableColumns;"></tr>
<mat-paginator [length]="500" [pageSize]="10" [pageSizeOptions]="[5, 10, 25, 100, 500]">
</mat-paginator>
</table>
<div *ngIf="tasks == null || tasks.length < 1">
<h4 class="noTasksInfo">You have no tasks!</h4>
</div>
</mat-card>
</div>
Der Befehl " ng-container matColumnDef="actions" " definiert die Spalte unserer Tabelle. Alle Zeilen aus unserem Array werden dann mit Hilfe dieses Befehls " mat-row *matRowDef="let row; columns: tableColumns;" " angezeigt.
"*ngIf" wird verwendet, um mittels "TypeScript"-Code zu entscheiden, ob ein bestimmter "HTML-Tag" angezeigt werden soll oder nicht.
CSS-Datei "view.component.css":
table {
width: 100%
}
.mat-column-right {
text-align: center;
}
CreateComponent
Diese Komponente wird zum Speichern einer Aufgabe verwendet. Ein "Formular"-Modul wird verwendet, um dies zu realisieren.
Typescript-Komponentenklasse "create.component.ts":
import { Component, OnInit } from '@angular/core';
import { TasksService } from 'src/app/tasks.service';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { MatSnackBar } from '@angular/material/snack-bar';
@Component({
selector: 'app-create',
templateUrl: './create.component.html',
styleUrls: ['./create.component.css']
})
export class CreateComponent implements OnInit {
createTaskForm: FormGroup;
priorityValue = 0;
constructor(private snackBar: MatSnackBar, private tasksService: TasksService, private formBuilder: FormBuilder, private router: Router) {
this.createTaskForm = this.formBuilder.group({
title: ['', Validators.required],
description: '',
priority: 0
});
}
ngOnInit(): void {
}
createTask(title, description, priority) {
this.tasksService.addTask(title, description, priority).subscribe(() => {
this.router.navigate(['/']);
this.snackBar.open("OK. Task was added.", "OK", {
duration: 4000
});
})
}
}
HTML-Datei "create.component.html":
<div>
<br>
<mat-card>
<br>
<h3>Add A New Task</h3>
<mat-divider></mat-divider>
<br>
<form [formGroup]="createTaskForm" class="create-task-form">
<mat-form-field class="column-full-width">
<input matInput placeholder="Task Title" formControlName="title" #title>
</mat-form-field>
<mat-form-field class="column-full-width">
<input matInput placeholder="Task Description" formControlName="description" #description>
</mat-form-field>
<div class="column-full-width">
<h4>Priority: </h4>
<mat-slider thumbLabel tickInterval="1" min="1" max="10" [(ngModel)]="priorityValue" formControlName="priority" #priority>
</mat-slider>
</div>
<mat-divider></mat-divider>
<br><br>
<button class="saveButton" type="submit" (click)="createTask(title.value, description.value, priority.value)"
[disabled]="createTaskForm.pristine || createTaskForm.invalid" mat-raised-button
color="primary">Save</button>
<button mat-raised-button color="accent" routerLink="/">Back</button>
</form>
</mat-card>
</div>
Der Befehl "" [disabled]="createTaskForm.pristine || createTaskForm.invalid" "" deaktiviert die Schaltfläche Senden, wenn mindestens ein Textfeld nicht angeklickt wurde oder falsche Daten in den Textfeldern vorhanden sind.
"[(ngModel)]" wird verwendet, um den Wert eines "Angular"-Oberflächenelements zu definieren.
CSS-Datei "create.component.css":
.create-task-form {
min-width: 160px;
width: 100%;
}
.column-full-width {
width: 100%;
}
.saveButton{
margin-right: 15px;
}
EditComponent
Diese Komponente wird zur Bearbeitung einer Aufgabe verwendet. Ein "Formular"-Modul wird verwendet, um dies zu realisieren.
Typescript-Komponentenklasse "edit.component.ts":
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { Router, ActivatedRoute } from '@angular/router';
import { TasksService } from 'src/app/tasks.service';
import { MatSnackBar } from '@angular/material/snack-bar';
@Component({
selector: 'app-edit',
templateUrl: './edit.component.html',
styleUrls: ['./edit.component.css']
})
export class EditComponent implements OnInit {
id: String;
task: any = {};
updateTaskForm: FormGroup;
priorityValue = 0;
constructor(private tasksService: TasksService,private router: Router, private route: ActivatedRoute, private snackBar: MatSnackBar, private formBuilder: FormBuilder ) {
this.loadEditTaskForm();
}
ngOnInit(): void {
this.route.params.subscribe(params => {
this.id = params.id;
this.tasksService.getTaskById(this.id).subscribe(res => {
this.task = res;
this.updateTaskForm.get("title").setValue(this.task.title);
this.updateTaskForm.get("description").setValue(this.task.description);
this.priorityValue = this.task.priority;
});
});
}
loadEditTaskForm(){
this.updateTaskForm = this.formBuilder.group({
title: ['', Validators.required],
description: '',
priority: 0
});
}
editTask(title, description, priority){
this.tasksService.updateTask(this.id, title, description, priority).subscribe(() => {
this.router.navigate(['/']);
this.snackBar.open('Task was updated successfully.', 'OK', {
duration: 3000
});
});
}
}
HTML-Datei "edit.component.html":
<div>
<br>
<mat-card>
<h3>Edit Task</h3>
<mat-divider></mat-divider>
<br>
<form [formGroup]="updateTaskForm" class="edit-task-form">
<mat-form-field class="column-full-width">
<input matInput placeholder="Task Title" formControlName="title" #title>
</mat-form-field>
<mat-form-field class="column-full-width">
<input matInput placeholder="Task Description" formControlName="description" #description>
</mat-form-field>
<div class="column-full-width">
<mat-slider thumbLabel tickInterval="1" min="1" max="10" [(ngModel)]="priorityValue" formControlName="priority" #priority></mat-slider>
</div>
<mat-divider></mat-divider>
<br><br>
<button class="saveButton" type="submit" (click)="editTask(title.value, description.value, priority.value)" mat-raised-button
color="primary">Save</button>
<button mat-raised-button color="accent" routerLink="/">Back</button>
</form>
</mat-card>
</div>
CSS-Datei "edit.component.css":
.edit-task-form {
min-width: 160px;
width: 100%;
}
.column-full-width {
width: 100%;
}
.saveButton{
margin-right: 15px;
}
Die Erstellung dieser App ist nun abgeschlossen.
Wenn Sie diese App starten wollen, dann führen Sie diese 2 Befehle in getrennten Terminals aus:
Im "Backend"-Ordner:
npm run dev
Im Ordner "Frontend":
ng serve --open
Wenn Sie diese Anwendung auf einem Webserver erstellen möchten (Produktionseinsatz), dann verwenden Sie diese Befehle:
Gehen Sie zum Ordner "Backend". Kopieren Sie alle Ihre JavaScript-Dateien in den Ordner "src". Danach führen Sie diesen Befehl aus:
npm run build
Im Ordner "Frontend":
ng build --prod
Dies war eine Einführung in die Erstellung einer "MEAN stack"-Anwendung mit "CRUD"-Funktionalität. Mit Hilfe des Paketmanagers "npm" und "Angular modules" können Sie auch eine ganze Reihe von Funktionen hinzufügen.
Diese Anwendung auf Github:
https://github.com/a-dridi/mean_crud_example
Mehr über MongoDB:
https://docs.mongodb.com/manual/core/databases-and-collections/
REST API Client - Swagger UI:
https://swagger.io/tools/swagger-ui/
REST API Client - Postman:
https://www.postman.com/
Angular Material:
https://material.angular.io/
Angular:
https://angular.io/docs
NodeJS:
https://nodejs.org/en/docs/