عرض المحتوى في وثائق المحتوى الزاوي أو المفقودة

عند تعلم Angular ، غالبًا ما يخطئون أو لا يهتمون بما يكفي بمفهوم مثل عرض المحتوى. هذه أداة قوية للغاية لإنشاء مكونات مرنة وقابلة لإعادة الاستخدام. لكن توثيقه يذكر فقط فقرتين في قسم خطافات دورة الحياة . دعونا نحاول إصلاح هذا الإغفال.



إسقاط المحتوى باستخدام المحتوى نانوغرام


يعد إسقاط المحتوى طريقة لاستيراد محتوى HTML من خارج مكون ولصقه في قالب مكون في موقع محدد. (ترجمة مجانية للوثائق)
التعريف معقد للغاية ، ولكن في الواقع ، كل شيء أبسط بكثير. لدينا نوع من المكونات ، وكل شيء بين علامتي الفتح والإغلاق هو محتوى.

<app-parent>
    <!-- content -->
    I'm content!
    <!-- content -->
</app-parent>

ويتيح لك Angular تضمين أي رمز HTML (محتوى) في قالب هذا المكون باستخدام عنصر ng-content.

دعونا نحاول معرفة سبب الحاجة لذلك وكيف يعمل مع المثال. لنفترض أن لدينا مكون زر بسيط. نص هذا الزر نمر إلى القالب من خلال input property.

// button.component.ts
import {Component, Input} from '@angular/core';

@Component({
  selector: 'app-button',
  template: '<button>{{text}}</button>'
})
export class ButtonComponent {
  @Input() text: string;
}

// app.component.ts
import {Component} from '@angular/core';

@Component({
  selector: 'app-root',
  template: `<app-button [text]="'Button'"></app-button>`,
})
export class AppComponent {
}

يبدو أنها تبدو جيدة. ولكن فجأة احتجنا إلى إضافة رمز إلى النص لبعض الأزرار. لدينا بالفعل مكون رمز. تحتاج فقط إلى إضافته إلى قالب الزر ، وإرفاق توجيه ngIfوكتابة أمر آخر input propertyللعرض الديناميكي للرمز.

// icon.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-icon',
  template: '☻',
})
export class IconComponent {
}

// button.component.ts
import {Component, Input} from '@angular/core';

@Component({
  selector: 'app-button',
  template: `<button>
               <app-icon *ngIf="showIcon"></app-icon>
               {{text}}
             </button>`,
})
export class ButtonComponent {
  @Input() text: string;
  @Input() showIcon = true;
}

كل شيء يعمل. ولكن ماذا يحدث إذا كنت بحاجة إلى تغيير موقع الرمز نسبة إلى النص؟ أو إضافة عنصر جديد آخر؟ يجب عليك تعديل التعليمات البرمجية الموجودة وإضافة خصائص جديدة وما إلى ذلك.

كل هذا يمكن تجنبه مع ng-content. يمكن اعتباره عنصرًا نائبًا للمحتوى. يعرض كل شيء تضعه بين علامتي الفتح والإغلاق للمكون.

// button.component.ts
import {Component} from '@angular/core';

@Component({
  selector: 'app-button',
  template: `<button>
               <ng-content></ng-content>
             </button>`,
})
export class ButtonComponent {
}

// app.component.ts
import {Component} from '@angular/core';

@Component({
  selector: 'app-root',
  template: `<app-button>
               <app-icon></app-icon>
               Button
             </app-button>`,
})
export class AppComponent {
}

كود Stackblitz

الآن ، إذا كنا بحاجة إلى زر رمز ، فإننا ببساطة نضع مكون الرمز بين علامات الزر. يمكنك إضافة أي شيء وأي شيء. أليست هذه الجنة؟ أصبح مكون الزر لدينا مرنًا وجميلًا.

ما الدور الذي تلعبه السمة select للمحتوى ng؟


في بعض الأحيان نحتاج إلى ترتيب بعض المحتوى في مكان معين بالنسبة لبقية المحتوى ، وفي هذه الحالة يمكننا استخدام سمة selectتقبل المحدد ( .some-class, some-tag, [some-attr]).

// button.component.ts
import {Component} from '@angular/core';

@Component({
  selector: 'app-button',
  template: `<button>
               <ng-content></ng-content>
               <div>
                 <ng-content select="app-icon"></ng-content>
               </div>
             </button>`,
})
export class ButtonComponent {
}

كود Stackblitz

الآن يظهر الرمز دائمًا أدناه ، بغض النظر عن بقية المحتوى. بيرفكتو!

ما هو ngProjectAs؟


تتوافق السمة selecty ng-contentمع العلامات الموجودة في المستوى الأول من تداخل المكون الأصلي. ولكن ماذا يحدث إذا قمنا بزيادة مستوى التداخل لمكون الأيقونة من خلال لفه في علامة؟

