본문 바로가기
블로그 이미지

방문해 주셔서 감사합니다! 항상 행복하세요!

  
   - 문의사항은 메일 또는 댓글로 언제든 연락주세요.
   - "해줘","답 내놔" 같은 질문은 답변드리지 않습니다.
   - 메일주소 : lts06069@naver.com


앵귤러, 리엑트, 뷰/Angular Tutorial(old)

앵귤러 튜토리얼 (Angular tutorial) -22 with 파이어 베이스

야근없는 행복한 삶을 위해 ~
by 마샤와 곰 2019. 7. 18.

* 제가 다시 작성한 최신 튜토리얼 수정본 입니다. 아래 주소를 통해서 진행하시는 것을 권장 드립니다. ^^

lts0606.tistory.com/328

 

앵귤러 튜토리얼(Angular tutorial) - 1

안녕하세요. 앵귤러에 대해서 알아보기위해 이곳을 찾아주신 분 들께 감사의 말씀 드립니다.^^ 천천히, 초심자도 조금 더 쉽게 접근할 수 있도록 내용을 구성하여 보겠습니다. 어��

lts0606.tistory.com

 

저번시간을 통해서 파이어베이스 데이터베이스 사용법에 대해 살펴보았다.
이번시간에는 컴포넌트에 존재하는 파이어베이스와 관련된 공통기능을 서비스로 분리하고, 실제 웹에서 등록을 하는 폼에대해서 알아보도록 하겠다.
먼저 서비스를 만들어준다.  
src - app 폴더까지 이동한 후에 아래 명령어를 입력한다.
ng g service db-connector

서비스가 훌륭하게 만들어 졌다. 그럼 이제 파이어베이스와 관련된 내용 전부 서비스로 옮기도록 하자.
db-connector.service.ts를 수정하여보자.

import { Injectable, Host, Optional } from '@angular/core';

import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore';
import { BehaviorSubject } from 'rxjs';
import { AngularFireStorage} from '@angular/fire/storage';
import { map } from "rxjs/operators"; // map이다.

@Injectable({
  providedIn: 'root'
})
export class DbConnectorService {
  //데이터 베이스 관련 객체
  private DataBase : AngularFirestore;
  //컬렉션을 담아두는 배열
  private collections = new Array();
  //나중에 쓸 저장소 관련 객체
  private storage : AngularFireStorage;

  constructor(@Host() @Optional() db : AngularFirestore,@Host() @Optional()strg : AngularFireStorage) { //Host와 Optional 데코레이터
    this.DataBase = db;
    this.storage = strg;
  }

  //컬렉션이 있는지 조사해서 해당 내용을 리턴하는 함수
  private getCollections (db_name : string) : AngularFirestoreCollection<any>{
    var result = this.collections[db_name];  //배열에 값을 확인
    if(result == undefined || result == null){  //배열에 값이 비어있다면
      this.collections[db_name] = this.DataBase.collection<any>(db_name, (ref) =>ref);   //새로 만들어준다.
      result = this.collections[db_name];
    }
    return result;
  }

  getItem(db_name : string){
    var items : BehaviorSubject<any[]> = new BehaviorSubject([]);
    this.getCollections(db_name).valueChanges().forEach((val)=>{  //데이터를 가져오기
      items.next(val); 
    });    
    return items; 
  }

  setItem(db_name : string, param : any){  //데이터 등록
    this.getCollections(db_name).add(param);  
  }

  updatItem(db_name : string, param: any, key:any){   //key 값 추가
    var subscription = this.getCollections(db_name).stateChanges().pipe(map( changes => { //pip 함수는 map 함수를 붙여주는 역할을 한다.
      return changes.map(a=>{  //map 함수는 데이터의 내용을 바꾸는 역할을 한다.
        const data = a.payload.doc.data() as any;
        const ID = a.payload.doc.id; //고유 아이디 값
        if(data.idx == key){
          this.getCollections(db_name).doc(ID).update(param);  //id 값을 통해 수정한다.
        }
        return data;
      });
    })).subscribe((oo)=>{  //해당 수정행위를 구독한다.
      subscription.unsubscribe();  //getItem 함수를 통해 구독을 하고 있기 때문에 여기서 수정한 구독행위는 바로 종료시킨다.
                                   //만약 해당 구독행위를 종료하지 않으면 구독행위가 다중 발생하여 무한loop 수정 행위가 발생한다.
    });
  }

