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

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

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


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

앵귤러 튜토리얼(Angular tutorial) - 18 : Observable 심화

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

저번시간에는 가드(guard)를 통하여 사용자의 요청에 따라 컴포넌트가 동작하기 전 행동을 하는, 인터셉터와 같은 역할의 기능에 대해서 살펴 보았습니다.
이번시간에는 가드(guard)를 완성하기 위해 자주 보였던 Observable이라는 내용을 살펴보려 합니다.

우리는 ask서비스에서 2가지 형태의 구독이 가능한 객체를 만들었습니다.
Observable BehaviorSubject라는 클래스 입니다.
두 클래스는 rxjs에서 제공되는 Reactive Extension으로 비동기 데이터 호출, 콜백 및 이벤트 기반 프로그램을 처리하는 반응 형 프로그래밍 작업을 지원합니다.
음....말이 조금 어렵네요..
ask서비스의 내용을 통해 확인하여 보겠습니다.

* 대상 : ask.service.ts

  //생략..
  tryToLogin(param : any){
    return new Observable( arg=>{
      if(param.id == this.storage.id && param.passwd == this.storage.passwd){
        arg.next({status: true});
        localStorage.setItem('status',"true");
      } else {
        arg.next({status: false, reason : 'wrong information'});
      }
      arg.complete();
    });
  }

 

new Observable은 행동을 만드는 것 입니다. 
내부의 arg는 구독자에게 전달할 행동을 정의합니다.
여기서는 next를 호출하면서 구독자에게 json형식의 파라미터를 전달 하도록 만들었습니다.
complete를 호출하면 구독자에게 행위가 끝났음을 알립니다.
이렇게 만들어진 Observable 객체는 subscribe라는 함수를 통해서 구독할 수 있습니다.

* 대상 : login.component.ts

  //생략..
  login() {
    this.service.tryToLogin({id : this.id, passwd : this.passwd}).subscribe( (arg:any)=>{
      console.log(arg);
      if(arg.status == true){
        alert('로그인 성공!')
        this.rout.navigate(['/board']);
      }
    });
  }

 

login컴포넌트 입니다. subscribe 함수를 호출하여 ask서비스의 tryToLogin함수가 반환한 값인 Observable객체를 구독하고 있습니다.
구독을 통해서 Observable객체 내부에 정의된 행동에 대해서 내용을 받을수가 있습니다.
이해를 위해서 ask서비스에 예제를 한번 만들어 보겠습니다.

* 대상 : ask.service.ts

import { Injectable } from '@angular/core';
import { Observable, BehaviorSubject } from 'rxjs';

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

  //저장소(나중에 데이터베이스 서버)
  private readonly storage = {
    id : 'admin',
    passwd : '1234'
  }

  constructor() {   //생성자에게 2개의 구독을 지시합니다!!!
    this.test1().subscribe((arg:any)=>{
      console.log(arg)
    });
    this.test1().subscribe((arg:any)=>{
      console.log(arg)
    });    
  }

  test1(){  //요렇게 추가합니다!!!!!!!!!!!!
    return new Observable( arg=>{
      arg.next({test:1});
      arg.complete();
    });
  }

  //로그인을 시도하는 함수
  tryToLogin(param : any){
    return new Observable( arg=>{
      if(param.id == this.storage.id && param.passwd == this.storage.passwd){
        arg.next({status: true});
        localStorage.setItem('status',"true");
      } else {
        arg.next({status: false, reason : 'wrong information'});
      }
      arg.complete();
    });
  }

  //가드가 구독하는 대상
  readonly isLogged: BehaviorSubject<boolean> = new BehaviorSubject(false);

  //로그인이 되었는지 확인하는 함수
  isLogIn() : void{
    if(localStorage.getItem('status') == 'true'){
      this.isLogged.next(true);
    } else {
      this.isLogged.next(false);
    }
  }

}

 

test1이라는 함수를 만들었습니다.
그리고 이를 생성자(constructor)에서 2번 호출하여 보았습니다.

2번 구독했으니 2개의 결과가 나오고 있습니다.

 

2번 호출하였으므로 2개의 내용이 출력된 것을 볼 수 있습니다.
test1 함수에 next 호출 부분을 2개 더 추가하여 보겠습니다.

next를 2개 더 추가하여 보았습니다. 그러니 총 6개의 결과가 출력되고 있습니다.

 

예상한 내용이 출력되는 것을 볼 수 있습니다.
이렇게 출력되는 내용, Observable 내부의 정의된 행동이 subscribe 메소드가 호출이 되어야만 동작하는 것이 특징입니다.
또한 subscribe 함수를 호출할 때 마다 구독자가 생성되는 것도 특징입니다.