// app.component.ts
import {Component} from '@angular/core';

@Component({
  selector: 'app-root',
  template: `<app-button>
               <ng-container>
                 <app-icon></app-icon>
               </ng-container>
               Button
             </app-button>`
})
export class AppComponent {}

سنرى أنه selectلا يعمل ، كما لو أنه غير موجود على الإطلاق. يحدث هذا لأنه <ng-content select="...">يبحث فقط في المستوى الأول من تداخل محتوى الوالدين. هناك صفة لحل هذه المشكلة ngProjectAs. يأخذ في محدد و "أقنعة" عقدة DOM بأكملها تحتها.

// app.component.ts
import {Component} from '@angular/core';

@Component({
  selector: 'app-root',
  template: `<app-button>
               <ng-container ngProjectAs="app-icon">
                 <app-icon></app-icon>
               </ng-container>
               Button
             </app-button>`
})
export class AppComponent {}

كود Stackblitz

Case * ngIf + ng-content


دعونا نفحص حالة أخرى مثيرة للاهتمام. لنفترض أننا بحاجة إلى النقر على زر إخفاء / إظهار أيقونة. أضف خاصية منطقية إلى فئة مكونات الزر المسؤولة عن عرض الرمز ، وقم بتغييره بالنقر فوق الزر وتعليقه ngIf.

// button.component.ts
import {Component, Input} from '@angular/core';

@Component({
  selector: 'app-button',
  template: `<button (click)="toggleIcon()">
               <ng-content></ng-content>
               <div *ngIf="showIcon">
                 <ng-content select="app-icon"></ng-content>
               </div>
             </button>`,
})
export class ButtonComponent {
  showIcon = true;

  toggleIcon() {
    this.showIcon = !this.showIcon;
  }
}

الرمز مخفي / يظهر عن طريق النقر. غرامة! ولكن دعونا إضافة بعض السجلات لالسنانير OnInitو OnDestroyللمكون رمز. من المعروف جيدًا أن التوجيه ، ngIfعندما يتم تغيير الحالة ، يزيل / ينشئ عنصرًا بالكامل ، و OnDestroy/ OnInitيجب أن يعمل وفقًا لذلك في كل مرة.

// icon.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';

@Component({
  selector: 'app-icon',
  template: '☻',
})
export class IconComponent implements OnInit, OnDestroy {
  ngOnInit() {
    console.log('app-icon init');
  }

  ngOnDestroy() {
    console.log('app-icon destroy')
  }
}

الرمز على Stackblitz

بضع مرات نضغط على الزر ، وتأكد من أن الرمز يختفي ، ثم يظهر. ثم نذهب إلى وحدة تحكم المطور على أمل رؤية سجلاتنا المرغوبة ، ومع ذلك ... فهي ليست كذلك!

يوجد سجل واحد فقط لإنشاء مكون. اتضح أن مكون الرمز الخاص بنا لا يتم حذفه أبدًا ، ولكنه ببساطة مخفي. لماذا يحدث هذا؟

ng-content لا إنشاء محتوى جديد، فإنه ببساطة المشاريع القائمة. لذلك ، فإن المكون الذي يتم التصريح عن المحتوى هو المسؤول عن إنشاء وحذف. بالنسبة لي كانت لحظة غير واضحة على الإطلاق. دعنا نصلح حلنا بحيث يعمل كما هو متوقع في البداية.

// button.component.ts
import {Component} from '@angular/core';

@Component({
  selector: 'app-button',
  template: `<button>
                <ng-content></ng-content>
                <ng-content select="app-icon"></ng-content>
             </button>`,
})
export class ButtonComponent {
}

// app.component.ts
import { Component } from "@angular/core";

@Component({
  selector: 'app-root',
  template: `<app-button (click)="toggleIcon()">
              <div *ngIf="showIcon" ngProjectAs="app-icon">
                <app-icon></app-icon>
              </div>
              Button
            </app-button>`,
})
export class AppComponent {
  showIcon = true;

  toggleIcon() {
    this.showIcon = !this.showIcon;
  }
}

الرمز على Stackblitz

بعد فتح السجلات ، يمكننا أن نرى أنه تم إنشاء وحذف مكون الرمز كما يجب.

بدلا من الاستنتاج


آمل أن تكون هذه المقالة قد ساعدتك قليلاً في عرض المحتوى في Angular.
من غير المفهوم تماما لي لماذا تجاهلت الوثائق الرسمية هذا الموضوع. مستودع الزاوي حتى يخيم على قضية على هذا منذ عام 2017. يبدو أن لدى فريق Angular أشياء أكثر أهمية للقيام بها.

All Articles