Angular 4:如何从HTML模板将数据保存至Mock JSON数据库
Hey there, let's break down why you can't save directly to a JSON file from your Angular 4 app, and walk through practical solutions to fix this!
First off, the key issue here is that browser-side JavaScript (including Angular) doesn't have permission to modify static files on your server or local filesystem directly. Browsers enforce security restrictions that prevent this kind of file system access for good reason—imagine if every website could edit files on your computer or server without permission! So your current approach of trying to write to a .json file directly from your Angular service won't work, but there are solid workarounds depending on your needs.
1. Use a Backend API (Production-Grade Solution)
This is the standard approach if you need data to persist across users or sessions on a server. You'll need a simple backend service (like Node.js/Express) to receive data from your Angular app and handle writing to the JSON file.
Example Backend (Node.js/Express)
// server.js const express = require('express'); const fs = require('fs'); const path = require('path'); const app = express(); // Allow CORS for your Angular app (adjust origin to your Angular dev URL) app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', 'http://localhost:4200'); res.header('Access-Control-Allow-Methods', 'GET, PUT'); res.header('Access-Control-Allow-Headers', 'Content-Type'); next(); }); // Parse JSON requests from Angular app.use(express.json()); // Endpoint to fetch mock data app.get('/api/reason-codes', (req, res) => { const filePath = path.join(__dirname, 'mock-db.json'); fs.readFile(filePath, 'utf8', (err, data) => { if (err) return res.status(500).json({ success: false, message: 'Failed to load data' }); res.json(JSON.parse(data)); }); }); // Endpoint to save updated data to JSON file app.put('/api/reason-codes', (req, res) => { const updatedData = req.body; const filePath = path.join(__dirname, 'mock-db.json'); fs.writeFile(filePath, JSON.stringify(updatedData, null, 2), (err) => { if (err) { console.error('Error writing file:', err); return res.status(500).json({ success: false, message: 'Failed to save data' }); } res.json({ success: true, message: 'Data saved successfully' }); }); }); app.listen(3000, () => console.log('Backend running on port 3000'));
Updated Angular Service
Modify your service to call this backend API:
// reason-code.service.ts import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class ReasonCodeService { private apiUrl = 'http://localhost:3000/api/reason-codes'; constructor(private http: HttpClient) {} getReasonCodes(): Observable<any[]> { return this.http.get<any[]>(this.apiUrl); } saveReasonCodes(updatedData: any[]): Observable<any> { return this.http.put(this.apiUrl, updatedData); } }
Component Usage
Add a save function to your component to trigger the API call:
// your-component.component.ts import { Component } from '@angular/core'; import { ReasonCodeService } from './reason-code.service'; import { Observable } from 'rxjs'; @Component({ selector: 'app-your-component', templateUrl: './your-component.component.html' }) export class YourComponent { displayReasonCodes$: Observable<any[]>; isEditable = false; localReasonCodes: any[] = []; // Keep a local copy to track edits constructor(private reasonCodeService: ReasonCodeService) { this.displayReasonCodes$ = this.reasonCodeService.getReasonCodes(); // Save local copy for edits this.displayReasonCodes$.subscribe(data => this.localReasonCodes = [...data]); } saveChanges(): void { this.reasonCodeService.saveReasonCodes(this.localReasonCodes).subscribe(response => { if (response.success) { alert('Changes saved successfully!'); } }); } }
2. Use Browser Local Storage (Local-Only Persistence)
If you don't need data to sync to a server (e.g., for a prototype or local tool), use localStorage to store data directly in the user's browser. This requires no backend setup.
Updated Angular Service
// reason-code.service.ts import { Injectable } from '@angular/core'; import { Observable, of } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class ReasonCodeService { private storageKey = 'reasonCodes'; // Initial mock data private defaultData = [ { id: 1, ReasonCode: 'Code 1' }, { id: 2, ReasonCode: 'Code 2' } ]; constructor() {} getReasonCodes(): Observable<any[]> { const storedData = localStorage.getItem(this.storageKey); return of(storedData ? JSON.parse(storedData) : this.defaultData); } saveReasonCodes(updatedData: any[]): Observable<any> { localStorage.setItem(this.storageKey, JSON.stringify(updatedData)); return of({ success: true }); } }
Fix Your Template Binding
Your template has a critical issue: ngModel="{{data.ReasonCode}}" uses one-way interpolation, which won't sync user input back to your data. Use two-way binding instead:
<ng-container *ngFor="let data of localReasonCodes; let i = index"> <tr class="row-break"> <td> <div class="form-group"> <input type="text" [readOnly]="!isEditable" [(ngModel)]="data.ReasonCode" name="textbox{{i}}" class="form-control" > </div> </td> </tr> </ng-container> <!-- Add a save button --> <button (click)="saveChanges()" [disabled]="!isEditable">Save Changes</button> <button (click)="isEditable = !isEditable">{{ isEditable ? 'Cancel' : 'Edit' }}</button>
3. Use IndexedDB (For Larger Local Datasets)
If your mock database has more data than localStorage can handle (it has a ~5MB limit), use IndexedDB. Libraries like dexie.js simplify working with IndexedDB in Angular:
- Install Dexie:
npm install dexie --save - Create a service:
// reason-code.service.ts import { Injectable } from '@angular/core'; import Dexie from 'dexie'; @Injectable({ providedIn: 'root' }) export class ReasonCodeService { private db: Dexie; constructor() { this.db = new Dexie('ReasonCodeDB'); this.db.version(1).stores({ reasonCodes: 'id, ReasonCode' // Define your data schema }); } getReasonCodes(): Promise<any[]> { return this.db.table('reasonCodes').toArray(); } saveReasonCodes(updatedData: any[]): Promise<void> { return this.db.transaction('rw', this.db.reasonCodes, async () => { await this.db.reasonCodes.clear(); await this.db.reasonCodes.bulkAdd(updatedData); }); } }
Key Takeaways
- Backend API is required for production: If you need data to persist across users or devices, you can't avoid a backend.
- Local storage is perfect for prototypes: It's simple and requires no extra setup, but data stays only on the user's browser.
- Fix template binding: Two-way
[(ngModel)]is essential to capture user input changes before saving.
内容的提问来源于stack exchange,提问作者user8819437

