ابرقدرت ها با دستورالعمل ها و تزریق وابستگی: قسمت 2

عکس روی جلد اصلی توسط Markus Spiske در Unsplash.
در پست قبلی نگاهی انداختیم به اینکه چگونه میتوانیم از تزریق وابستگی + دستورالعملها برای سادهسازی قالبهایمان استفاده کنیم. و دستیابی به قابلیت استفاده مجدد در این مقاله، ما قصد داریم دستورالعملهای ساختاری را بررسی کنیم و اینکه چگونه میتوانیم اجزاء و دستورالعملها را با یکدیگر هماهنگ کنیم و به هم ریختگی را در خود کاهش دهیم. .html
فایل های حتی بیشتر.
بیایید یک کامپوننت لودر بسازیم!
مورد استفاده
سناریوی زیر را تصور کنید: ما مؤلفهای داریم که برخی از دادهها را بارگیری میکند، و میخواهیم نشانگر بارگیری را در حین واکشی دادهها نشان دهیم. معیارهای زیر باید رعایت شود:
- ما باید بتوانیم هر قالبی را داخل مولفه لودر خود بپیچانیم و در صورت نیاز یک اسپینر نمایش می دهد
- کامپوننت باید یک ویژگی ورودی دریافت کند که نشان دهد داده در حال بارگذاری است یا خیر
- الگو باید توسط یک پوشش پوشانده شود، به طوری که کاربر نتواند در هنگام بارگیری داده ها تعامل داشته باشد (و احتمالاً تماس های HTTP دیگر را راه اندازی کند).
در اینجا یک پیاده سازی بسیار ساده است:
@Component({
selector: 'app-loader',
template: `
<div class="loading-container">
<ng-content/>
<div *ngIf="loading" class="blocker">
<p-progressSpinner/>
</div>
</div>`,
standalone: true,
styles: [
`
.loading-container {
position: relative;
}
.blocker {
background-color: black;
position: absolute;
top: 0;
z-index: 9999;
width: 100%;
height: 100%;
opacity: 0.4;
}
`,
],
imports: [NgIf, ProgressSpinnerModule],
})
export class LoaderComponent {
@Input() loading = false;
}
توجه داشته باشید: من از PrimeNG برای مثال های این مقاله استفاده می کنم، اما شما به راحتی می توانید آنها را با هر پیاده سازی دیگری دوباره استفاده کنید
بنابراین، در اینجا ما فقط هر محتوایی را که دریافت می کنیم را در آن طرح می کنیم ng-content
و بقیه چند CSS ساده + PrimeNG هستند ProgressSpinner
جزء. را loading
ویژگی ورودی برای روشن و خاموش کردن اسپینر استفاده می شود.
اکنون می توانیم از آن در قالب به صورت زیر استفاده کنیم:
<app-loader [loading]="loading">
<p>Some content</p>
</app-loader>
صبر کنید، من فکر کردم این مقاله در مورد دستورالعمل است؟
خوب، خبر خوب: آن است است در مورد بخشنامه ها اما مشکل کامپوننت چیست؟ خوب، مثالی از استفاده از آن که دیدیم کاملاً خوش بینانه بود: سناریوهای زندگی واقعی معمولاً به این سادگی نیستند. این قطعه از الگو را در نظر بگیرید:
<app-loader [loading]="loading">
<div class="p-grid">
<div class="p-col-12">
<p>Some content</p>
</div>
<app-loader [loading]="otherLoading">
<div class="p-col-12">
<p>Some other content</p>
<app-loader [loading]="evenMoreLoading">
<div class="p-col-12">
<p>Even more content</p>
</div>
</app-loader>
</div>
</app-loader>
</div>
</app-loader>
اکنون، در اینجا، زمانی که ما چند عنصر تودرتو داریم، و الگو به طور غیر ضروری رشد میکند، سطوح تورفتگی بیشتر، تگهای بسته شدن بیشتر، و غیره و غیره را اضافه میکند. کاری که من شخصاً واقعاً دوست دارم بتوانم انجام دهم موارد زیر است:
<p *loading="loading">Some content</p>
اما چگونه می توانیم به این امر برسیم؟ خوب، ما به یک دستورالعمل نیاز داریم که کارهای زیر را انجام دهد:
- a را ایجاد می کند
LoaderComponent
نمونه به صورت پویا - به نوعی قالب تودرتو را در آن پروژه می دهد
- آنها را در همگام نگه می دارد – زمانی که
loading
ویژگی های ورودی تغییر می کند، دستورالعمل باید به روز شودLoaderComponent
نمونه بر این اساس - همه چیز را رندر کنید
بیایید در آن شیرجه بزنیم!
دستورالعمل های ساختاری
دستورالعملهای ساختاری واقعاً جالب هستند، زیرا به ما اجازه میدهند از طریق برخی از الگوها ارجاع دهیم TemplateRef
و انواع جادوها را با آن انجام دهید.
همچنین می توانیم از ViewContainerRef
برای ایجاد اجزا به صورت پویا چیزی که برای ما باقی می ماند این است که قالب را در کامپوننت طرح ریزی کنیم، و بله، امکانش وجود دارد! بیایید ساده شروع کنیم:
@Directive({
selector: '[loading]',
standalone: true,
})
export class LoaderDirective {
private readonly templateRef = inject(TemplateRef);
private readonly vcRef = inject(ViewContainerRef);
@Input() loading = false;
templateView: EmbeddedViewRef<any>;
loaderRef: ComponentRef<LoaderComponent>;
}
در اینجا ما چیزهایی را که نیاز داریم تزریق کردیم (TemplateRef
و ViewContainerRef
) افزود loading
ورودی، به طور طبیعی، و ایجاد دو ویژگی: templateView
و loaderRef
. اولین مورد برای ذخیره ارجاع به قالبی که دریافت می کنیم استفاده می شود و مورد دوم برای ذخیره ارجاع به الگو استفاده می شود. ComponentRef
نمونه ای که می خواهیم ایجاد کنیم – باید هر دو را ذخیره کنیم.
در مرحله بعد، به سمت چپ، مقداری وزنه برداری اولیه را انجام دهید تا کل کار را تنظیم کنید:
@Directive({
selector: '[loading]',
standalone: true,
})
export class LoaderDirective implements OnInit {
private readonly templateRef = inject(TemplateRef);
private readonly vcRef = inject(ViewContainerRef);
@Input() loading = false;
templateView: EmbeddedViewRef<any>;
loaderRef: ComponentRef<LoaderComponent>;
ngOnInit() {
this.templateView = this.templateRef.createEmbeddedView({});
this.loaderRef = this.vcRef.createComponent(LoaderComponent, {
injector: this.vcRef.injector,
projectableNodes: [this.templateView.rootNodes],
});
this.loaderRef.setInput('loading', this.loading);
}
}
اینجا، ما ngOnInit
روش چرخه حیات چهار کار را انجام می دهد:
- قالب را به یک نمای تعبیه شده تبدیل کنید تا بتوانیم آن را به صورت پویا رندر کنیم
- ایجاد یک
LoaderComponent
نمونه، مثال - قالب را در
LoaderComponent
نمونه از طریقprojectableNodes
– اینجاست که جادو اتفاق می افتد! - را تنظیم کنید
loading
ویژگی ورودی درLoaderComponent
نمونه، مثال
حالا، به این ترتیب به نوعی کار خواهد کرد، اما ما به دو چیز دیگر نیاز داریم تا آن را به درستی کار کنیم:
- ما باید به روز رسانی کنیم
LoaderComponent
به عنوان مثال زمانی کهloading
ویژگی ورودی تغییر می کند - ما باید اطمینان حاصل کنیم که تشخیص تغییر همچنان روی الگوی پیشبینیشده کار میکند، علیرغم اینکه از نمای والد جدا شده و به یک جزء جدید نمایش داده میشود. ما استفاده خواهیم کرد
ngDoCheck
برای این
بیایید اجرای را نهایی کنیم:
@Directive({
selector: '[loading]',
standalone: true,
})
export class LoaderDirective implements OnInit, DoCheck, OnChanges {
private readonly templateRef = inject(TemplateRef);
private readonly vcRef = inject(ViewContainerRef);
@Input() loading = false;
templateView: EmbeddedViewRef<any>;
loaderRef: ComponentRef<LoaderComponent>;
ngOnInit() {
this.templateView = this.templateRef.createEmbeddedView({});
this.loaderRef = this.vcRef.createComponent(LoaderComponent, {
injector: this.vcRef.injector,
projectableNodes: [this.templateView.rootNodes],
});
this.loaderRef.setInput('loading', this.loading);
}
ngOnChanges() {
this.loaderRef?.setInput('loading', this.loading);
}
ngDoCheck() {
this.templateView?.detectChanges();
}
}
این اضافات نسبتاً ساده هستند: وقتی که loading
ویژگی کامپوننت تغییر می کند، ما آن را به روز می کنیم LoaderComponent
بر این اساس، و هنگامی که تشخیص تغییر برای نمونه دستورالعمل اجرا می شود، ما همچنین به الگوی فرزند در ngDoCheck
روش چرخه حیات از طریق templateView.detectChanges()
. اگر با نحوه آن آشنا نیستید ngDoCheck
کار می کند یا چرا از آن استفاده می شود، می توانید اسناد رسمی یا این آموزش را بخوانید.
اکنون می توانیم به سادگی از آن در قالب استفاده کنیم، حتی زمانی که چندین عنصر تودرتو داریم:
<p *loading="loading">
Some content
<span *loading="otherLoading">
Some other content
</span>
<p *loading="evenMoreLoading">
Even more content
</p>
</p>
و بنابراین، هیچ الگوی تودرتو، هیچ تورفتگی غیر ضروری، و هیچ برچسب بستن غیر ضروری وجود ندارد. این فقط یک دستورالعمل ساده است که کار را انجام می دهد.
می توانید نمونه کامل را با یک دمو زنده در StackBlitz مشاهده کنید:
نتیجه
همانطور که قبلا ذکر شد، من معتقدم دستورالعمل ها بسیار، خیلی قدرتمند، اما متأسفانه در جامعه گسترده تر استفاده نشده است. با این سری از مقالهها، میخواهم موارد استفاده مختلف را بررسی کنیم که در آن دستورالعملها به ما کمک میکنند الگوهایمان را ساده کنیم و خوانایی را بهبود ببخشیم. در قسمت بعدی، استفاده از دستورالعملها برای هک کردن اجزای موجود را بررسی خواهیم کرد. گوش به زنگ باشید!