각도 투영/주입(옵션)

25763 단어 angular

각도 투영/주입(옵션)
최근에 사용자 정의 내용으로 구성 요소의 일부분을 교체해야 하는 요구가 있습니다. (사용자 정의 표의 제목)어떤 내용도 제공하지 않으면 '기본' 내용을 보여야 합니다.주입/투영의 내용은 간단한 텍스트에서 슬라이더/전환의 모든 내용일 수 있기 때문에 간단한 속성으로는 부족합니다..
이러한 요구 사항은 다음과 같이 요약할 수 있습니다.
  • 맞춤형 콘텐츠를 제공하는 경우
  • 기본값
  • 나는 문제를 해결하기 위해 ng-content이나 ng-template을 사용하고 있다.현명한 결정을 내리기 위해, 나는 이 두 가지 옵션을 실현하기 위해 POC를 만들었는데, 그 중 하나가 다른 것보다 우수한지 보았다.필요에 반하여 만들어진 POC은 여러 개의 내용(예를 들어 눈썹과 꼬리)을 교체하여 해결 방안이 앞으로 확장될 수 있는지 검증할 수 있습니다(필요할 경우).다음 몇 절에서는 내가 생각해 낼 수 있는 대체 방안을 소개할 것이다.

    내용
    이것은 통상적으로 첫 번째 옵션이다. 왜냐하면 그것은 실현하기 쉽고 사용하기 쉽기 때문이다.사용자 정의 컨텐트는 ng-content을 하위 레벨로 사용합니다.select 속성을 사용하여 여러 컨텐트를 투영할 수도 있습니다.
    <ng-content select="[slot='header']"></ng-content>
    <ng-content select="[slot='footer']"></ng-content>
    
    이것은 첫 번째 요구를 포함한다.두 번째 문제는 ng-content을 단독으로 사용하면 더욱 어렵다.사용자 정의 내용을 보여줄지 기본 내용을 보여줄지 확인하려면 어떤 내용이 ng-content으로 전달되었는지 확인하는 방법이 필요합니다.구성 요소나 템플릿에서 정보를 조회하거나 얻을 수 있는 내장 기능을 찾을 수 없습니다. 사용자 정의 해결 방안이 필요합니다.
    다음 예제에서는 투영할 컨텐트에 배치하는 명령을 작성합니다.
    <app-render-slot>
      <div appSlot slot="header">Custom Header</div>
      <div appSlot slot="footer">Custom Footer</div>
    </app-render-slot>
    
    구성 요소는 appSlot 조회를 사용하여 명령을 검색할 수 있습니다.자리 표시자 컨텐트가 발견되면 사용자 정의 컨텐트를 사용하고 그렇지 않으면 기본 컨텐트가 반환됩니다.
    @Component({
      selector: 'app-render-slot',
      templateUrl: './component.html',
      styleUrls: ['./component.css'],
      changeDetection: ChangeDetectionStrategy.OnPush
    })
    export class RenderSlotComponent {
      @ContentChildren(SlotDirective, { read: ElementRef }) set slotDirectives(
        value: QueryList<ElementRef>
      ) {
        this.nativeSlots.next(Array.from(value));
      }
    
      private nativeSlots: BehaviorSubject<Array<ElementRef>>;
      readonly slotNames$: Observable<SlotNames>;
    
      constructor() {
        this.nativeSlots = new BehaviorSubject<Array<ElementRef>>([]);
    
        this.slotNames$ = this.setSlotsByName(this.nativeSlots.asObservable());
      }
    
      isSlotSet(slotName: SlotName): Observable<boolean> {
        return this.slotNames$.pipe(
          map((slotNames) => slotNames.includes(slotName))
        );
      }
    
      private setSlotsByName(
        slots$: Observable<Array<ElementRef>>
      ): Observable<SlotNames> {
        return slots$.pipe(
          map((slots) =>
            slots.map((slot) => slot.nativeElement.getAttribute('slot'))
          )
        );
      }
    }
    
    예를 들어, 슬롯의 이름(머리글 또는 바닥글)은 투영 컨텐트에 대한 사용자 정의 슬롯 속성에 설정된 컨텐트를 기준으로 추출됩니다.찾으려는 @ContentChildrenElementRef에 표시되어 있고 SlotDirective에 표시되어 있습니다.구현의 또 다른 부분은 @ContentChildren의 목록을 슬롯 이름에 비추는 것입니다.ElementRef 메서드의 도움말에서 템플릿은 사용자 정의 컨텐트(슬롯이 있는 경우)를 표시하거나 기본 컨텐트로 되돌릴 수 있습니다.
    예를 들면, 구성 요소의 템플릿은 isSlotSet 자리 표시자만 포함하여 간단합니다.
    <ng-content
      select="[slot='header']"
      *ngIf="isSlotSet('header') | async; else defaultHeader"
    ></ng-content>
    <ng-content
      select="[slot='footer']"
      *ngIf="isSlotSet('footer') | async; else defaultFooter"
    ></ng-content>
    
    <ng-template #defaultHeader> Default Header </ng-template>
    <ng-template #defaultFooter> Default Footer </ng-template>
    
    여기 설명된 대안은 예시 저장소의 ng-content 폴더에서 찾을 수 있습니다.ng-content/render-slotdiv 템플릿에서 "사용자 머리글"또는 "사용자 머리글"AppComponent을 삭제하면 기본 예비(fallback)이 표시됩니다.

    더 느린 사람
    알림: 이 해결 방안은 효과가 없습니다. 관심이 없으면 넘어가십시오.
    상술한 방법의 단점은 선택할 수 있는 내용 투영을 가진 모든 구성 요소는 반드시 렌더링 내용을 찾거나 확정하는 메커니즘을 실현해야 한다는 것이다.app-render-slot이라는 Assistant 구성 요소를 생성하여 솔루션을 개선하는 것이 제 생각입니다. 이 구성 요소는 구성 요소를 사용하여 전달하는 내용을 보여줍니다.
    <app-slot-renderer [defaultSlotContent]="defaultHeader"
      ><ng-content select="[slot='header']"></ng-content
    ></app-slot-renderer>
    <app-slot-renderer [defaultSlotContent]="defaultFooter"
      ><ng-content select="[slot='footer']"></ng-content
    ></app-slot-renderer>
    
    <ng-template #defaultHeader> <div>Default Header</div> </ng-template>
    <ng-template #defaultFooter> <div>Default Footer</div> </ng-template>
    
    사용자 정의 컨텐트는 SlotRendererComponentng-content 속성을 사용하여 제공됩니다(select 항목이 하나일 경우 후자를 생략할 수 있습니다).기본 컨텐트는 ng-content 속성을 TemplateRef 전송으로 사용합니다.Input은 또한 SlotRendererComponent을 사용하여 어셈블리에서 투영된 컨텐트를 렌더링합니다.
    <ng-content *ngIf="isSlotSet$ | async; else defaultSlotContent"></ng-content>
    
    따라서 처음에 전달된 사용자 정의 컨텐트는 두 번 투영됩니다.
  • 외부 부품에 먼저 연결(예: ng-content)
  • 초 ~ RenderSlotSlotRendererComponent
  • 편평한 계층은 다음과 같습니다(실제 DOM 구조가 아님).
    <!-- From SlotRendererComponent  -->
    <ng-content *ngIf="isSlotSet$ | async; else defaultSlotContent">
      <!-- From RenderSlotSlotRendererComponent  -->
      <ng-content select="[slot='header']">
        <!-- Projected custom content   -->
        <div appSlot slot="header">Custom Header</div>
      </ng-content>
    </ng-content>
    <!-- Same for the footer -->
    
    첫 번째 방법과 같은 메커니즘을 통해 사용자 정의 또는 기본 내용은 SlotRendererComponent에 나타난다.
    이 해결 방안이 작동하지 않는 이유는 SlotRendererComponent에서 끼워 넣은 @ContentChildrens를 조회할 수 없기 때문이다. ng-content을 설정하는 것도 나에게 작용하지 않는다.나는 issue{ descendants: true } 저장소의 문제를 묘사했기 때문에 그것이 관련되었을 수도 있다. (또는 내가 여기서 무엇을 잘못했는지.)

    템플릿

    템플릿 속성 사용AngularDart을 기반으로 한 해결 방안의 하나는 속성 중의 사용자 정의 내용을 ng-templates로 직접 전달하는 것이다.
    <app-template-render-props
      [templates]="{ 'header': header, 'footer': footer }"
    ></app-template-render-props>
    
    <ng-template #header><div>Custom Header</div></ng-template>
    <ng-template #footer><div>Custom Footer</div></ng-template>
    
    슬롯당 TemplateRefTemplateRef을 사용하여 렌더링됩니다.*ngTemplateOutlet 방법과 마찬가지로, 정의된 내용이 없으면 구성 요소는 기본 내용을 되돌려줍니다. (이 예에서 ng-content 조수가 완성했습니다.)
    <app-render-template
      [template]="{ customTemplate: templates.header, defaultTemplate: defaultHeader }"
    ></app-render-template>
    <app-render-template
      [template]="{ customTemplate: templates.footer, defaultTemplate: defaultHeader }"
    ></app-render-template>
    
    <ng-template #defaultHeader> <div>Default Header</div> </ng-template>
    <ng-template #defaultFooter> <div>Default Footer</div> </ng-template>
    

    지령을 달다
    모든 사용자 정의 내용에 전용 RenderTemplateComponent 패키지를 정의해야 합니다. 이것은 구성 요소의 템플릿을 사용하는 데 불편을 주고 혼란을 초래할 수 있습니다.스토리지 ng-template과 슬롯 이름에 대한 Fabric 명령을 사용하여 이러한 상황을 방지할 수 있습니다.
    @Directive({
      selector: '[appTemplateSlot]'
    })
    export class TemplateSlotDirective {
      @Input() appTemplateSlot: SlotName | null = null;
    
      constructor(public template: TemplateRef<unknown>) {}
    }
    
    이 명령은 슬롯 이름(예제의 머리글 또는 바닥글)을 입력 속성으로 하고 연관된 TemplateRef을 공통 TemplateRef 속성에 저장합니다(templateunknown 유형은 알고 있거나 사용 가능한 경우 연관된 컨텍스트로 대체할 수 있음).
    렌더링 구성 요소는 현재 TemplateRef을 사용하여 TemplateSlotDirective을 쿼리하고 스토리지의 @ContentChildren을 관련 슬롯에 렌더링할 수 있습니다.
    @Component({
      selector: 'app-render-props-directive',
      templateUrl: './component.html',
      styleUrls: ['./component.css'],
      changeDetection: ChangeDetectionStrategy.OnPush
    })
    export class RenderPropsDirectiveComponent {
      @ContentChildren(TemplateSlotDirective) set templateSlots(
        templateSlots: QueryList<TemplateSlotDirective>
      ) {
        this.templateDirectives.next(
          templateSlots.length > 0 ? Array.from(templateSlots) : []
        );
      }
    
      private templateDirectives: ReplaySubject<Array<TemplateSlotDirective>>;
      templates$: Observable<Partial<Templates>>;
    
      constructor() {
        this.templateDirectives = new ReplaySubject(1);
    
        this.templates$ = this.setupTemplates(
          this.templateDirectives.asObservable()
        );
      }
    
      private setupTemplates(
        templateDirectives$: Observable<Array<TemplateSlotDirective>>
      ): Observable<Partial<Templates>> {
        return templateDirectives$.pipe(
          map((templateDirectives) =>
            templateDirectives.reduce(
              (partialTemplateDirectives, templateDirective) =>
                templateDirective.appTemplateSlot
                  ? {
                      ...partialTemplateDirectives,
                      [templateDirective.appTemplateSlot]:
                        templateDirective.template
                    }
                  : partialTemplateDirectives,
              {}
            )
          ),
          shareReplay({ bufferSize: 1, refCount: true })
        );
      }
    }
    
    일반적으로 렌더링 구성 요소는 각 슬롯에 대해 사용자 지정 또는 예비(fallback) 컨텐트를 렌더링합니다.
    <app-render-template
      [template]="{ customTemplate: (templates$ | async)?.header, defaultTemplate: defaultHeader }"
    ></app-render-template>
    <app-render-template
      [template]="{ customTemplate: (templates$ | async)?.footer, defaultTemplate: defaultHeader }"
    ></app-render-template>
    
    <ng-template #defaultHeader> <div>Default Header</div> </ng-template>
    <ng-template #defaultFooter> <div>Default Footer</div> </ng-template>
    
    다음 그림과 같이 template 패키지를 사용자 정의 콘텐츠에 ng-template 선택기로 교체합니다.
    <app-render-props-directive>
      <div *appTemplateSlot="'header'">Custom Header</div>
      <div *appTemplateSlot="'footer'">Custom Footer</div>
    </app-render-props-directive>
    

    결론TemplateSlotDirectiveng-content을 사용하면 사용자 정의 내용을 표시하거나 기본 내용을 표시하는 요구를 충족시킬 수 있습니다.ng-template 기반 솔루션을 선호합니다.
  • 은 구조 명령과 함께 사용할 때 사용하는 구성 요소(특히 템플릿 내)에 대해 ng-template과 같은 용이성을 제공한다.
  • 은 렌더링과 관련된 모든 반복 실행을 추출할 수 있으며, 이러한 실현은 같은 '기능'이 필요한 구성 요소에 사용할 수 있다.ng-content 쿼리 중첩 ng-content의 문제로 인해 ng-content의 해결 방안을 바탕으로 이 점을 실현할 수 없다.
  • POC의 전체 코드는 here에서 찾을 수 있습니다.

    좋은 웹페이지 즐겨찾기