프로필사진
angular에서 동적으로 컴포넌트 생성하자 - ApplicationRef

2020. 9. 1. 15:31🔴 Angular

300x250

개발자가 component 안의 html에 등록한 template들을 화면에 보여주는 게 기본이지만,
원하는 특정 시점에서 동적으로 컴포넌트를 생성해서 화면에 변화를 줘야할 때가 있다.


특정 시점에
New Container (div) 를 만들어서
그 안에 그래프가 만들어져있는 New Component를 넣을 것이다.

ApplicationRef를 사용하여 New Component를 어떻게 동적으로 생성하는지 알아보자.


ApplicationRef ?

A reference to an Angular application running on a page.

interface ApplicationRef {
  componentTypes: Type<any>[]
  components: ComponentRef<any>[]
  isStable: Observable<boolean>
  viewCount
  bootstrap<C>(componentOrFactory: ComponentFactory<C> | Type<C>, rootSelectorOrNode?: any): ComponentRef<C>
  tick(): void
  attachView(viewRef: ViewRef): void
  detachView(viewRef: ViewRef): void
}

ApplicationRef
즉 angular application이라고 볼 수 있다. 
router, directive 등 시스템의 기반이 되는 내용들이 정의되어 있다.

여기에 bootstrap() 메소드를 사용할 것인데,

bootstrap<C>(componentOrFactory: ComponentFactory<C> | Type<C>, rootSelectorOrNode?: any): ComponentRef<C>

 

이는 새로운 컴포넌트를 애플리케이션의 root level에서 부트스트랩 해준다.

부트스트랩 과정은 이러하다 :
새로운 컴포넌트를 애플리케이션에 부트스트랩을 할 때,
앵귤러는 componentType의 selector가 가리키는 DOM 요소에
특정 application component를 설치한다.

또한 선택적으로 (option),
컴포넌트를 componentType의 selector에
일치하지 않는 DOM 요소에 설치할 수도 있다.

그러고 나선 자동 변화 감지를 하여
컴포넌트 초기화를 한다.


ApplicationRef.bootstrap()에
컴포넌트를 만들 수 있는 component factory & 추가하고 싶은 root selector
이렇게 2개 인자를 모두 전달하여 컴포넌트를 생성할 것이다.

1. ComponentFactoryResolver를 사용하여 component factory를 만들기

const compFactory = this.componentFactoryResolver.resolveComponentFactory(NewComponent);

ComponentFactoryResolver.resolveComponentFactory는 NewComponent를 만들 수 있는 ComponentFactory를 반환한다.

2. ApplicationRef.bootstrap() 하여 컴포넌트 동적 생성

const newComp = this.appRef.bootstrap(compFactory, $newContainerDiv);

앞서 만든 compFactory를 첫 번째 인자로,
미리 제이쿼리로 만든 New Container(div)를 두 번째 인자로 넣었다.

이는 ComponentFactory로 만든 component인
ComonentRef를 반환한다.

newComp.instance.changeColor('red');

newComp.instance를 통해
새로 추가한 컴포넌트 인스턴스를 조작할 수도 있다.


<전체 소스>

constructor(private componentFactoryResolver: ComponentFactoryResolver,
              private appRef: ApplicationRef) { }

  public createComponent() {
  	const compFactory = this.componentFactoryResolver.resolveComponentFactory(NewComponent);
  	const newComp = this.appRef.bootstrap(compFactory, $newContainerDiv);
  }

발생한 이슈 🤔!!

필자는 이렇게 동적으로 생성한 컴포넌트가 ApplicationRef에 계속해서 쌓이는 것을 발견했다.

ApplicationRef의 componentTypes[], components[] 를 보면
동적으로 추가한 컴포넌트 (ReportWidgetComponent)가 누적해서 쌓이고 있다.

따라서 동적으로 컴포넌트를 생성하는 부분을 service 단으로 빼서
따로 관리하여 쓰지 않는 ComponentRef를 destroy 하려고 하였다.

참고한 소스 :
https://ngohungphuc.wordpress.com/2019/08/22/create-and-destroy-dynamic-component-in-angular/

 

Create and destroy dynamic component in Angular

Today I will show you guys how to create dynamic component using service in Angular and how to safely destroy component when we dont need it anymore. And finally we will learn how to interact with …

ngohungphuc.wordpress.com

아래와 같이 ComponentFactoryService를 만들었다 :

import {ApplicationRef, ComponentFactoryResolver, ComponentRef, Injectable} from '@angular/core';

@Injectable()
export class ComponentFactoryService {

  private componentRef: ComponentRef<any>;

  constructor(private componentFactoryResolver: ComponentFactoryResolver,
              private appRef: ApplicationRef) { }

  public createComponent(component: any, rootSelector: any) {
    const compFactory = this.componentFactoryResolver.resolveComponentFactory(component);
    this.componentRef = this.appRef.bootstrap(compFactory, rootSelector);
    return this.componentRef;
  }

  public destroyComponent() {
    if (this.componentRef) {
      this.componentRef.destroy();
    }
  }
}

createComponet(), destroyComponent()를 통해
appRef.bootstrap()으로 생성한 컴포넌트들을 관리하려고 했다.

하지만,
destroyComponent() 하는 부분에서 ApplicationRef를 찍어본 결과 :
components[] 에는 원하는 대로 불필요한 컴포넌트들을 destroy 한 것을 확인할 수 있었지만
componentTypes[] 에서는 앞 문제와 똑같이 계속해서 컴포넌트가 누적되고 있었다.

흠........................................
두 배열 모두 현재 애플리케이션에 register 되어있는 component에 대한 정보를 가지고 있는 거 아닌가 ?

ComponentRef.destory() 를 했을 때
components[] 에서 컴포넌트는 지워졌지만
componentTypes[] 에서는 지워지지 않았나 보다.
( 좀 알아서 지워주지.. )

😵멘붕이 와서 도움을 요청해본 결과,
angular core 코드를 확인해 보니
componentTypes 는 추가만 있고 삭제가 없다고 하셨다....!!

애초부터 New Component (div) 를 제이쿼리로 동적으로 만들어야 해서,
새로 만든 div로부터 viewContainerRef 를 얻기 어려워서
ApplicationRef.bootstrap() 방식을 채택했는데..

다른 예제들을 찾아보면 죄다 viewContainerRef 를 사용하여 동적으로 컴포넌트를 생성했다.
그 예제에서는 @ViewChild와 같이 html 안에 이미 들어있는 컴포넌트를 가져와서
해당 컴포넌트의 viewContainerRef 를 사용하여 새로 컴포넌트를 만들어줬다.


결국 ApplicationRef 가 아닌 viewContainerRef 를 사용해보기로 한다.

300x250