Angular에서 동적으로 구성 요소 생성

나는 내 고객 중 한 명과 이야기하고 있었고 그들은 Angular에서 동적으로(또는 데이터/메타데이터를 기반으로) 개별 구성 요소를 빌드할 수 있는 방법을 궁금해했습니다. 나는 이것이 가능하다는 것을 알고 있었지만 이것을 직접 해본 적이 없기 때문에 파헤쳐 봐야겠다고 생각했습니다.

흥미로운 점 중 하나는 중요한 기술이지만 실제로는 필요하지 않은 Angular의 Reactive Forms에 사로잡힐 수 있다는 것입니다. 내가 할 수 있기를 원했던 것은 구성 요소에서 레이아웃을 만드는 것이었습니다. 그리 어렵지 않은 것으로 나타났습니다.

나는 새로운 Angular 프로젝트로 시작했습니다. 완료율이 있는 막대를 표시하기 위해 간단한 작은 구성 요소를 만들었습니다(정말 간단함).

import { Component } from "@angular/core";

@Component({
  template: `<div class="border rounded border-gray-300 m-1 p-1">
    <div>% Complete</div>
    <div [innerHtml]="'&#x2588;'.repeat(this.val)"></div>
  </div>`
})
export class Gauge {
  val = 0;
}



이 중 하나는 다음과 같습니다.



동적으로 여러 개를 만들 수 있기를 원했습니다. 두 가지가 필요했습니다.
  • 구성 요소를 주입할 컨테이너를 가져오는 방법이 필요했습니다.
  • 구성 요소를 생성하는 방법이 필요했습니다(new Gauge()를 호출하는 것만으로는 작동하지 않음).

  • 컨테이너 가져오기



    템플릿의 최상위 컨테이너에 액세스하려는 경우 ViewContainerRef 객체를 생성자에 주입하면 됩니다.

    @Component({
      selector: 'app-root',
      template: `
      <div class="container mx-auto bg-white">
        <div class="text-xl">Dashboard</div>
        <div class="grid grid-cols-4">
        </div>
      </div>
      `,
      styles: []
    })
    export class AppComponent implements OnDestroy {
    
      components: Array<ComponentRef<Gauge>> = [];
    
      constructor(private ViewContainerRef container) { }
    
    


    이 접근 방식의 문제는 내가 최상위 컨테이너를 원하지 않고 마크업 내부에 추가로 삽입하고 싶었다는 것입니다. 그리드 div에 주입하고 싶었습니다. 이를 위해 div 내부에 ng-template을 추가했습니다.

      <div class="container mx-auto bg-white">
        <div class="text-xl">Dashboard</div>
        <div class="grid grid-cols-4">
          <ng-template #gauges ></ng-template>
        </div>
      </div>
    
    


    컨테이너를 잡을 수 있도록 #gauges를 사용하여 컨테이너 이름을 지정했습니다. @ViewChild 데코레이터로 이 작업을 수행했습니다.

      @ViewChild("gauges", { read: ViewContainerRef }) container: ViewContainerRef;
    
    


    이것은 컨테이너 멤버를 ViewContainerRef(위의 생성자 주입처럼)로 연결하지만 이 특정 요소에 대해 연결합니다. 이것이 연결되려면 보기가 초기화될 때까지 기다려야 합니다.

      ngAfterViewInit(): void {
        // container is now valid, ngOnInit is too early
      }
    
    


    이제 컨테이너가 생겼습니다. 새 게이지 구성 요소를 생성하려면 어떻게 해야 합니까?

    컴포넌트 팩토리 얻기



    게이지를 생성할 수 있는 팩토리를 얻으려면 생성자에 주입할 수 있는 팩토리 리졸버가 필요합니다.

    constructor(private resolver: ComponentFactoryResolver) { }
    
    


    이 리졸버를 사용하여 컴포넌트의 팩토리를 확인할 수 있습니다.

    // Get a factory for a known component
    const factory: ComponentFactory<Gauge> =
      this.resolver.resolveComponentFactory(Gauge);
    
    


    이는 컴포넌트를 생성하는 데 사용할 수 있는 팩토리를 제공합니다. 그런 다음 동적으로 여러 개를 만들 수 있습니다.

    // Dynamic creating them
    for (let x = 0; x < 20; ++x) {
      this.container.createComponent(factory);
    }
    
    


    createComponent를 호출하면 컨테이너에 삽입됩니다. 이것은 팩터리를 허용하는 컨테이너의 메서드입니다. 문제가 없는지 확인하려면 onDestroy로 제거할 수 있도록 구성 요소에 대한 핸들을 유지해야 합니다.

    // Dynamic creating them
    for (let x = 0; x < 20; ++x) {
      const gauge = this.container.createComponent(factory);
    
      // Keep a copy for destruction
      this.myGauges.push(gauge);
    }
    
    


    그런 다음 그냥 파괴하십시오.

      ngOnDestroy(): void {
        for (let x = 0; x < this.myGauges.length; ++x) {
          this.myGauges[x].destroy();
        }
      }
    
    


    이것은 잘 작동하지만 일부 상태를 설정해야 하는 경우에는 어떻게 해야 합니까? 게이지에는 백분율을 표시하는 val 속성이 있음을 기억하십시오. 이를 위해 인스턴스를 확인하여 게이지 자체에 속성을 설정할 수 있습니다(여기서 반환된 게이지는 구성 요소에 대한 참조일 뿐임을 기억하십시오).

    // Dynamic creating them
    for (let x = 0; x < 20; ++x) {
      const gauge = this.container.createComponent(factory);
    
      // Set instance properties
      gauge.instance.val = Math.ceil(Math.random() * Math.floor(20));
    
      // Ensure that change detection happens once
      gauge.changeDetectorRef.detectChanges();
    
      // Keep a copy for destruction
      this.myGauges.push(gauge);
    }
    
    


    이 경우 각 게이지에 임의의 숫자를 설정합니다. 그러나 구성 요소에 의해 생성된 후 상태를 변경하는 경우 변경 내용을 연결하도록 changeDetector에 지시해야 합니다. 해당 줄이 없으면 변경 감지의 일관성을 얻습니다.



    그게 다야.

    여기에서 전체 코드를 얻을 수 있습니다.

    https://github.com/wilder-minds/ngDynamicExample





    Shawn Wildermuth의 이 저작물은 Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License에 따라 라이선스가 부여되었습니다.
    wildermuth.com의 작업을 기반으로 합니다.


    이 기사가 마음에 들면 Pluralsight에서 Shawn의 과정을 참조하십시오.

    좋은 웹페이지 즐겨찾기