Angular 수명 주기 후크를 사용하여 가져올 때 렌더링

비동기 파이프, 구독 또는 RxJS 없이 구성 요소 내에서 바로 비동기 데이터를 로드할 수 있다면 어떨까요? 바로 뛰어들자.

데이터를 가져 오는 중



먼저 앱에 표시할 데이터가 필요합니다. HTTP를 통해 할 일을 가져오는 서비스를 정의합니다.

const endpoint = "https://jsonplaceholder.typicode.com/todos"

@Injectable({ providedIn: "root" })
class TodosResource implements Fetchable<Todo[]> {
   fetch(userId: number) {
      return this.http.get(endpoint, {
         params: { userId }
      })
   }

   constructor(private http: HttpClient) {}
}


가져오기 함수 자체는 관찰 가능 항목을 반환할 필요가 없습니다. 약속과 같은 관찰 가능한 입력을 쉽게 반환할 수 있습니다. 모든 리소스가 이Fetchable 인터페이스를 준수하도록 할 것입니다.

현재 이것은 일반적인 Angular 서비스입니다. 나중에 다시 다루겠습니다.

리소스 인터페이스



기본적으로 리소스는 다음 두 가지 작업을 수행할 수 있습니다.

interface Resource<T extends Fetchable<any>> {
   fetch(...params: FetchParams<T>): any
   read(): FetchType<T> | undefined
}


가져오기 일부 데이터를 가져오도록 리소스에 지시합니다. 이는 HTTP 또는 GraphQL 엔드포인트, 웹소켓 또는 다른 비동기 소스에서 올 수 있습니다.

읽기 리소스의 현재 값을 읽으려고 시도합니다. 값이 아직 도착하지 않았기 때문에 undefined일 수 있습니다.

이 인터페이스를 정의하면 이를 구현하는 클래스를 작성할 수 있습니다.

구현



아래 예는 간결함을 위해 잘렸습니다. 보다 구체적인 예는 다음과 같습니다found here.

import { EMPTY, from, Subscription } from "rxjs"

export class ResourceImpl<T extends Fetchable> 
   implements Resource<T> {

   value?: FetchType<T>
   params: any
   subscription: Subscription
   state: string

   next(value: FetchType<T>) {
      this.value = value
      this.state = "active"
      this.changeDetectorRef.markForCheck()
   }

   read(): FetchType<T> | undefined {
      if (this.state === "initial") {
         this.connect()
      }
      return this.value
   }

   fetch(...params: FetchParams<T>) {
      this.params = params
      if (this.state !== "initial") {
         this.connect()
      }
   }

   connect() {
      const source = this.fetchable.fetch(...this.params)
      this.state = "pending"
      this.unsubscribe()
      this.subscription = from(source).subscribe(this)
   }

   unsubscribe() {
      this.subscription.unsubscribe()
   }

   constructor(
      private fetchable: T,
      private changeDetectorRef: ChangeDetectorRef
   ) {
      this.source = EMPTY
      this.subscription = Subscription.EMPTY
      this.state = "initial"
   }
}


리소스는 생성자에 삽입된 fetchable 개체에 실제 데이터 가져오기 논리를 위임합니다. 리소스는 읽을 때 항상 최신 값을 반환합니다.

또한 초기 상태에 있는 경우 데이터를 즉시 가져오지 않는다는 것을 알 수 있습니다. 첫 번째 가져오기를 위해 read가 호출될 때까지 기다립니다. 이는 컴포넌트가 처음 마운트될 때 불필요한 가져오기를 방지하기 위해 필요합니다.

리소스 관리에 도움이 되는 다른 서비스도 작성해 보겠습니다.

import { 
   Injectable, 
   InjectFlags, 
   Injector, 
   ChangeDetectorRef
} from "@angular/core"

@Injectable()
export class ResourceManager {
   private cache: Map<any, ResourceImpl<Fetchable>>

   get<T extends Fetchable>(token: Type<T>): Resource<T> {
      if (this.cache.has(token)) {
         return this.cache.get(token)!
      }
      const fetchable = this.injector.get(token)
      const changeDetectorRef = this.injector
         .get(ChangeDetectorRef, undefined, InjectFlags.Self)
      const resource = new ResourceImpl(
         fetchable, 
         changeDetectorRef
      )
      this.cache.set(token, resource)
      return resource
   }

   ngOnDestroy() {
      for (const resource of this.cache.values()) {
         resource.unsubscribe()
      }
   }

   constructor(private injector: Injector) {
      this.cache = new Map()
   }
}


용법



리소스 서비스를 구축했으므로 이제 실제로 살펴보겠습니다!

<!-- todos.component.html -->

<div *ngFor="let todo of todos">
  <input type="checkbox" [value]="todo.complete" readonly />
  <span>{{ todo.title }}</span>
</div>

<button (click)="loadNextUser()">
  Next user
</button>



import {
   Component,
   OnChanges,
   DoCheck,
   Input,
   ChangeDetectionStrategy
} from "@angular/core"

import { 
   Resource,
   ResourceManager
} from "./resource-manager.service"

import { Todos, TodosResource } from "./todos.resource"

@Component({
   selector: "todos",
   templateUrl: "./todos.component.html",
   providers: [ResourceManager],
   changeDetection: ChangeDetectionStrategy.OnPush
})
export class TodosComponent implements OnChanges, DoCheck {
   @Input()
   userId: number

   resource: Resource<TodosResource>

   todos?: Todos[]

   ngOnChanges() {
      this.loadNextUser(this.userId)
   }

   ngDoCheck() {
      this.todos = this.resource.read()
   }

   loadNextUser(userId = this.userId++) {
      this.resource.fetch(userId)
   }

   constructor(manager: ResourceManager) {
      this.userId = 1
      this.resource = manager.get(TodosResource)
      this.resource.fetch(this.userId)
   }
}


마지막으로 fetch가 두 번 호출되는 것을 볼 수 있습니다. 생성자에서 한 번 그리고 ngOnChanges 수명 주기 후크 동안 다시 한 번. 그렇기 때문에 처음으로 데이터 소스를 구독하기 전에 read를 기다려야 합니다.

모든 마법은 ngDoCheck에서 발생합니다. 일반적으로 이 후크를 사용하는 것은 좋지 않지만 가져올 때 렌더링하는 데는 완벽합니다! read 함수는 단순히 리소스의 현재 값을 반환하고 이를 todos에 할당합니다. 마지막 읽기 이후 리소스가 변경되지 않은 경우 no-op입니다.

이것이 작동하는 이유가 궁금하다면 nextResourceImpl 함수로 다시 스크롤하십시오.

next() {
   // ...
   this.changeDetectorRef.markForCheck()
}


이는 자원이 새 값을 수신할 때마다 보기를 더티로 표시하고 결국 ngDoCheck를 트리거합니다. 리소스가 동기 값을 매우 빠르게 생성하는 경우 추가 변경 감지 호출도 방지합니다. 정돈된!

요약



Angular의 변경 감지 메커니즘을 활용하여 가져올 때 렌더링할 수 있습니다. 이를 통해 뷰를 차단하지 않고 여러 데이터 스트림을 병렬로 쉽게 로드할 수 있으며, 조금 더 노력하면 데이터가 로드되는 동안 사용자에게 멋진 폴백을 보여줄 수도 있습니다. 접근 방식은 데이터에 구애받지 않으며 기존 코드를 보완해야 합니다.

즐거운 코딩하세요!

좋은 웹페이지 즐겨찾기