  deleteItem(db_name : string, key: any){
    console.log(db_name, key);
    var subscription = this.getCollections(db_name).stateChanges().pipe(map( changes => { //pip 함수는 map 함수를 붙여주는 역할을 한다.
      return changes.map(a=>{  //map 함수는 데이터의 내용을 바꾸는 역할을 한다.
        const data = a.payload.doc.data() as any;
        const ID = a.payload.doc.id; //고유 아이디 값
        if(data.idx == key){
          this.getCollections(db_name).doc(ID).delete();  //key를 통해 삭제한다.
          return data;
        }
        return data;
      });
    })).subscribe((oo)=>{  //해당 수정행위를 구독한다.
      subscription.unsubscribe();  //getItem 함수를 통해 구독을 하고 있기 때문에 여기서 수정한 구독행위는 바로 종료시킨다.
    });
  }

}

기존 컴포넌트에 존재하는 파이어베이스 관련된 내용 전부 옮겨보았다.
그렇다면 해당 서비스가 다른 컴포넌트에 자동으로 내용이 완성되도록 하려면 어떻게 해야 되는가? 
모듈에 서비스를 주입하여야 한다. app.module.ts파일을 수정하여 보자.

 

//..생략

import { DbConnectorService } from './db-connector.service';

const fireEnvironment = {
     //..생략
};

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
     //..생략
  ],
  providers: [ 
    //..생략
    DbConnectorService   //데이터베이스 접속관련 서비스
  ], 
  bootstrap: [AppComponent]
})
export class AppModule { }

서비스를 주입하여 해당공통내용을 이제 어느 컴포넌트에서던지 사용이 가능하게 변경이 되었다.
app.component.ts를 수정하여 보자.

import { Component } from '@angular/core';
import { DbConnectorService } from './db-connector.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  
  private list : any = new Array(); //리스트
  private name = 'board';

  constructor(private service : DbConnectorService) {   //서비스 주입
    service.getItem(this.name).subscribe((res : any)=>{  //리스트를 조회하는 구독행위
      console.log(res);
      this.list = res;
    });
  }  

  makeID(length) {
    var result = '';
    var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    var charactersLength = characters.length;
    for ( var i = 0; i < length; i++ ) {
       result += characters.charAt(Math.floor(Math.random() * charactersLength));
    }
    return result;
 }

}

깔끔하게 동작함을 볼 수 있다.
매번 데이터를 저장하거나 수정하는 것을 우리는 ts파일에서만 하였다. 이번에는 html 파일에 실제 input 테그를 활용해서 데이터를 CRUD해보도록 하자.
app.component.html 파일을 수정하여 보자.

<div>-s-</div>
<div>
  <input type="text" value="" id='aaa'/>
  <input type="text" value="" id='bbb'/>
  <input type="button" value="등록" (click)='insert()'/>
</div>

<table>
  <tr *ngFor='let items of list;let i = index' >
    <td *ngIf="items" >{{items.text}}</td>
    <td *ngIf="items" (click)='update(items.idx)'>
      <input type="button" value='수정' />
    </td>
    <td *ngIf="items" (click)='delete(items.idx)'>
      <input type="button" value='삭제' />
    </td>
  </tr>
</table>
<div>-e-</div>

뭔가 매번 하던데로 id를 부여하였다. 
음..이렇게 해놓고 보니까 id에 있는 value값을 어떻게 가져오는지 막막하다. 
예전기억을 더듬어 보면 FormsModule을 사용하였던 것 같다. 
app.module.ts로 다시돌아가 FormsModule을 추가하여 주자.

import { BrowserModule } from '@angular/platform-browser';  //새로추가!!

//..생략
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    FormsModule,  //새로추가!!!
    //..생략
  ],
  //..생략
})
export class AppModule { }

html에 저렴하게(?) 주었던 id 값을 제거하고 바인딩할 내용으로 수정하여 보자.

<div>-s-</div>
<div>
  <input type="text" [(ngModel)]="text" />  <!--바로 요 부분이다-->
  <input type="text" [(ngModel)]="desc" />   <!--바로 요 부분이다-->
  <input type="button" value="등록" (click)='insert()'/>
</div>

<table>
  <tr *ngFor='let items of list;let i = index' >
    <td *ngIf="items" >{{items.text}}</td>
    <td *ngIf="items" (click)='update(items.idx)'>
      <input type="button" value='수정' />
    </td>
    <td *ngIf="items" (click)='delete(items.idx)'>
      <input type="button" value='삭제' />
    </td>
  </tr>
</table>
<div>-e-</div>

text와 desc라는 객체로 바인딩을 하였다. 이에따른 컴포넌트에서 변수를 마찬가지로 추가하여 앞선시간에 만든 파이어베이스 데이터베이스로 저장되는 함수를 실행시키면 완성이다.
최종 저장기능이 탑재된 모습이다.