우리는 서비스에서 생성자를 통해서 test1함수를 2번 호출하였습니다.

2번 호출을 통해 2개의 구독자가 탄생했습니다.
login컴포넌트에서 우리가 만약에 로그인을 여러번 호출하면 위 내용처럼 구독자가 n개씩 생성될 것 입니다.
그리고 위의 사진처럼 여러명의 구독자에 의해서 여러개의 행위가 동작 할 것 입니다.


여기서 중요한 점은, 여러개의 구독행위가 필요하지 않는경우에 의해서는 한번 구독을 하고나면 다음 구독행위를 만들 때 기존의 구독행위는 끝내야 합니다.
안그러면 같은 행동을 여러번 할 수 있기 때문 입니다.

예를들어, 우리가 데이터베이스 서버에 데이터를 바꾸어달라고 요청(subscribe)하는 기능을 만들었다고 가정하여 봅니다.
test라는 데이터를 1로 만들어 달라고 요청(subscribe)하였다가 마음이 바뀌어 2로 바꾸어 달라고 요청(subscribe)하였다고 가정하여 봅니다.
이런 식으로 subscribe를 2번 호출하면 2개의 구독자가 생기므로 2번의 구독행위가 지속해서 발생 할 수 있습니다.

* 무조건 발생하는 것은 아닙니다.

* 구독하는 행위의 정의에 따라서 발생 할 수 있고 발생하지 않을수도 있습니다.

 

그러면, 구독행위를 끝내는 방법을 login컴포넌트에 적용하여 보겠습니다.

* 대상 : login.component.ts

import { Component, OnInit, OnDestroy } from '@angular/core';
import { AskService } from '../ask.service';
import { Router } from '@angular/router';
import { Subscription } from 'rxjs';

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

  id : string;
  passwd : string;

  constructor(private service : AskService, private rout: Router) { 

  }

  ngOnInit(): void {
 
  }

  private script : Subscription;  //subscribe 동작 후 만들어지는 대상

  login() {
    if(this.script){
      console.log(this.script);
      this.script.unsubscribe();  //구독 종료
    }
    this.script = this.service.tryToLogin({id : this.id, passwd : this.passwd}).subscribe( (arg:any)=>{
      if(arg.status == true){
        alert('로그인 성공!')
        this.rout.navigate(['/board']);
      }
    });
  }
  ngOnDestroy(): void {
    console.log(this.script);
    if(this.script){
      this.script.unsubscribe(); //구독 종료
    }
  }
}

 

subscribe가 동작을 하고난 뒤에 Subscription의 형태의 객체가 만들어 졌습니다.
만들어진 Subscription의 형태의 객체를 unsubscribe라는 함수를 통해서 구독행위를 종료 하였습니다.
take같은 연산자를 통해서도 가능하지만 내용이 조금 복잡해 지므로 나중에 한번 찾아서 적용해 보시는 것을 권장드립니다.
* 나중에 pipe와 take를 통해서는 조금 더 깔끔하게 구현 할 수 있습니다.

그리고 login컴포넌트에서 추가로 OnDestroy를 상속 받았습니다.
OnDestroy는 해당 컴포넌트가 영문 명칭 그대로 컴포넌트가 destroy 될 때의 행위를 정의할 수 있습니다.
이렇게 구독행위를 종료(unsubscribe) 하는 것이 이번 내용의 핵심입니다.

 

정리하여 보겠습니다.

1. 구독가능한 객체(subscribe가 실행되는)는 구독(subscribe)을 할 때 마다 구독자가 생성 됩니다.

2. 구독자는 생성 된 갯수만큼 동작을 합니다.

3. 구독행위가 여러번 필요하지 않는 경우에는 반드시 종료(unsubscribe)를 해 주어야 합니다.

 

내용이 쉽지가 않습니다만 여기서는 일단 공식이라고 생각하는 것이 좋을 것 같습니다.

 -> subscribe로 생성된 객체는 여러번 호출이 필요한 경우가 생긴다면 unsubscribe를 한다!

나중에 파이어베이스와의 연동에서 왜 이렇게 해야되는지 좀 더 이해하기 쉬울 것 입니다. +ㅁ+
앵귤러에서의 http, https, 파이어베이스 등 네트워크와 관련된 내용의 대부분은 rxjs의 subscribe과 unsubscribe의 내용으로 되어 있습니다.
해당 패턴에 대해서 좀 더 익숙해 지도록 연습할 필요가 있겠습니다.

다음시간에는 드디어 파이어베이스 연동을 통해서 실제 데이터와의 연동기능을 만들어 보겠습니다.

thirdStudy.zip
0.01MB

 

 

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

댓글