반응형
Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- 네트워크
- VirtualBox
- 맥
- MachineLearning
- 센토스
- unittest
- IOS
- localserver
- PYTHON
- MAC
- 리눅스
- TensorFlow
- androidstudio
- centos
- linux
- Android
- vsCode
- picker
- Chrome
- avds
- qunit
- webpack
- node
- jest
- 개발
- react
- ReactNative
- xcode
- build
Archives
- Today
- Total
로메오의 블로그
[Angular] Rest API Service 구현하기 본문
반응형
모듈추가
src/app/app.module.ts
....
import { HttpClientModule } from '@angular/common/http';
@NgModule({
....
imports: [
....
HttpClientModule,
....
],
....
})
httpClientModule을 주입합니다.
전체소스
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';
import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [
BrowserModule,
FormsModule,
HttpClientModule,
ReactiveFormsModule,
IonicModule.forRoot(),
AppRoutingModule,
BrowserAnimationsModule,
DragDropModule,
ScrollingModule
],
providers: [
StatusBar,
SplashScreen,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
],
bootstrap: [AppComponent]
})
export class AppModule {}
product.ts 모델 추가
$ touch src/app/product.ts
export class Product {
_id: number;
prod_name: string;
prod_desc: string;
prod_price: number;
updated_at: Date;
}
service 추가
$ ionic g service api
api.service.ts
api.service.spec.ts
두 개의 파일이 생성되었습니다.
api.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class ApiService {
constructor() { }
}
아래와 같이 코드를 수정합니다.
import { Injectable } from '@angular/core';
import { Observable, of, throwError } from 'rxjs';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { catchError, tap, map } from 'rxjs/operators';
import { Product } from './product';
const httpOptions = {
headers: new HttpHeaders({'Content-Type': 'application/json'})
};
const apiUrl = 'http://localhost:3000/api/v1/products';
@Injectable({
providedIn: 'root'
})
export class ApiService {
constructor(private http: HttpClient) { }
private handleError<T>(operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
console.error(error); // log to console instead
return of(result as T);
};
}
getProducts(): Observable<Product[]> {
return this.http.get<Product[]>(apiUrl)
.pipe(
tap(product => console.log('fetched products')),
catchError(this.handleError('getProducts', []))
);
}
getProduct(id: any): Observable<Product> {
const url = `${apiUrl}/${id}`;
return this.http.get<Product>(url).pipe(
tap(_ => console.log(`fetched product id=${id}`)),
catchError(this.handleError<Product>(`getProduct id=${id}`))
);
}
addProduct(product: Product): Observable<Product> {
return this.http.post<Product>(apiUrl, product, httpOptions).pipe(
tap((prod: Product) => console.log(`added product w/ id=${prod._id}`)),
catchError(this.handleError<Product>('addProduct'))
);
}
updateProduct(id: any, product: any): Observable<any> {
const url = `${apiUrl}/${id}`;
return this.http.put(url, product, httpOptions).pipe(
tap(_ => console.log(`updated product id=${id}`)),
catchError(this.handleError<any>('updateProduct'))
);
}
deleteProduct(id: any): Observable<Product> {
const url = `${apiUrl}/${id}`;
return this.http.delete<Product>(url, httpOptions).pipe(
tap(_ => console.log(`deleted product id=${id}`)),
catchError(this.handleError<Product>('deleteProduct'))
);
}
}
데이터 LIST 구현
src/app/home/home.page.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
})
export class HomePage {
constructor() {}
}
아래와 같이 코드를 수정합니다.
import { Component, OnInit } from '@angular/core';
import { LoadingController } from '@ionic/angular';
import { ActivatedRoute, Router } from '@angular/router';
import { ApiService } from '../api.service';
import { Product } from '../product';
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
@Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
})
export class HomePage implements OnInit {
products: Product[] = [];
constructor(
public api: ApiService,
public loadingController: LoadingController,
public router: Router,
public route: ActivatedRoute
) {}
ngOnInit() {
this.getProducts();
}
async getProducts() {
const loading = await this.loadingController.create({
message: 'Loading...'
});
await loading.present();
await this.api.getProducts()
.subscribe(res => {
this.products = res;
console.log(this.products);
loading.dismiss();
}, err => {
console.log(err);
loading.dismiss();
});
}
drop(event: CdkDragDrop<string[]>) {
moveItemInArray(this.products, event.previousIndex, event.currentIndex);
}
}
src/app/home/home.module.ts
....
import { ScrollingModule } from '@angular/cdk/scrolling';
import { DragDropModule } from '@angular/cdk/drag-drop';
....
@NgModule({
imports: [
....
ScrollingModule,
DragDropModule,
....
],
....
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { RouterModule } from '@angular/router';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { HomePage } from './home.page';
@NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
ScrollingModule,
DragDropModule,
RouterModule.forChild([
{
path: '',
component: HomePage
}
])
],
declarations: [HomePage]
})
export class HomePageModule {}
src/app/home/home.module.html
home.module.html 파일을 열어서 아래 코드로 대체합니다.
<ion-header>
<ion-toolbar>
<ion-title>Home</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<cdk-virtual-scroll-viewport cdkDropList itemSize="20" class="example-viewport" (cdkDropListDropped)="drop($event)">
<ion-item *cdkVirtualFor="let p of products" class="example-item" href="/tabs/(details:{{p._id}})" cdkDrag>
<ion-icon name="desktop" slot="start"></ion-icon>
{{p.prod_name}}
<div class="item-note" slot="end">
{{p.prod_price | currency}}
</div>
</ion-item>
</cdk-virtual-scroll-viewport>
</ion-content>
src/app/home/home.page.sass
.example-viewport {
height: 100%;
width: 100%;
border: none;
}
.example-item {
min-height: 50px;
}
sass 파일도 위 코드로 대체합니다.
데이터 DETAIL 구현
src/app/product-detail/product-detail.page.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-product-detail',
templateUrl: './product-detail.page.html',
styleUrls: ['./product-detail.page.scss'],
})
export class ProductDetailPage implements OnInit {
constructor() { }
ngOnInit() {
}
}
아래와 같이 코드를 수정합니다.
import { Component, OnInit } from '@angular/core';
import { AlertController } from '@ionic/angular';
import { ApiService } from '../api.service';
import { ActivatedRoute, Router } from '@angular/router';
import { Product } from '../product';
@Component({
selector: 'app-product-detail',
templateUrl: './product-detail.page.html',
styleUrls: ['./product-detail.page.scss'],
})
export class ProductDetailPage implements OnInit {
product: Product = { _id: null, prod_name: '', prod_desc: '', prod_price: null, updated_at: null };
isLoadingResults = false;
constructor(
public api: ApiService,
public alertController: AlertController,
public route: ActivatedRoute,
public router: Router
) {}
ngOnInit() {
this.getProduct();
}
async getProduct() {
if (this.route.snapshot.paramMap.get('id') === 'null') {
this.presentAlertConfirm('You are not choosing an item from the list');
} else {
this.isLoadingResults = true;
await this.api.getProduct(this.route.snapshot.paramMap.get('id'))
.subscribe(res => {
console.log(res);
this.product = res;
this.isLoadingResults = false;
}, err => {
console.log(err);
this.isLoadingResults = false;
});
}
}
async presentAlertConfirm(msg: string) {
const alert = await this.alertController.create({
header: 'Warning!',
message: msg,
buttons: [
{
text: 'Okay',
handler: () => {
this.router.navigate(['']);
}
}
]
});
await alert.present();
}
editProduct(id: any) {
this.router.navigate([ '/product-edit', id ]);
}
}
src/app/product-detail/product-detail.page.html
<ion-header>
<ion-toolbar>
<ion-title>product-detail</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
</ion-content>
<ion-header>
<ion-toolbar color="primary">
<ion-buttons slot="start">
<ion-back-button defaultHref="/home"></ion-back-button>
</ion-buttons>
<ion-title>Product Details</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<div class="example-container mat-elevation-z8">
<div class="example-loading-shade"
*ngIf="isLoadingResults">
<mat-spinner *ngIf="isLoadingResults"></mat-spinner>
</div>
<mat-card class="example-card">
<mat-card-header>
<mat-card-title><h2>{{product.prod_name}}</h2></mat-card-title>
<mat-card-subtitle>{{product.prod_desc}}</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<dl>
<dt>Product Price:</dt>
<dd>{{product.prod_price}}</dd>
<dt>Updated At:</dt>
<dd>{{product.updated_at | date}}</dd>
</dl>
</mat-card-content>
<mat-card-actions>
<a mat-flat-button color="primary" (click)="editProduct(product._id)"><mat-icon>edit</mat-icon></a>
<a mat-flat-button color="warn" (click)="deleteProduct(product._id)"><mat-icon>delete</mat-icon></a>
</mat-card-actions>
</mat-card>
</div>
</ion-content>
src/app/product-detail/product-detail.page.scss
.example-container {
position: relative;
padding: 5px;
height: 100%;
background-color: aqua;
}
.example-loading-shade {
position: absolute;
top: 0;
left: 0;
bottom: 56px;
right: 0;
background: rgba(0, 0, 0, 0.15);
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
}
.mat-flat-button {
margin: 5px;
}
Add Data 구현
src/app/product-add/product-add.page.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-product-add',
templateUrl: './product-add.page.html',
styleUrls: ['./product-add.page.scss'],
})
export class ProductAddPage implements OnInit {
constructor() { }
ngOnInit() {
}
}
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { ApiService } from '../api.service';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { ErrorStateMatcher } from '@angular/material/core';
/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const isSubmitted = form && form.submitted;
return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
}
}
@Component({
selector: 'app-product-add',
templateUrl: './product-add.page.html',
styleUrls: ['./product-add.page.scss'],
})
export class ProductAddPage implements OnInit {
productForm: FormGroup;
prod_name = '';
prod_desc = '';
prod_price: number = null;
isLoadingResults = false;
matcher = new MyErrorStateMatcher();
constructor(
private router: Router,
private api: ApiService,
private formBuilder: FormBuilder
) { }
ngOnInit() {
this.productForm = this.formBuilder.group({
'prod_name': [null, Validators.required],
'prod_desc': [null, Validators.required],
'prod_price': [null, Validators.required]
})
}
onFormSubmit() {
this.isLoadingResults = true;
this.api.addProduct(this.productForm.value)
.subscribe((res: any) => {
const id = res._id;
this.isLoadingResults = false;
this.router.navigate(['/product-details', id]);
}, (err: any) => {
console.log(err);
this.isLoadingResults = false;
});
}
}
src/app/product-add/product-add.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { ProductAddPageRoutingModule } from './product-add-routing.module';
import { ProductAddPage } from './product-add.page';
@NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
ProductAddPageRoutingModule
],
declarations: [ProductAddPage]
})
export class ProductAddPageModule {}
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { Routes, RouterModule } from '@angular/router';
import { IonicModule } from '@ionic/angular';
import { ProductAddPageRoutingModule } from './product-add-routing.module';
import { ProductAddPage } from './product-add.page';
import {
MatInputModule,
MatPaginatorModule,
MatProgressSpinnerModule,
MatSortModule,
MatTableModule,
MatIconModule,
MatButtonModule,
MatCardModule,
MatFormFieldModule } from '@angular/material';
@NgModule({
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
IonicModule,
MatInputModule,
MatPaginatorModule,
MatProgressSpinnerModule,
MatSortModule,
MatTableModule,
MatIconModule,
MatButtonModule,
MatCardModule,
MatFormFieldModule
],
declarations: [ProductAddPage]
})
export class ProductAddPageModule {}
src/app/product-add/product-add.page.html
<ion-header>
<ion-toolbar>
<ion-title>product-add</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
</ion-content>
<ion-header>
<ion-toolbar color="primary">
<ion-buttons slot="start">
<ion-back-button defaultHref="/home"></ion-back-button>
</ion-buttons>
<ion-title>Product Add</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<div class="example-container mat-elevation-z8">
<div class="example-loading-shade"
*ngIf="isLoadingResults">
<mat-spinner *ngIf="isLoadingResults"></mat-spinner>
</div>
<mat-card class="example-card">
<form [formGroup]="productForm" (ngSubmit)="onFormSubmit()">
<mat-form-field class="example-full-width">
<input matInput placeholder="Product Name" formControlName="prod_name"
[errorStateMatcher]="matcher">
<mat-error>
<span *ngIf="!productForm.get('prod_name').valid && productForm.get('prod_name').touched">Please enter Product Name</span>
</mat-error>
</mat-form-field>
<mat-form-field class="example-full-width">
<input matInput placeholder="Product Desc" formControlName="prod_desc"
[errorStateMatcher]="matcher">
<mat-error>
<span *ngIf="!productForm.get('prod_desc').valid && productForm.get('prod_desc').touched">Please enter Product Description</span>
</mat-error>
</mat-form-field>
<mat-form-field class="example-full-width">
<input matInput placeholder="Product Price" formControlName="prod_price"
[errorStateMatcher]="matcher">
<mat-error>
<span *ngIf="!productForm.get('prod_price').valid && productForm.get('prod_price').touched">Please enter Product Price</span>
</mat-error>
</mat-form-field>
<div class="button-row">
<button type="submit" [disabled]="!productForm.valid" mat-flat-button color="primary"><mat-icon>save</mat-icon></button>
</div>
</form>
</mat-card>
</div>
</ion-content>
src/app/product-add/product-add.page.scss
.example-container {
position: relative;
padding: 5px;
height: 100%;
background-color: aqua;
}
.example-form {
min-width: 150px;
max-width: 500px;
width: 100%;
}
.example-full-width {
width: 100%;
}
.example-full-width:nth-last-child(0) {
margin-bottom: 10px;
}
.button-row {
margin: 10px 0;
}
.mat-flat-button {
margin: 5px;
}
.example-card {
margin: 5px;
}
Edit Data 구현
src/app/product-edit/product-edit.page.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-product-edit',
templateUrl: './product-edit.page.html',
styleUrls: ['./product-edit.page.scss'],
})
export class ProductEditPage implements OnInit {
constructor() { }
ngOnInit() {
}
}
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { ApiService } from '../api.service';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { ErrorStateMatcher } from '@angular/material/core';
/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const isSubmitted = form && form.submitted;
return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
}
}
@Component({
selector: 'app-product-edit',
templateUrl: './product-edit.page.html',
styleUrls: ['./product-edit.page.scss'],
})
export class ProductEditPage implements OnInit {
productForm: FormGroup;
_id = '';
prod_name = '';
prod_desc = '';
prod_price: number = null;
isLoadingResults = false;
matcher = new MyErrorStateMatcher();
constructor(
private router: Router,
private route: ActivatedRoute,
private api: ApiService,
private formBuilder: FormBuilder
) { }
ngOnInit() {
this.getProduct(this.route.snapshot.params['id']);
this.productForm = this.formBuilder.group({
'prod_name' : [null, Validators.required],
'prod_desc' : [null, Validators.required],
'prod_price' : [null, Validators.required]
});
}
getProduct(id: any) {
this.api.getProduct(id).subscribe((data: any) => {
this._id = data._id;
this.productForm.setValue({
prod_name: data.prod_name,
prod_desc: data.prod_desc,
prod_price: data.prod_price
});
});
}
onFormSubmit() {
this.isLoadingResults = true;
this.api.updateProduct(this._id, this.productForm.value)
.subscribe((res: any) => {
const id = res._id;
this.isLoadingResults = false;
this.router.navigate(['/product-details', id]);
}, (err: any) => {
console.log(err);
this.isLoadingResults = false;
}
);
}
productDetails() {
this.router.navigate(['/product-details', this._id]);
}
}
src/app/product-edit/product-edit.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { ProductEditPageRoutingModule } from './product-edit-routing.module';
import { ProductEditPage } from './product-edit.page';
@NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
ProductEditPageRoutingModule
],
declarations: [ProductEditPage]
})
export class ProductEditPageModule {}
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { ProductEditPageRoutingModule } from './product-edit-routing.module';
import { ProductEditPage } from './product-edit.page';
import {
MatInputModule,
MatPaginatorModule,
MatProgressSpinnerModule,
MatSortModule,
MatTableModule,
MatIconModule,
MatButtonModule,
MatCardModule,
MatFormFieldModule } from '@angular/material';
@NgModule({
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
IonicModule,
ProductEditPageRoutingModule,
MatInputModule,
MatPaginatorModule,
MatProgressSpinnerModule,
MatSortModule,
MatTableModule,
MatIconModule,
MatButtonModule,
MatCardModule,
MatFormFieldModule
],
declarations: [ProductEditPage]
})
export class ProductEditPageModule {}
src/app/product-edit/product-edit.module.html
<ion-header>
<ion-toolbar>
<ion-title>product-edit</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
</ion-content>
<ion-header>
<ion-toolbar color="primary">
<ion-buttons slot="start">
<ion-back-button defaultHref="/product-detail/{{_id}}"></ion-back-button>
</ion-buttons>
<ion-title>Product Edit</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<div class="example-container mat-elevation-z8">
<div class="example-loading-shade"
*ngIf="isLoadingResults">
<mat-spinner *ngIf="isLoadingResults"></mat-spinner>
</div>
<mat-card class="example-card">
<form [formGroup]="productForm" (ngSubmit)="onFormSubmit()">
<mat-form-field class="example-full-width">
<input matInput placeholder="Product Name" formControlName="prod_name"
[errorStateMatcher]="matcher">
<mat-error>
<span *ngIf="!productForm.get('prod_name').valid && productForm.get('prod_name').touched">Please enter Product Name</span>
</mat-error>
</mat-form-field>
<mat-form-field class="example-full-width">
<input matInput placeholder="Product Desc" formControlName="prod_desc"
[errorStateMatcher]="matcher">
<mat-error>
<span *ngIf="!productForm.get('prod_desc').valid && productForm.get('prod_desc').touched">Please enter Product Description</span>
</mat-error>
</mat-form-field>
<mat-form-field class="example-full-width">
<input matInput placeholder="Product Price" formControlName="prod_price"
[errorStateMatcher]="matcher">
<mat-error>
<span *ngIf="!productForm.get('prod_price').valid && productForm.get('prod_price').touched">Please enter Product Price</span>
</mat-error>
</mat-form-field>
<div class="button-row">
<button type="submit" [disabled]="!productForm.valid" mat-flat-button color="primary"><mat-icon>save</mat-icon></button>
</div>
</form>
</mat-card>
</div>
</ion-content>
src/app/product-edit/product-edit.module.scss
.example-container {
position: relative;
padding: 5px;
height: 100%;
background-color: aqua;
}
.example-form {
min-width: 150px;
max-width: 500px;
width: 100%;
}
.example-full-width {
width: 100%;
}
.example-full-width:nth-last-child(0) {
margin-bottom: 10px;
}
.button-row {
margin: 10px 0;
}
.mat-flat-button {
margin: 5px;
}
.example-card {
margin: 5px;
}
반응형
'Frontend > angular' 카테고리의 다른 글
[Angular] Login page 화면 제작 (0) | 2020.01.13 |
---|---|
[Angular] ionic angular router (0) | 2020.01.13 |
[Angular] angular-cli / material 설치 / page module 추가 (0) | 2020.01.10 |
[Ionic] iOS 생성 / 빌드 / 실행하기 (0) | 2020.01.10 |
[ionic] Android 생성 / 빌드 / 실행하기 (0) | 2020.01.10 |
Comments