数週間のグーグルと1つのStackoverflown質問だけで、Angular CRUD AppをMaterial Table Component 。バックエンド(JSON)からのデータを表示し、CRUD操作では、図に示すようなダイアログを使用しています(これは編集です。クロアチア語で申し訳ありません)。ダイアログは最適な方法ではないかもしれませんが、インライン編集の方が良いかもしれません。それでも、新しいアイテムを追加するには、ダイアログなどが必要です。
最後に私が悩んでいるのは、それに応じてテーブルのフィールドを更新する方法です。そのため、ダイアログで「保存」を押すと、データはバックエンド(MySQLテーブル内)で更新されますが、フロントでは更新されません。当分の間、私はこのためにい回避策を持っています。更新を行うたびに、テーブル全体も更新されます。
とにかくここにコードがあります:
テーブルコンポーネント:
export class BazaComponent implements OnInit {
....
constructor(public httpClient: HttpClient, public dialog: MatDialog) {
}
ngOnInit() {
this.loadData();
}
// TODO: Simplfy this...
addNew(ident: number, naziv: string, mt: number, kutija: number,
komada: number, jm: string, orginal: number, lokacija: number, napomena: string) {
console.log('add new clicked');
const dialogRef = this.dialog.open(AddDialogComponent, {
data: {ident: ident, naziv: naziv, mt: mt, kutija: kutija,
komada: komada, jm: jm, orginal: orginal, lokacija: lokacija, napomena: napomena }
});
dialogRef.afterClosed().subscribe(result => {
console.log(result);
if (result === 1) {
this.loadData(); // --> This is a temp workaround, every time when I do CRUD operation just redraw whole thing again
}
});
}
startEdit(id: number, ident: number, naziv: string, mt: number, kutija: number,
komada: number, jm: string, orginal: number, lokacija: number, napomena: string) {
const dialogRef = this.dialog.open(EditDialogComponent, {
data: {id: id, ident: ident, naziv: naziv, mt: mt, kutija: kutija,
komada: komada, jm: jm, orginal: orginal, lokacija: lokacija, napomena: napomena}
});
dialogRef.afterClosed().subscribe(result => {
if (result === 1) {
this.loadData(); // --> This is a temp workaround, every time when I do CRUD operation just redraw whole thing again
}
});
}
deleteItem(id: number, ident: number, naziv: string, mt: number) {
const dialogRef = this.dialog.open(DeleteDialogComponent, {
data: {id: id, ident: ident, naziv: naziv, mt: mt}
});
dialogRef.afterClosed().subscribe(result => {
if (result === 1) {
this.loadData();
}
});
}
public loadData() {
this.exampleDatabase = new DataService(this.httpClient);
this.dataSource = new ExampleDataSource(this.exampleDatabase, this.paginator, this.sort);
Observable.fromEvent(this.filter.nativeElement, 'keyup')
.debounceTime(150)
.distinctUntilChanged()
.subscribe(() => {
if (!this.dataSource) {
return;
}
this.dataSource.filter = this.filter.nativeElement.value;
});
}
}
export class ExampleDataSource extends DataSource<Baza> {
_filterChange = new BehaviorSubject('');
get filter(): string {
return this._filterChange.value;
}
set filter(filter: string) {
this._filterChange.next(filter);
}
filteredData: Baza[] = [];
renderedData: Baza[] = [];
constructor(private _exampleDatabase: DataService,
private _paginator: MatPaginator,
private _sort: MatSort) {
super();
// Reset to the first page when the user changes the filter.
this._filterChange.subscribe(() => this._paginator.pageIndex = 0);
}
/** Connect function called by the table to retrieve one stream containing the data to render. */
connect(): Observable<Baza[]> {
// Listen for any changes in the base data, sorting, filtering, or pagination
const displayDataChanges = [
this._exampleDatabase.dataChange,
this._sort.sortChange,
this._filterChange,
this._paginator.page,
];
this._exampleDatabase.getAllItems();
return Observable.merge(...displayDataChanges).map(() => {
// Filter data
this.filteredData = this._exampleDatabase.data.slice().filter((item: Baza) => {
const searchStr = (item.ident + item.naziv + item.mt + item.lokacija + item.napomena).toLowerCase();
return searchStr.indexOf(this.filter.toLowerCase()) !== -1;
});
// Sort filtered data
const sortedData = this.sortData(this.filteredData.slice());
// Grab the page's slice of the filtered sorted data.
const startIndex = this._paginator.pageIndex * this._paginator.pageSize;
this.renderedData = sortedData.splice(startIndex, this._paginator.pageSize);
return this.renderedData;
});
}
disconnect() {
}
/** Returns a sorted copy of the database data. */
sortData(data: Baza[]): Baza[] {
... sort stuff
}
ここで、フィールドの更新を行う必要があると思われるDataServiceを示します。
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse, HttpHeaders} from '@angular/common/http';
import { Baza } from '../models/kanban.baza';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
@Injectable()
export class DataService {
private readonly API_URL = 'http://localhost/api/'
/** Stream that emits whenever the data has been modified. */
dataChange: BehaviorSubject<Baza[]> = new BehaviorSubject<Baza[]>([]);
constructor(private httpClient: HttpClient) {
}
get data(): Baza[] {
return this.dataChange.value;
}
getAllItems(): void {
this.httpClient.get<Baza[]>(this.API_URL).subscribe(data => {
this.dataChange.next(data['items']);
});
}
addItem(baza: Baza): void {
this.httpClient.post(this.API_URL, Baza).subscribe(data => {
//THIS WAS MY BEST TRY BUT IT DOESN'T WORK :(
const copiedData = this.data.slice();
copiedData.Push(baza);
console.log(copiedData);
this.dataChange.next(copiedData);
});
}
updateItem(baza: Baza): void {
this.httpClient.put(this.API_URL + baza.id, baza).subscribe();
}
deleteItem(id: number): void {
this.httpClient.delete(this.API_URL + id, {headers: new HttpHeaders().set('Access-Control-Allow-Origin', '*')} ).subscribe();
}
}
UPDATE 27.11.2017:
さて、私はついに新しい行の追加をトリガーする方法を見つけました。テーブルコンポーネント内でdataChange.value
を呼び出す必要がありました。データをロードすると、すぐに新しい行が表示されます。
const data = {id: 208, ident: 233, naziv: 'test', mt: 291, komada: 2, jm: 'a', orginal: 100, lokacija: 3, napomena: 'pls work'};
this.exampleDatabase.dataChange.value.Push(data);
DataServiceでも同じことが機能しません。
this.dataChange.value.Push(data);
プランカーはこちら:
https://plnkr.co/edit/IWCVsBRl54F7ylGNIJJ3?p=info
EDIT 28.11.2017:
今残っているのは、追加、編集、削除のロジックを構築することだけです。簡単に追加できるのは、単に「value.Push(data)」です。みんな助けてくれてありがとう。
時間がかかりましたが、すべてがうまくいきました。あなたの答えとさまざまなアプローチも役立ちました。だから、誰かがこれで困った場合のCRUD実装は次のとおりです:
https://github.com/marinantonio/angular-mat-table-crud
スクリーンショット:
または、プロジェクトのデモを確認できます: https://marinantonio.github.io/angular-mat-table-crud/
重要な部分はtable.tsファイルにあります。
....
addNew(issue: Issue) {
const dialogRef = this.dialog.open(AddDialogComponent, {
data: {issue: issue }
});
dialogRef.afterClosed().subscribe(result => {
if (result === 1) {
this.exampleDatabase.dataChange.value.Push(this.dataService.getDialogData());
this.refreshTable();
}
});
}
startEdit(i: number, id: number, title: string, state: string, url: string, created_at: string, updated_at: string) {
this.index = i;
this.id2 = id;
console.log(this.index);
const dialogRef = this.dialog.open(EditDialogComponent, {
data: {id: id, title: title, state: state, url: url, created_at: created_at, updated_at: updated_at}
});
dialogRef.afterClosed().subscribe(result => {
if (result === 1) {
// Part where we do frontend update, first you need to find record using id
const foundIndex = this.exampleDatabase.dataChange.value.findIndex(x => x.id === this.id2);
// Then you update that record using dialogData
this.exampleDatabase.dataChange.value[foundIndex] = this.dataService.getDialogData();
// And lastly refresh table
this.refreshTable();
}
});
}
deleteItem(i: number, id: number, title: string, state: string, url: string) {
this.index = i;
this.id2 = id;
const dialogRef = this.dialog.open(DeleteDialogComponent, {
data: {id: id, title: title, state: state, url: url}
});
dialogRef.afterClosed().subscribe(result => {
if (result === 1) {
const foundIndex = this.exampleDatabase.dataChange.value.findIndex(x => x.id === this.id2);
this.exampleDatabase.dataChange.value.splice(foundIndex, 1);
this.refreshTable();
}
});
}
private refreshTable() {
// If there's no data in filter we do update using pagination, next page or previous page
if (this.dataSource._filterChange.getValue() === '') {
if (this.dataSource._paginator.pageIndex === 0) {
this.dataSource._paginator.nextPage();
this.dataSource._paginator.previousPage();
} else {
this.dataSource._paginator.previousPage();
this.dataSource._paginator.nextPage();
}
// If there's something in filter, we reset it to 0 and then put back old value
} else {
this.dataSource.filter = '';
this.dataSource.filter = this.filter.nativeElement.value;
}
}
....
コードから、ページネーションを使用していることがわかりますが、crud操作の後に次の操作を実行できます。
this.dataSource.paginator = this.paginator;
これにより、現在のページが更新されます。そして、クロアチアの誰かがangular素材を使用していることを嬉しく思います。
コードの重要な部分は次のとおりです。
dialogRef.afterClosed().subscribe(result => {
if (result === null) { return; }
switch (mode) { // add new
case 'C': {
data.Push(result.vendor);
this.refreshTable();
break;
}
case 'U': { // update
const index = data.findIndex((item) => item.buFmisVendorId === result.vendor.buFmisVendorId);
if (index > -1) {
data[index] = vendor;
this.refreshTable();
}
break;
}
}
});
private refreshTable() {
this.dataSource.paginator = this.paginator;
}
モーダルウィンドウを使用せずにテーブル内のデータを編集するには、回避策があります。
あなたは私のCRUD実装を見ることができますAngular 6およびMaterial
データサービス
import {Injectable} from '@angular/core';
import {HttpClient, HttpParams, HttpHeaders} from '@angular/common/http';
import {User} from './user';
@Injectable()
export class UserService{
private url = "http://localhost:51120";
constructor(private http: HttpClient){ }
getUsers(){
let getUrl = this.url + "/api/all/";
return this.http.get(getUrl);
}
createUser(user: User){
let saveUrl = this.url + "/api/Users";
return this.http.post(saveUrl, user);
}
updateUser(id: number, user: User) {
const urlParams = new HttpParams().set("id", id.toString());
return this.http.post(this.url + "/api/update", user);
}
deleteUser(id: number){
const urlParams = new HttpParams().set("id", id.toString());
return this.http.delete(this.url + "/api/delete/" + id);
}
}
成分
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [UserService]
})
export class AppComponent implements OnInit {
@ViewChild(MatPaginator) paginator: MatPaginator;
addNewUser: User[] = [
{ Id: 0, Name: null, Age: null, Email: null, Surname: null }
];
users: Array<User>;
showTable: boolean;
statusMessage: string;
isLoaded: boolean = true;
displayedColumnsUsers: string[] = ['Id', 'Name', 'Surname', 'Age', 'Email', 'Change', 'Delete'];
displayedColumnsAddUser: string[] = ['Name', 'Surname', 'Age', 'Email', 'Save', 'Cancel'];
dataSourceUsers: any;
dataSourceAddUser: any;
newUser : User;
constructor(private serv: UserService, public dialog: MatDialog, public snackBar: MatSnackBar) {
this.users = new Array<User>();
}
@ViewChild(MatSort) sort: MatSort;
ngOnInit() {
this.loadUsers();
this.dataSourceAddUser = new MatTableDataSource();
}
applyFilter(filterValue: string) {
this.dataSourceUsers.filter = filterValue.trim().toLowerCase();
if (this.dataSourceUsers.paginator) {
this.dataSourceUsers.paginator.firstPage();
}
}
private loadUsers() {
this.isLoaded = true;
this.serv.getUsers().subscribe((data: User[]) => {
this.users = data;
this.users.sort(function (obj1, obj2) {
// Descending: first id less than the previous
return obj2.Id - obj1.Id;
});
this.isLoaded = false;
this.dataSourceUsers = new MatTableDataSource(this.users);
this.dataSourceAddUser = new MatTableDataSource(this.addNewUser);
this.dataSourceUsers.sort = this.sort;
this.dataSourceUsers.paginator = this.paginator;
},
error => {
alert("Error: " + error.name);
this.isLoaded = false;
}
);
}
deleteUserForDialog(user: User) {
this.serv.deleteUser(user.Id).subscribe(data => {
this.statusMessage = 'User ' + user.Name + ' is deleted',
this.openSnackBar(this.statusMessage, "Success");
this.loadUsers();
})
}
editUser(user: User) {
this.serv.updateUser(user.Id, user).subscribe(data => {
this.statusMessage = 'User ' + user.Name + ' is updated',
this.openSnackBar(this.statusMessage, "Success");
this.loadUsers();
},
error => {
this.openSnackBar(error.statusText, "Error");
}
);
}
saveUser(user: User) {
if (user.Age != null && user.Name != null && user.Name != "" && user.Age != 0) {
this.serv.createUser(user).subscribe(data => {
this.statusMessage = 'User ' + user.Name + ' is added',
this.showTable = false;
this.openSnackBar(this.statusMessage, "Success");
this.loadUsers();
},
error => {
this.showTable = false;
this.openSnackBar(error.statusText, "Error");
}
);
}
else {
this.openSnackBar("Please enter correct data", "Error")
}
}
show() {
this.showTable = true;
this.addNewUser = [{ Id: 0, Name: null, Age: null, Email: null, Surname: null }];
}
cancel() {
this.showTable = false;
}
//snackBar
openSnackBar(message: string, action: string) {
this.snackBar.open(message, action, {
duration: 3000,
});
}
//material dialog
openDialog(element): void {
const dialogRef = this.dialog.open(DialogOverviewExampleDialogComponent,
{
width: '250px',
data: element,
});
dialogRef.afterClosed().subscribe(result => {
console.log('The dialog was closed');
if (result == "Confirm") {
this.deleteUserForDialog(element);
}
});
}
// Form field with error messages
name = new FormControl('', [Validators.required]);
getErrorMessage() {
return this.name.hasError('required') ? 'You must enter a value' :
this.name.hasError('name') ? 'Not a valid name' : '';
}
age = new FormControl('', [Validators.required]);
email = new FormControl('', [Validators.required, Validators.email]);
surnameFormControl= new FormControl('', [Validators.required]);
emailGetErrorMessage() {
return this.email.hasError('required') ? 'You must enter a value' :
this.email.hasError('email') ? 'Not a valid email' :
'';
}
onSubmit(newUser:User){
this.newUser = new User(0,"",0,"","");
}
}
https://github.com/AleksandrChuikov/Angular6MaterialCRUD
デモへのリンクは次のとおりです。 https://crud-angular6.azurewebsites.net
私の答えはAngular 6 Material 2です。
編集した行のインデックスを引数として使用するsplice
関数を使用し、次に削除する行数(あなたの場合は1)と、そのインデックスに挿入される編集された行の新しいバージョンを使用します。
dialogRef.afterClosed().subscribe(result => {
if(result !== '' && result !== null) {
const idx_editedRow = this.mattabledatasource.data.indexOf(row);
this.mattabledatasource.data.splice(idx_editedRow, 1, result);
loadData();
}
});
実際に、次のHTMLがある場合、編集後にテーブルを更新する必要はありません。
<mat-table [dataSource]="dataSource" matSort>
<ng-container matColumnDef="userName">
<mat-header-cell mat-sort-header> UserName </mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.userName}} </mat-cell>
</ng-container>
<ng-container matColumnDef="actions">
<mat-cell *matCellDef="let user">
<button mat-icon-button matTooltip="Edit" (click)="editUser(user)">
<mat-icon>edit</mat-icon>
</button>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;">
</mat-row>
</mat-table>
そして、.tsには次のものがあります。
private editUser(user?: User) {
let userTest: User = user;
userTest.userName = "user123";
}
ユーザー名の変更をプッシュ編集すると、この行が自動的に表示されます(この場合は「user123」になります)
アイテムを削除してデータテーブルを更新する少し異なるアプローチ。再びAPIを呼び出しますが、これは小さなデータセットで機能する場合があります。
public deleteMember(memberId) {
// Call the confirm dialog component
this.confirmService.confirm('Confirm Delete', 'This action is final. Gone forever!')
.switchMap(res => {if (res === true) {
return this.appService.deleteItem(this.dbTable, memberId);
}})
.subscribe(
result => {
this.success();
// Refresh DataTable to remove row. This solution calls the db and is a hack.
this.ngAfterViewInit();
},
(err: HttpErrorResponse) => {
console.log(err.error);
console.log(err.message);
this.messagesService.openDialog('Error', 'Delete did not happen.');
}
);
}
これはもちろんコンポーネントの上部近くで呼び出されますが、参照用にここに含まれています。
private dbTable = 'members';
dataSource = new MatTableDataSource();
ngAfterViewInit() {
this.appService = new AppService(this.http);
this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;
// Populate the Material2 DataTable.
Observable.merge(this.paginator.page)
.startWith(null) // Delete this and no data is downloaded.
.switchMap(() => {
return this.appService.getItems( this.dbTable,
this.paginator.pageIndex);
})
.map(data => {
return data.resource;
})
.subscribe(data => {
this.dataLength = data.length;
this.dataSource.data = data;
});
}
このソリューションでは、既存の削除コードを使用しますが、更新コードでも同じです。重要な問題は、編集または削除されたアイテムの配列インデックスを見つけることです。結果が成功したら、成功モーダルを呼び出してユーザーに通知し、関数を呼び出してデータテーブルから行を削除します。または、オブジェクトの配列にデータをプッシュするなど、少し異なるコードでその行のデータを更新できます。この方法では、すべてのデータを再度ダウンロードする必要はありません。
public deleteMember(memberId) {
// Call the confirm dialog component
this.confirmService.confirm('Confirm Delete', 'This action is final. Gone forever!')
.switchMap(res => {if (res === true) {
return this.appService.deleteItem(this.dbTable, memberId);
}})
.subscribe(
result => {
this.success();
// Refresh DataTable to remove row.
this.updateDataTable (memberId);
},
(err: HttpErrorResponse) => {
console.log(err.error);
console.log(err.message);
this.messagesService.openDialog('Error', 'Delete did not happen.');
}
);
}
次に、削除または編集した行を削除または更新します。
private dsData: any;
// Remove the deleted row from the data table. Need to remove from the downloaded data first.
private updateDataTable (itemId) {
this.dsData = this.dataSource.data;
if (this.dsData.length > 0) {
for (let i = 0; i < this.dsData.length; i++ ) {
if (this.dsData[i].member_id === itemId) {
this.dataSource.data.splice(i, 1);
}
}
}
this.dataSource.paginator = this.paginator;
}
ご覧になれますか
addItem(baza: Baza): void {
this.httpClient.post(this.API_URL, Baza).subscribe(data => {
//THIS WAS MY BEST TRY BUT IT DOESN'T WORK :(
const copiedData = this.data.slice();
copiedData.Push(baza);
console.log(copiedData);
this.dataChange.next(copiedData);
});
}
POSTリクエストは機能し、データを送信していますか? POSTリクエストでBazaを参照します。これは 'baza'(小文字のB)でなければなりません。たぶん、これが原因でリクエストが失敗し、観察可能なサブスクリプションが満たされることはありません...サブスクリプションのエラーハンドラーでその理論を再確認できます。
addItem(baza: Baza): void {
this.httpClient.post(this.API_URL, baza).subscribe(data => {
const copiedData = this.data.slice();
copiedData.Push(baza);
console.log(copiedData);
this.dataChange.next(copiedData);
}, (errror) => {
console.log(error);
});
}
最後に、編集に関しては、私のアプローチは少し異なります。 DataServiceの同じインスタンスをコンポーネントに挿入し、この同じ参照を新しいインスタンスではなくテーブルDataSourceに渡します。次に、プロパティだけでなく、bazaオブジェクト全体を編集ダイアログに渡します。次に、ダイアログを閉じるときに、元の(編集されていないオブジェクト)と新しいプロパティ(または、さらに良いことに、編集されたフィールドを持つBazaクラスの新しいオブジェクト)を渡します。 「編集/更新」メソッドでこれらをデータサービスに送信します。 edit/updateメソッドは、既存のデータ配列セットをフィルター処理して、未編集のオブジェクトに一致するエントリを探し、それらを新しいオブジェクトに等しくなるように設定します。以下に示すわずかに抽象化された例
//例成分
export class BazaComponent implements OnInit {
....
constructor(
public httpClient: HttpClient,
public dialog: MatDialog,
public dataService: DataService
){}
....
public loadData() {
this.dataSource = new ExampleDataSource(this.dataService, this.paginator, this.sort);
Observable.fromEvent(this.filter.nativeElement, 'keyup')
.debounceTime(150)
.distinctUntilChanged()
.subscribe(() => {
if (!this.dataSource) {
return;
}
this.dataSource.filter = this.filter.nativeElement.value;
});
}
....
startEdit(baza: Baza) {
const dialogRef = this.dialog.open(EditDialogComponent, {
data: {
baza: baza
}
});
dialogRef.afterClosed().subscribe(result => {
// result will be simple array of our 'old' baza object that we passed in, and the 'new' baza object that contains the edits
this.dataService.updateItem(result[0], result[1]);
});
}
dialogRef.close(['close',editBaza,baza]);
//例サービス
export class DataService {
....
set data(data: Baza[]) {
this.dataChange.next(data);
}
....
updateItem(oldBaza: Baza, newBaza: Baza){
this.data = this.data.map((baza: Baza) => {
if(baza === oldBaza) return newBaza;
return baza;
});
}
Jobposting.component.tsファイルの構造:
export class JobPostingComponent implements OnInit {
values: JobPosting[];
columns: string[] = ['title', 'vacancies','division.name'];
displayedColumns: string[] = ['actions'].concat(this.columns);
dataSource: MatTableDataSource<JobPosting>;
更新する行にfindIndexを使用し、行の更新された値をvalues配列のそのインデックスに挿入しました。
onEdit(data: JobPosting) {
const dialogRef = this.dialog.open(AddJobPostingComponent, {
data,
width: '1000px'
});
dialogRef.afterClosed().subscribe(res => {
if (res !== undefined) {
const id = res.id;
const index = this.values.findIndex(x => x.id === id);
this.values[index] = res;
this.dataSource.data = this.values;
}
});
}