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

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

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


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

앵귤러 튜토리얼(Angular tutorial) - 15 : Observable

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

저번시간에는 특정 데이터를 모듈에서 제공(provider)하여 컴포넌트에서 받는(inject) 내용에 대해서 살펴 보았습니다.
모듈에서의 데이터를 이렇게 전달하고 공유하는 방법은 사용하는 각각의 컴포넌트에게 일괄된 내용을 전달하기 위해서 자주 사용됩니다.
또한 사용하는 다른 모듈에게 데이터를 전달하는 방법으로도 사용됩니다.
오직 단순한 데이터를 전달하기 위해서만 모듈의 제공(provider) 기능이 있는 것은 아닙니다.
이를 위해 app컴포넌트 html 파일을 한번 살펴보겠습니다.

상위 컴포넌트에서 하위 컴포넌트로 이벤트를 요런식으로 전달 했었지요..

 

사진처럼 우리는 상위 컴포넌트에서 하위 컴포넌트에게 데이터를 전달하고 받기 위해서 여러 작업을 하였습니다.
상위 컴포넌트에서는 데이터를 전달하고, 하위컴포넌트에서는 Input, Output 데코레이터를 통해서 데이터를 받고 보내었습니다.
컴포넌트의 갯수가 많아지고 종류가 다양해 진 다면 작업해야 되는 대상도 엄청나게 늘어날 것 입니다.
서로 규칙도 정해야되고 변수명도 일일이 맞춰야되고..
그런데, 모듈과 다른 기능을 통해서 이러한 내용을 좀더 쉽게 손 볼수가 있습니다!


먼저 서비스를 만들어 보도록 하겠습니다.
아래 명령어를 입력하여 봅니다.

ng g service myService

뭔가 파일이 만들어졌습니다.

 

위 명령어는 서비스 라는 형태의 클래스를 만드는 기능입니다.
우리가 여태껏 사용한 명령어는 컴포넌트를 만들 때 사용하였습니다.
ng g component "이름"
이와 마찬가지로 앵귤러에서 서비스 클래스를 생성할 때는 ng g service "이름" 형식으로 입력을 하면 됩니다.

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class MyServiceService {

  constructor() { }
}

 

서비스의 형태는 매우 간단합니다.
Injectable이라는 데코레이터가 존재하며 일반 타입스크립트 형식의 클래스 모양만 존재 합니다.
Injectable 데코레이터에 providedIn이라는 속성이 보이는데, 일단 넘어가도록 합니다.
자, 그러면 app모듈에서 선언하였던 데이터를 서비스에 붙여 넣어 보겠습니다.

* 대상 : my-service.service.ts

import { Injectable } from '@angular/core';
import { INFORMATION } from './MyType';

@Injectable({
  providedIn: 'root'
})
export class MyServiceService {

  public readonly myData : INFORMATION = {  //내가 전달할 데이터
    data1 : 'data1',
    data2 : 1433,
    data3 : ['data3_1','data3_2']
  };

  constructor() { 

  }
}

 

여기까지는 어렵지가 않습니다.
그러면, 해당 서비스를 app모듈에서 provider를 통해 공급해 보겠습니다.
myData를 전달하는 방식과 동일하게 작성하면 됩니다.

* 대상 : app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';

import {FormsModule} from '@angular/forms';
import { DashboardComponent } from './dashboard/dashboard.component'; 
import { ReactiveFormsModule } from '@angular/forms'; 

import { MyServiceService } from './my-service.service';  //서비스 클래스 추가

