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

مؤلفه های زاویه ای: فرم های ترکیب محور بر وراثت
در برنامه نویسی شی گرا (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.
مخزن: ترکیب فرم زاویه ای
استفاده از ترکیب منجر به جداسازی بهتر نگرانی ها ، آزمایش آسانتر و اجزای قابل استفاده مجدد می شود. امیدوارم از آن لذت ببرید



