مؤلفه های زاویه ای: فرم های ترکیب محور بر وراثت

مؤلفه های زاویه ای: فرم های ترکیب محور بر وراثت
در برنامه نویسی شی گرا (OOP) ، ترکیب (HAS-A) یک مفهوم کلیدی است ، در کنار وراثت (IS-A)بشر
در زاویه ای ، ما اغلب هنگام تزریق خدمات به مؤلفه هایی که درخواست های API ، حافظه پنهان و غیره را اداره می کنیم ، به طور طبیعی از ترکیب استفاده می کنیم.
با این حال ، وراثت – گسترش a BaseComponent
– همچنین در زاویه ای رایج است. یکی از اصلی ترین نقاط قوت وراثت ، رفتار مشترک است.
در بسیاری از موارد خوب کار می کند ، اما گاهی اوقات یک مشکل ظریف ایجاد می کند:
هنگامی که مؤلفه پایه شروع به نگه داشتن منطق مربوط به فرم می کند ، هر مؤلفه کودک (حتی آنهایی که فقط داده ها را نمایش می دهند ، ایجاد نمی کنند یا به روز می کنند) را مجبور می کند تا منطق را که ممکن است به آنها احتیاج نداشته باشد ، به ارث ببرد. این می تواند به اجزای محکم و محکم منجر شود.
ساده و حداقل مؤلفه پایه یک روش OOP معتبر است.
به عنوان مثال ، الف BaseComponent
ممکن است اینگونه به نظر برسد:
// src/app/shared/component/base.component.ts
@Component({
selector: '',
template: '',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BaseComponent {
private errorMsg: WritableSignal<string> = signal('');
private statusMsg: WritableSignal<string> = signal('');
private ready: WritableSignal<boolean> = signal(false);
protected componentReady(): void {
this.ready.set(true);
}
protected resetErrorMessage(): void {
this.errorMsg.set('');
}
protected resetStatusMessage(): void {
this.statusMsg.set('');
}
protected updateStatusMessage(message: string = ''): void {
this.statusMsg.set(message);
}
protected updateErrorMessage(message: string = ''): void {
this.errorMsg.set(message);
}
public get isComponentReady(): boolean {
return this.ready();
}
public get errorMessage(): string {
return this.errorMsg();
}
public get statusMessage(): string {
return this.statusMsg();
}
}
استفاده مجدد از منطق از طریق وراثت مانند این کاملاً خوب است – وقتی با دقت انجام شد.
با این حال ، هنگامی که صحبت از اشکال می شود ، یک رویکرد پاک کننده و مقیاس پذیر تر این است که به جای وراثت آن ، عملکردی را در داخل اجزای ایجاد کنید.
در این راهنما خواهیم دید که چگونه:
- یک مدل دامنه ایجاد کنید (
ChatRoom
) - یک فرم قابل استفاده مجدد بسازید (
ChatRoomForm
) - رابط مؤلفه را تعریف کنید (
HasForm
شبیهOnInit
) - یک کلاس پایه ایجاد کنید که می تواند توسط کلاسهای مرتبط با فرم گسترش یابد.
بخش 1: ایجاد ChatRoom
بیایید یک مدل دامنه ساده را برای ایجاد یا به روزرسانی اتاق گپ تعریف کنیم.
// src/app/model/domain/chat-room.ts
export class ChatRoom {
public readonly id: number | string;
public readonly title: string;
public readonly description: string;
public readonly tags: string;
public readonly guidelinesOrRules: string;
public readonly visibility: ChatRoomVisibility;
public constructor(data: ChatRoom) {
this.id = data.id ?? 0;
this.title = data.title ?? '';
this.description = data.description ?? '';
this.tags = data.tags ?? '';
this.guidelinesOrRules = data.guidelinesOrRules ?? '';
this.visibility = data.visibility ?? '';
}
public static of(data: ChatRoom): ChatRoom {
return new ChatRoom(data);
}
public static empty(): ChatRoom {
return new ChatRoom({} as ChatRoom);
}
}
بخش 2: CreatingBaseForm
حال ، بیایید فرم پایه را به شرح زیر تعریف کنیم:
// src/app/model/form/base.form.ts
export abstract class BaseForm<T> {
protected formGroup!: FormGroup;
private submitting: WritableSignal<boolean> = signal(false);
private formReady: WritableSignal<boolean> = signal(false);
private formCompleted: WritableSignal<boolean> = signal(false);
protected constructor() {}
protected initForm(): void {}
protected control(name: string): AbstractControl | null | undefined {
return this.form?.get(name);
}
public enableFormComplete(): void {
this.formCompleted.set(true);
}
public disableFormCompleted(): void {
this.formCompleted.set(false);
}
public openForm(): void {
this.formReady.set(true);
}
public startSubmitting(): void {
this.submitting.set(true);
}
public stopSubmitting(): void {
this.submitting.set(false);
}
public get form(): FormGroup {
return this.formGroup;
}
public get value(): T {
return this.form.value as T;
}
public get isFormValid(): boolean {
return this.formGroup.valid;
}
public get isFormCompleted(): boolean {
return this.formCompleted();
}
public get isFormReady(): boolean {
return this.formReady();
}
public get isSubmitting(): boolean {
return this.submitting();
}
public get isNotSubmitting(): boolean {
return !(this.isSubmitting);
}
}
در formGroup
حاوی فرم شما است.
در submitting
سیگنال وضعیت ارسال فرم شما را ردیابی می کند. تا زمان پردازش درخواست می توانید از آن برای جلوگیری از ارسال های متعدد استفاده کنید.
در formReady
سیگنال به شما در نمایش فرم HTML در UI در هنگام اولیه سازی فرم با کنترل و داده های آن کمک می کند.
در formCompleted
سیگنال اختیاری است. این به شما کمک می کند تا پس از اتمام ارسال فرم ، انیمیشن های UI یا انتقال را به عنوان بازخورد نمایش دهید.
در control
روش به شما امکان می دهد بدون تکرار نحو ، کنترل فرم را از یک گروه فرم بازیابی کنید. به عنوان مثال:
public get title(): AbstractControl | null | undefined {
return this.control('title');
}
public get description(): AbstractControl | null | undefined {
return this.control('description');
}
در مقایسه با
public get title(): AbstractControl | null | undefined {
return this.formGroup?.get('title');
}
public get description(): AbstractControl | null | undefined {
return this.formGroup?.get('description');
}
نام متغیر فرم ممکن است تغییر کند ، اما با استفاده از control
روش یک منبع واحد از حقیقت را فراهم می کند و منطق بازیابی کنترل فرم محصور می شود. هنگامی که تغییری وجود دارد ، فقط این روش را به روز می کنید.
در getter
و روش های دیگر راهی است محاصره کننده منطق و رفتار فرم و همچنین به شما امکان دسترسی به وضعیت و داده های آن را می دهد.
بخش 3: CreatingChatRoomForm
اکنون ، ما ایجاد خواهیم کرد ChatRoomForm
// src/app/model/form/chat-room.form.ts
export class ChatRoomForm extends BaseForm<CreateChatRoomPayload> {
private constructor(
private formBuilder: FormBuilder,
private chatRoom: ChatRoom) {
super();
}
public get title(): AbstractControl | null | undefined {
return this.control('title');
}
public get description(): AbstractControl | null | undefined {
return this.control('description');
}
public get guidelines(): AbstractControl | null | undefined {
return this.control('guidelinesOrRules');
}
public get tags(): AbstractControl | null | undefined {
return this.control('tags');
}
public get visibility(): AbstractControl | null | undefined {
return this.control('visibility');
}
public get visibilities(): string[] {
return Object.values(ChatRoomVisibility);
}
protected override initForm(): void {
this.formGroup = this.formBuilder.group({
title: [this.chatRoom.title, [
required,
minLength(10),
maxLength(500),
]],
description: [this.chatRoom.description, [
required,
maxLength(1000),
]],
tags: [this.chatRoom.tags, [
required,
minLength(10),
maxLength(500),
]],
guidelinesOrRules: [this.chatRoom.guidelinesOrRules, [
required,
maxLength(1500),
]],
visibility: [this.chatRoom.visibility, [
required,
oneOf(ChatRoomVisibility)
]],
});
}
public static of(formBuilder: FormBuilder, chatRoom: ChatRoom): ChatRoomForm {
const chatRoomForm: ChatRoomForm = new ChatRoomForm(formBuilder, chatRoom);
chatRoomForm.initForm();
return chatRoomForm;
}
public static empty(formBuilder: FormBuilder): ChatRoomForm {
return new ChatRoomForm(formBuilder, ChatRoom.empty());
}
}
در ChatRoomForm
از آنجا که در تزریق وابستگی استفاده نمی شود ، یک سازنده خصوصی دارد. در عوض ، از طریق static of
روش ، به دنبال الگوی کارخانه.
با استفاده از اعتبار سنجی از این طریق ، تکرار کلاس اعتبار سنج ها را کاهش می دهد و از پیاده سازی های چند شکل پشتیبانی می کند. می توانید اعتبار سنجی های سفارشی را معرفی کنید و بدون به روزرسانی کل پایگاه کد ، بلافاصله اعمال می شود. به عنوان مثال ، می توانید اعتبار سنجی مانند این را تعریف کنید:
// src/app/shared/validators/index.ts
export const required = Validators.required;
export const minLength = Validators.minLength;
export const maxLength = Validators.maxLength;
در مورد نحوه سازماندهی اعتبار سنج ها در یک برنامه زاویه ای بیشتر بدانید.
بخش 4: ایجاد HasForm
رابط
در HasForm
رابط ، برای مؤلفه هایی که به یک فرم پر از کاربر نیاز دارند ، به نظر می رسد:
// src/app/model/interface/form/has-form.interface.ts
export interface HasForm {
formReady(): void;
initForm(data?: any): void;
startSubmitting(): void;
stopSubmitting(): void;
completeForm(): void;
get formModel(): BaseForm<any>;
get payload(): any;
get isFormReady(): boolean;
get isFormCompleted(): boolean;
get isFormValid(): boolean
get isNotSubmitting(): boolean;
get isSubmitting(): boolean;
}
این روشهای دریافت کننده رفتار و منطق فرم واقعی را محاصره می کنند و در UI و بخشی از مؤلفه شما استفاده می شوند.
این رفتارها معمولاً درBaseComponent
قبل و سپس با گسترش مؤلفه ها به ارث رسیده است. این همیشه ایده آل نیست به عنوان مؤلفه هایی مانند ChatRoomItem
وت ChatRoomList
به طور معمول داده ها را بدون جهش یا به روزرسانی آن نمایش دهید.
شما می توانید این رابط را برای کار با الگوهای طراحی و معماری مؤلفه موجود خود سازگار کنید.
در CreateChatRoom
مؤلفه به نظر می رسد
@Component({
selector: 'app-create-chat-room',
imports: [
ValidationErrorComponent,
ReactiveFormsModule,
FaIconComponent,
],
providers: [
ChatRoomService,
],
templateUrl: './create-chat-room.html',
styleUrl: './create-chat-room.css'
})
export class CreateChatRoom extends BaseComponent implements OnInit, HasForm {
protected readonly chatRoomService: ChatRoomService = inject(ChatRoomService);
protected readonly formBuilder: FormBuilder = inject(FormBuilder);
protected readonly chatRoomForm: WritableSignal<ChatRoomForm> = signal(ChatRoomForm.empty(this.formBuilder));
public ngOnInit(): void {
this.initForm();
}
public formReady(): void {
this.formModel.openForm();
this.componentReady();
}
public completeForm(): void {
this.formModel.completeForm();
}
public initForm(): void {
const createChatRoomForm: ChatRoomForm = ChatRoomForm.of(this.formBuilder, ChatRoom.empty());
this.chatRoomForm.set(createChatRoomForm);
this.formReady();
}
public createChatRoom(): void {
if (this.isFormValid && this.isNotSubmitting) {
this.startSubmitting();
const payload: CreateChatRoomPayload = this.payload;
this.chatRoomService.create(payload)
.subscribe({
next: (response: CreateChatRoomResponse): void => { this.createChatRoomSuccess(response); },
error: (error: ErrorResponse): void => { this.createChatRoomFailure(error); },
complete: (): void => { this.createChatRoomComplete(); }
});
}
}
protected createChatRoomSuccess(result: CreateChatRoomResponse): void {
this.updateStatusMessage(result.message);
}
protected createChatRoomFailure(error: ErrorResponse): void {
this.updateErrorMessage(error.message);
this.createChatRoomComplete();
}
protected createChatRoomComplete(): void {
this.stopSubmitting();
this.completeForm();
}
public startSubmitting(): void {
this.formModel.startSubmitting();
}
public stopSubmitting(): void {
this.formModel.stopSubmitting();
}
public get createChatRoomForm(): FormGroup {
return this.formModel.form;
}
public get payload(): CreateChatRoomPayload {
return this.formModel.value;
}
public get formModel(): ChatRoomForm {
return this.chatRoomForm();
}
public get isFormReady(): boolean {
return this.formModel.isFormReady;
}
public get isFormValid(): boolean {
return this.formModel.isFormValid;
}
public get isFormCompleted(): boolean {
return this.formModel.isFormCompleted;
}
public get isSubmitting(): boolean {
return this.formModel.isSubmitting;
}
public get isNotSubmitting(): boolean {
return this.formModel.isNotSubmitting;
}
protected readonly faCheck = faCheck;
protected readonly faSpinner = faSpinner;
protected readonly faPlus = faPlus;
}
در signal(ChatRoomForm.empty())
سیگنال را با فرم شروع کنید اما وضعیت آن آماده نخواهد شد ، UI و کد با بررسی ISFormReady در الگوی HTML محافظت و محافظت می شود.
@if (isFormReady) {
}
در initForm
روش جدید ایجاد می کند ChatRoomForm
با استفاده از استاتیک روش ، فرم را با فرم باز کنید و آماده شوید تا با آن تعامل کنید و سپس آن را برگردانید.
در openForm
روش تنظیم می کند formReady
حالت سیگنال به true
که نشان می دهد اولیه سازی کامل است و فرم آماده است تا توسط کاربر با یا تکمیل شود.
در payload
روش Getter مقدار فرم مورد استفاده خود را برای ایجاد اتاق چت برمی گرداند. آن را type
به نظر می رسد
export type CreateChatRoomPayload = {
title: string;
description: string;
guidelinesOrRules: string;
tags: string;
visibility: ChatRoomVisibility | string;
}
روش Payload Getter به شما امکان می دهد قبل از استفاده از داده ها ، از پیش پردازش یا اضافه کردن جزئیات دیگر استفاده کنید. به عنوان مثال
public get payload(): CreateLiveStreamPayload {
const data: CreateLiveStreamPayload = this.formModel.value;
return {
...data,
startDateTime: addSecondsToDate(this.formModel.startDateTimeValue),
endDateTime: addSecondsToDate(this.formModel.endDateTimeValue),
};
}
بخش 5: الگوی HTML
@if (isComponentReady && isFormReady) {
}
شما از روشهای دریافت کننده برای تقویت UI و منطق شکل استفاده می کنید.
علاوه بر این:
چه در مورد کامل ()؟
پیش از این ، من ذکر کردم که چگونه formCompleted
انیمیشن های UI به سیگنال کمک می کند. اجرای به شرح زیر است:
protected formCompleted: WritableSignal<boolean> = signal(false);
اجرای در شکل پایه چیزی شبیه به این خواهد بود:
public completeForm(delayToReset: number = 5000): void {
// Ensure delay is non-negative
const safeDelay: number = Math.max(0, delayToReset);
// Mark the form as completed
this.enableFormComplete();
// Set a timer to reset form completion status
timer(safeDelay).pipe(
// Reset form completion status
tap(() => this.disableFormCompleted()),
).subscribe();
}
پس از اتمام فرم کاربر و completeForm()
نامیده می شود ، انیمیشن جذاب قلم همانطور که در اینجا نشان داده شده است شروع می شود
@if (isFormCompleted) {<fa-icon [icon]="faCheck"/>}
پس از تأخیر پیش فرض 5 ثانیه ، وضعیت دوباره تنظیم می شود false
و بررسی یا علامت گذاری نماد ناپدید می شودبشر
توجه:
البته ، روش ها در HasForm
قبلاً در formModel
یا BaseForm
بشر این قابل بحث است که آیا این امر باعث افزایش بیش از حد است. شما می توانید تصمیم بگیرید که تمام روش های موجود در Hasform را پیاده سازی کنید یا می توانید برخی از آنها را حذف کرده و به آن دسترسی پیدا کنید formModel
در جزء کد یا مستقیماً در الگوی HTML.
مخزن: ترکیب فرم زاویه ای
استفاده از ترکیب منجر به جداسازی بهتر نگرانی ها ، آزمایش آسانتر و اجزای قابل استفاده مجدد می شود. امیدوارم از آن لذت ببرید