@NgModule({
  declarations: [
    AppComponent,
    LoginComponent,
    DashboardComponent
  ],
  imports: [
    BrowserModule, FormsModule, ReactiveFormsModule 
  ],
  providers: [
    MyServiceService  //MyServiceService를 제공하도록 합니다.
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

 

마지막으로 login컴포넌트 파일을 수정하여 줍니다.

* login.component.ts

//..생략
import { MyServiceService } from '../my-service.service';
export class LoginComponent implements OnInit {
  //..생략
  constructor(service : MyServiceService) { 
    console.log(service)
  }
  //..생략
}

 

login컴포넌트에서 생성자를 통해 받는 부분이 매우 간결해 졌습니다.
자 이제 그러면 준비는 다 되었습니다.
이제 서버를 구동하여 줍니다.

콘솔에 데이터가 잘 나옵니다!

 

서비스 클래스가 myData를 가지고 나타났습니다!

우리가 단순하게 데이터만 전달(Injection) 하였던 것이 서비스라는 클래스(Class) 형태로 전달 할 수 있게 되었습니다.

서비스는 Class이므로 다양한 함수와 속성들을 가질 수 가 있습니다!

 

다음 아래내용은 난이도가 있는 내용입니다.

위 내용을 반드시 이해하고 진행하여 주세요. ^-^


이어서 서비스에 멋진 기능을 한번 추가해 보겠습니다.

* 대상 : my-service.service.ts

import { Injectable } from '@angular/core';
import { INFORMATION } from './MyType';
import { BehaviorSubject, Observable } from 'rxjs';
@Injectable({
  providedIn: 'root'
})
export class MyServiceService {

  public readonly myData : INFORMATION = {  //내가 전달할 데이터
    data1 : 'data1',
    data2 : 1433,
    data3 : ['data3_1','data3_2']
  };

  constructor() { 

  }

  private FACTORY : BehaviorSubject<any> = new BehaviorSubject({});  //??
  public readonly TV: Observable<any> = this.FACTORY.asObservable();  //??????

  public addData(arg : boolean, loginInfomation? : any) : void{
    if(arg){
      this.FACTORY.next(loginInfomation);  //????
    } 
  }

}

 

첨보는 녀석들이 나왔습니다.
Rxjs를 접해보신 분들은 바로 이해를 할 수 있지만 그렇지 않는 분들은 조금 어려운 개념입니다.
설명을 위해서 login컴포넌트와 app컴포넌트를 수정해 보겠습니다.

* 대상 : login.component.ts

import { Component, OnInit, Input, Output, EventEmitter, Inject } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Validators } from '@angular/forms';
import { MyServiceService } from '../my-service.service';

declare type MyCustomType = {
  text : any;
  number : any;
};

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {

  @Input() visible1 : boolean; 
  @Output() sendMyEvent : EventEmitter<any> = new EventEmitter(); 
  id = new FormControl('');  
  pwd = new FormControl('', [ Validators.required, Validators.minLength(4) ]); 

  private message;

  styleArray = {'wrong_id':false, 'wrong_pwd':false};

  constructor(private service : MyServiceService) { //private 제어자를 붙여서 클래스 내부에서 사용가능하게 합니다.
    console.log(service)
  }

  ngOnInit(): void {
    
  }

  tryToLogin() : void{
    console.log(this.pwd)
    this.service.addData(true, {id:'admin', name:'사용자'});  //서비스에 addData 함수를 호출합니다.

    if(this.id.value =='admin' && this.pwd.value == '1234'){
      alert('로그인합니다!');
      this.visible1 = true;
      this.sendMyEvent.emit(this.visible1); 
    } else if(this.id.value != 'admin'){
      this.setMessage = 'wrong id';
      this.styleArray.wrong_id = true;
      this.styleArray.wrong_pwd = false;
    } else if(this.pwd.value != '1234'){
      this.setMessage = 'wrong pwd';
      this.styleArray.wrong_id = false;
      this.styleArray.wrong_pwd = true;
    } 
  }

  set setMessage(arg){ 
    this.message = arg;
  } 

  get getMessage() : any{  
    return this.message;
  }

}


이어서 app컴포넌트도 수정합니다.

* 대상 : app.component.ts

import { Component } from '@angular/core';
import { MyServiceService } from './my-service.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  loginBool = true;
  boardBool = false;

  constructor(private service : MyServiceService){
    service.TV.subscribe( arg=>{
      console.log('서비스의 특정 데이터가 변동되어 동작했습니다 : ',arg);
    });
  }

  getEventThanks(event){
    console.log(event)
    if(event == true){
      this.loginBool = false;
      this.boardBool = true;
    }
  }
}

 

자 그러면 이제 실행하여봅니다.

자세히 봐보셔요!

 

이제 우리는 서비스를 눈여겨 보아야 합니다.
먼저 서비스에는 2가지 형태의 클래스가 존재합니다.
1개는 BehaviorSubject, 다른 1개는 Observable 입니다.

 

BehaviorSubject 클래스는 데이터의 변화에 대해서 마치 공장처럼 발행을 하는 클래스 입니다.
BehaviorSubject는 next라는 함수를 통해서 데이터를 전달합니다.
Observable 클래스는 그러한 BehaviorSubject클래스를 바라보는 구독자 입니다.
BehaviorSubject의 클래스가 next로 행위를 전달하면 구독하는 Observable클래스가 그 행동에 대한 행위를 실시 합니다.

1개의 클래스를 여러명이 공유하듯 관찰하는 개념 입니다!

 

음..채팅앱으로 비유하여보면,
BehaviorSubject 클래스는 채팅앱 입니다.
Observable 클래스는 채팅앱을 사용하는 사용자 입니다.
BehaviorSubject의 next 함수의 기능은 채팅앱에서 채팅 메시지를 전달하는 기능입니다.
그리고 Observable클래스의 subscribe 기능은 채팅앱에서 알림이 온 경우 입니다.

이제 login컴포넌트로 돌아가보겠습니다.
1. login컴포넌트에서는 서비스의 addData 함수를 호출하고 있습니다.
2. addData함수는 BehaviorSubject 클래스의 next 함수를 호출하는 기능입니다.
3. 다시말해 login컴포넌트에서는 채팅 메시지를 보내고 있습니다.

4. app컴포넌트를 살펴보겠습니다.
5. app컴포넌트에서는 생성자를 통해서 서비스를 받은 다음에 subscribe하고 있습니다. 
6. 채팅메시지가 날라오면 메시지가 도착하면 그다음 행동에 대해서 정의를 하고 있습니다.


위 내용을 토대로 로그인 성공유무에 따른 값 전달 방법을 이제 서비스를 통한 전달 방식으로 바꾸어 보겠습니다.

* 대상 : login.component.ts

import { Component, OnInit, Input, Output, EventEmitter, Inject } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Validators } from '@angular/forms';
import { MyServiceService } from '../my-service.service';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {

  //@Input() visible1 : boolean;   //이제 필요가 없습니다.
  //@Output() sendMyEvent : EventEmitter<any> = new EventEmitter(); 
  id = new FormControl('');  
  pwd = new FormControl('', [ Validators.required, Validators.minLength(4) ]); 

  private message;

  styleArray = {'wrong_id':false, 'wrong_pwd':false};

  constructor(private service : MyServiceService) { 
    
  }

  ngOnInit(): void {
    
  }

  tryToLogin() : void{
    console.log(this.pwd)
    

    if(this.id.value =='admin' && this.pwd.value == '1234'){
      alert('로그인합니다!');
      this.service.addData(true, {id:'admin', name:'사용자'});  //서비스에 addData 함수를 호출, 로그인 성공값을 전달 합니다.
    } else if(this.id.value != 'admin'){
      this.setMessage = 'wrong id';
      this.styleArray.wrong_id = true;
      this.styleArray.wrong_pwd = false;
    } else if(this.pwd.value != '1234'){
      this.setMessage = 'wrong pwd';
      this.styleArray.wrong_id = false;
      this.styleArray.wrong_pwd = true;
    } 
  }

  set setMessage(arg){ 
    this.message = arg;
  } 

  get getMessage() : any{  
    return this.message;
  }

}

 