import { Component } from '@angular/core';
import { DbConnectorService } from './db-connector.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  
  private list : any = new Array(); //리스트
  private name = 'newBoard';

  private text : any;
  private desc : any;

  constructor(private service : DbConnectorService) {   //서비스 주입
    service.getItem(this.name).subscribe((res : any)=>{  //리스트를 조회하는 구독행위
      console.log(res);
      this.list = res;
    });
  }  

  makeID(length) {  
    var result = '';
    var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    var charactersLength = characters.length;
    for ( var i = 0; i < length; i++ ) {
       result += characters.charAt(Math.floor(Math.random() * charactersLength));
    }
    return result;
 }

 insert(){
   var param = {
    text : this.text,
    desc : this.desc,
    idx : this.makeID(30)
   }
   this.service.setItem(this.name,param);
 }

}

저장되는 곳을 newBoard로 바꾸었다. text와 desc라는 변수는 html파일에서의 바인딩 효과에 의해서 값이 변하면 자동으로 변동되는 것을 확인 할 수 있다.
이제 app.component.ts에서 수정기능과 삭제 기능도 추가하여보자.

//..생략
update(item){  //나중에 내용이 바뀐다~
  if(confirm('수정하시겠습니까?')){
    console.log(item);
  }
 }


 delete(idx){
  if(confirm('삭제하시겠습니까?')){
    this.service.deleteItem(this.name, idx);
  }
 }
//..생략

오..여기까지 하니까 그럴싸해진 웹 기능이 된 것 같다. 그런데 삭제까지는 뭐 상관없는데..수정은 뭘 어떻게 해야되는지 모르겠다.
text 타입의 input 테그로 바꾸도록하자. 그리고 change라는 이벤트를 추가하여주자.
app.component.html파일의 모습이다.

    <td *ngIf="items" >
      <input type="text"  value="{{items.text}}"  (change) = 'items.text = $event.target.value'/>    <!-- 발생한 이벤트를 items에 바로 적용시킨다. -->
      <input type="text"  value="{{items.desc}}"  (change) = 'items.desc = $event.target.value' /> <!-- 발생한 이벤트를 items에 바로 적용시킨다. -->
    </td>
    <td *ngIf="items" (click)='update(items)'> <!-- 수정 내용 자체를 수정함수에 바로 전달하여 준다 -->
      <input type="button" value='수정' />
    </td>

update 함수에 전달하는 내용은 수정할 그 내용 자체가 전달되게 변경하였다.
해당 내용까지 적용하고 수정버튼을 누르면 아래콘솔 내용이 표시가 된다.

수정할 내용까지 잘 전달 되었으니 update 함수를 고쳐보자.

 update(item){ 
  if(confirm('수정하시겠습니까?')){
    this.service.updatItem(this.name, {
      text : item.text,
      desc : item.desc
    }, item.idx);
  }
 }

요..드디어 수정까지 제대로 동작하는 앱이 되었다. 그래도..역시 화면에서 스타일은 좀 있어야 볼만 한 것 같다. 
예전에 해 보았던 bootstrap을 활용하여 스타일만 좀 꾸며보자.

app.component.html의 최종 모습이다.

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">

<div class="container">
  <br><br>
  <div>
    <input type="text" [(ngModel)]="text" class='form-control' style='width:30%; display: inline-block;' />
    <input type="text" [(ngModel)]="desc" class='form-control' style='width:30%; display: inline-block;' />
    <input type="button" value="등록" (click)='insert()' class='btn btn-primary' style='float: right;' />
  </div>
  <br>
  <table class='table table-striped'>
    <tr *ngFor='let items of list;let i = index'>
      <td *ngIf="items">
        <input type="text" value="{{items.text}}" (change)='items.text = $event.target.value' class='form-control'/>
      </td>
      <td *ngIf="items">
        <input type="text" value="{{items.desc}}" (change)='items.desc = $event.target.value' class='form-control' />
      </td>
      <td *ngIf="items" (click)='update(items)'>
        <input type="button" value='수정' class='btn btn-info' />
      </td>
      <td *ngIf="items" (click)='delete(items.idx)'>
        <input type="button" value='삭제' class='btn btn-warning'/>
      </td>
    </tr>
  </table>
</div>

해당 화면 모습이다.

드디어 어플리케이션 같이 만들어 진 것 같다.

실제 데이터베이스와 CRUD 기능도 되면서 화면도 나오는 앱이 탄생하였다.
이정도 수준으로 작업이 가능하다면 이제 어플리케이션 만드는 것은 어렵지 않는 듯 하다.
다음시간에는 앵귤러에서의 라우팅 기능에 대해 알아보도록 하자.

src.zip
0.01MB

반응형
* 위 에니메이션은 Html의 캔버스(canvas)기반으로 동작하는 기능 입니다. Html 캔버스 튜토리얼 도 한번 살펴보세요~ :)
* 직접 만든 Html 캔버스 애니메이션 도 한번 살펴보세요~ :)

댓글