다음으로 app컴포넌트html을 수정합니다.

* 대상 : app.component.html

<app-login *ngIf="loginBool"></app-login>
<app-dashboard *ngIf="boardBool"></app-dashboard> 

 

내용이 매우 간결해 졌습니다.
마지막으로 app컴포넌트를 수정합니다.

* 대상 : app.component.ts

import { Component } from '@angular/core';
import { MyServiceService } from './my-service.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  loginBool = true;
  boardBool = false;

  constructor(private service : MyServiceService){
    service.TV.subscribe( arg=>{
      if(arg && arg.id){
        console.log('로그인이 성공했군요 : ',arg);
        this.loginBool = false;
        this.boardBool = true;
      }
    });
  }

}

 

훌륭하게 변경하였습니다.
서비스를 통해 컴포넌트간의 데이터를 공유하고 관리를 할 수 있게 되었습니다!
앞으로 컴포넌트가 추가되어 로그인 정보를 공유하려 한 다면 위 예제처럼 서비스를 생성자로부터 공급받아 서비스의 Observable객체로 부터 subscribe 만 추가해 주면됩니다.
따로 html파일에 여러 속성 및 함수 대입, ts파일에 input, output 및 eventEmit 등을 추가 해줄 필요가 없게 되었습니다.

앵귤러에서의 서비스는 클래스에 Injectable라는 데코레이터를 붙여줌으로써 기능을 발휘할 수 있게 되어 있습니다.
잠깐 지나갔던 Injectable의 데코레이터 내부의 속성인 providedIn은 기본값은 root이며 모든 모듈에 적용가능한 기본값을 의미 합니다.
만약 특정 모듈에서만 사용하게 하려 한 다면 해당 값을 변경해 주면 됩니다. 
그러나 우리는 지금 1개의 모듈만 사용하므로 굳이 바꾸어줄 필요는 없겠습니다. 
나중에 여러개의 모듈을 사용하면서 해당 서비스가 다른 모듈에서 사용되지 못하게 하려 한다면 값을 변경 해 주면 됩니다.

여기까지 우리는 앵귤러에서의 "공유기법"에 대해 살펴보았습니다.
통상적으로 "의존성 주입" 이라는 내용으로 많이 불리우는 개념 입니다.

이처럼 컴포넌트에서 어떠한 데이터, 이벤트등을 주고받으려면 서비스를 많이 사용 합니다.
서비스는 모듈에서 제공(provider)되어야 하며 컴포넌트에서는 생성자(constructor)를 통해 공급 받습니다.
데이터 변경에 따른 이벤트를 손쉽게 구현 하려면 BehaviorSubject 클래스와, Observable 클래스를 사용합니다.
BehaviorSubject클래스는 이벤트를 만들어 내는, 채팅앱과 같은 기능을 담당하며, next 함수를 통해 이벤트를 발생 시킵니다.
Observable클래스는 이벤트를 구독하는 채팅앱을 사용하는 사용자이며, subscribe 함수를 통해 이벤트에 대한 행위를 정의 합니다.
* BehaviorSubject과 Observable에 대해서 개념을 잡으시려면 RxJs를 한번 살펴보시면 좋습니다.

다음시간에는 라우팅이라는 새로운 기능에 대해서 살펴보겠습니다.
라우팅 부터는 새로운 프로젝트를 만들어 진행하겠습니다.

src.zip
0.01MB

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

댓글