Add/Create Blog Post āĻĢāĻŋāĻāĻžāϰāĻāĻŋ āϤā§āϰāĻŋ āĻāϰāĻžāϰ āϏāĻŽāϝāĻŧ āĻŽā§āϞ āĻā§āϝāĻžāϞā§āĻā§āĻ āĻšāϞ⧠āύāϤā§āύ āĻĄāĻžāĻāĻž āϏā§āĻ āĻāϰāĻžāϰ āĻĒāĻžāĻļāĻžāĻĒāĻžāĻļāĻŋ Many-to-Many Relationship (āĻā§āϝāĻžāĻāĻžāĻāϰāĻŋāĻā§āϞā§) āϏāĻ āĻŋāĻāĻāĻžāĻŦā§ āĻĄāĻžāĻāĻžāĻŦā§āϏ⧠āĻāύāϏāĻžāϰā§āĻ āĻāϰāĻžāĨ¤
āύāĻŋāĻā§ āĻāĻĒāύāĻžāϰ āĻĒā§āϰāĻā§āĻā§āĻā§āϰ āĻāϞā§āĻā§ Create Blog Post-āĻāϰ āĻāĻāĻāĻŋ āĻāĻŽāĻĒā§āϞāĻŋāĻ “Master Guide” āĻĻā§āĻāϝāĻŧāĻž āĻšāϞā§āĨ¤
ā§§. āĻŦā§āϝāĻžāĻāĻāύā§āĻĄ (ASP.NET Core / EF Core) āϞāĻāĻŋāĻ
āĻā§āϰāĻŋāϝāĻŧā§āĻ āĻāϰāĻžāϰ āϏāĻŽāϝāĻŧ āĻāĻŽāĻžāĻĻā§āϰ āĻāĻžāĻ āĻšāϞ⧠āĻĢā§āϰāύā§āĻāĻāύā§āĻĄ āĻĨā§āĻā§ āĻāϏāĻž Guid āĻāĻāĻĄāĻŋāϰ āϞāĻŋāϏā§āĻ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻāϰ⧠āĻāĻžāĻāĻļāύ āĻā§āĻŦāĻŋāϞ (BlogPostCategory) āϏāĻš āĻŽā§āĻāύ āĻ
āĻŦāĻā§āĻā§āĻāĻāĻŋ āϏā§āĻ āĻāϰāĻžāĨ¤
C#
public async Task<BlogPost> CreateAsync(BlogPost blogPost)
{
// ā§§. āĻŽā§āĻāύ BlogPost āĻ
āĻŦāĻā§āĻā§āĻāĻāĻŋ āĻĄāĻžāĻāĻžāĻŦā§āϏ āĻā§āϰā§āϝāĻžāĻāĻŋāĻāϝāĻŧā§ āĻ
ā§āϝāĻžāĻĄ āĻāϰāĻž
await dbContext.BlogPosts.AddAsync(blogPost);
// ⧍. āϏā§āĻ āĻā§āĻā§āĻā§āϏ āĻāϞ āĻāϰāĻž (āĻāĻāĻŋ āĻāĻžāĻāĻļāύ āĻā§āĻŦāĻŋāϞāϏāĻš āϏāĻŦ āĻĄāĻžāĻāĻž āĻāύāϏāĻžāϰā§āĻ āĻāϰāĻŦā§)
await dbContext.SaveChangesAsync();
return blogPost;
}
āύā§āĻ: āĻāĻĒāύāĻžāϰ
BlogPostāĻŽāĻĄā§āϞā§āϰ āĻā§āϤāϰ⧠āϝāĻĻāĻŋpublic ICollection<Category> Categories { get; set; }āĻĨāĻžāĻā§, āϤāĻŦā§ EF Core āĻ āĻā§āĻŽā§āĻāĻŋāĻ āĻĄāĻžāĻāĻž āĻāύāϏāĻžāϰā§āĻ āĻāϰ⧠āĻĻā§āĻŦā§āĨ¤
⧍. āĻĢā§āϰāύā§āĻāĻāύā§āĻĄ (Angular) – āĻĢāϰā§āĻŽ āĻ āĻĄāĻžāĻāĻž āĻšā§āϝāĻžāύā§āĻĄā§āϞāĻŋāĻ
āύāϤā§āύ āĻĒā§āϏā§āĻ āĻā§āϰāĻŋāϝāĻŧā§āĻ āĻāϰāĻžāϰ āϏāĻŽāϝāĻŧ āĻāĻŽāĻžāĻĻā§āϰ āĻāĻāĻāĻŋ āĻĢāĻžāĻāĻāĻž āĻĢāϰā§āĻŽ āϞāĻžāĻāĻŦā§ āĻāĻŦāĻ āĻāĻāĻāĻžāϰ āϝ⧠āĻā§āϝāĻžāĻāĻžāĻāϰāĻŋāĻā§āϞ⧠āϏāĻŋāϞā§āĻā§āĻ āĻāϰāĻŦā§ āϏā§āĻā§āϞ⧠āĻāĻāĻāĻŋ āĻ ā§āϝāĻžāϰā§āϤ⧠āĻāĻŽāĻž āĻāϰāϤ⧠āĻšāĻŦā§āĨ¤
āĻŽā§āϞ āĻā§āϞāϏ: ReactiveFormsModule, signal(), push(), āĻāĻŦāĻ filter()āĨ¤
TypeScript
// ā§§. āϏāĻŋāϞā§āĻā§āĻ āĻāϰāĻž āĻāĻāĻĄāĻŋāĻā§āϞ⧠āϰāĻžāĻāĻžāϰ āĻāύā§āϝ āĻ
ā§āϝāĻžāϰā§
selectedCategoryIds: string[] = [];
isSubmitting = signal(false);
// ⧍. āϰāĻŋāĻ
ā§āϝāĻžāĻā§āĻāĻŋāĻ āĻĢāϰā§āĻŽ āϏā§āĻāĻāĻĒ
addBlogPostForm = new FormGroup({
title: new FormControl('', Validators.required),
content: new FormControl('', Validators.required),
urlHandle: new FormControl('', Validators.required),
featuredImgUrl: new FormControl('', Validators.required),
author: new FormControl('', Validators.required),
publishedDate: new FormControl('', Validators.required),
isVisible: new FormControl(true)
});
// ā§Š. āϏāĻžāĻŦāĻŽāĻŋāĻ āĻŽā§āĻĨāĻĄ
onSubmit() {
if (this.addBlogPostForm.valid) {
this.isSubmitting.set(true);
const request = {
...this.addBlogPostForm.value,
// āĻāĻŽāĻžāĻĻā§āϰ āϏāĻŋāϞā§āĻā§āĻ āĻāϰāĻž āĻā§āϝāĻžāĻāĻžāĻāϰāĻŋāĻā§āϞ⧠āϰāĻŋāĻā§āϝāĻŧā§āϏā§āĻā§ āϝā§āĻ āĻāϰāĻž
categories: this.selectedCategoryIds
};
this.blogPostService.createBlogPost(request).subscribe({
next: (response) => {
this.router.navigate(['/admin/blogposts']);
},
error: (err) => this.isSubmitting.set(false)
});
}
}
ā§Š. āĻāĻāĻāĻāĻŋāĻāĻŽāĻāϞ (HTML) – āĻŽāĻžāϞā§āĻāĻŋ-āϏāĻŋāϞā§āĻā§āĻ āĻā§āϝāĻžāĻāĻžāĻāϰāĻŋ
āĻāĻāĻāĻžāϰāĻā§ āϏāĻŦ āĻā§āϝāĻžāĻāĻžāĻāϰāĻŋ āĻĻā§āĻāĻžāĻŦā§ āĻāĻŦāĻ āϏ⧠āϝā§āĻā§āϞ⧠āĻāĻŋāĻ āĻĻā§āĻŦā§ āϏā§āĻā§āϞ⧠āĻāĻŽāĻžāĻĻā§āϰ āϞāĻŋāϏā§āĻā§ āϝā§āĻ āĻšāĻŦā§āĨ¤
HTML
<label class="form-label fw-bold">Select Categories</label>
<div class="d-flex flex-wrap gap-2">
@for (category of categoryList(); track category.id) {
<div class="form-check">
<input class="form-check-input"
type="checkbox"
[id]="category.id"
[value]="category.id"
(change)="onCategoryChange($event)">
<label class="form-check-label" [for]="category.id">
{{ category.name }}
</label>
</div>
}
</div>
ā§Ē. āϰāĻŋāϞā§āĻļāύāĻļāĻŋāĻĒ āĻšā§āϝāĻžāύā§āĻĄā§āϞāĻŋāĻ (onCategoryChange)
āĻāĻāĻāĻžāϰ āϝāĻāύ āĻāĻŋāĻ āĻĻā§āϝāĻŧ āϤāĻāύ push āĻāϰ āĻāĻŋāĻ āϤā§āϞ⧠āĻĻāĻŋāϞ⧠filter āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻāϰ⧠āĻāĻāĻĄāĻŋ āĻŽā§āϝāĻžāύā§āĻ āĻāϰāĻž āĻšāϝāĻŧāĨ¤
TypeScript
onCategoryChange(event: Event) {
const element = event.target as HTMLInputElement;
const categoryId = element.value;
if (element.checked) {
// ā§§. āϞāĻŋāϏā§āĻā§ āύāϤā§āύ āĻāĻāĻĄāĻŋ āϝā§āĻ āĻāϰāĻž
this.selectedCategoryIds.push(categoryId);
} else {
// ⧍. āϞāĻŋāϏā§āĻ āĻĨā§āĻā§ āĻāĻāĻĄāĻŋ āϏāϰāĻŋāϝāĻŧā§ āĻĢā§āϞāĻž (āĻāĻžāĻāĻā§āύāĻŋ)
this.selectedCategoryIds = this.selectedCategoryIds.filter(id => id !== categoryId);
}
}
ā§Ģ. āϏāĻžāĻŽāĻžāϰāĻŋ āĻā§āĻ-āϞāĻŋāϏā§āĻ (āϰāĻŋāĻāĻŋāĻļāύ āύā§āĻ)
| āĻĢāĻŋāĻāĻžāϰ | āĻŦā§āϝāĻŦāĻšā§āϤ āĻŽā§āĻĨāĻĄ/āĻā§āϞāϏ | āĻā§āύ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻāϰāϞāĻžāĻŽ? |
| Form Management | FormGroup | āϏāĻŦ āĻāύāĻĒā§āĻ āĻĄāĻžāĻāĻž āĻāĻāϏāĻžāĻĨā§ āĻā§āϝāĻžāϞāĻŋāĻĄā§āĻļāύāϏāĻš āĻšā§āϝāĻžāύā§āĻĄā§āϞ āĻāϰāϤā§āĨ¤ |
| Data Collection | .push() | āĻāĻāĻāĻžāϰ āϝāĻāύ āĻā§āύ⧠āĻā§āϝāĻžāĻāĻžāĻāϰāĻŋ āϏāĻŋāϞā§āĻā§āĻ āĻāϰ⧠āϤāĻž āϞāĻŋāϏā§āĻā§ āĻāĻŽāĻž āĻāϰāϤā§āĨ¤ |
| Data Removal | .filter() | āĻāĻāĻāĻžāϰ āĻā§āϞ āĻāϰ⧠āϏāĻŋāϞā§āĻā§āĻ āĻāϰāϞ⧠āĻŦāĻž āĻāύ-āĻā§āĻ āĻāϰāϞ⧠āϤāĻž āϞāĻŋāϏā§āĻ āĻĨā§āĻā§ āϏāϰāĻžāϤā§āĨ¤ |
| UI Interaction | (change) | āĻā§āĻāĻŦāĻā§āϏ⧠āĻā§āϞāĻŋāĻ āĻāϰāĻžāϰ āϏāĻžāĻĨā§ āϏāĻžāĻĨā§ āϞāĻāĻŋāĻ āĻā§āϰāĻŋāĻāĻžāϰ āĻāϰāϤā§āĨ¤ |
| Loading State | signal(false) | āĻŦāĻžāĻāύ āϞā§āĻĄāĻŋāĻ āĻāĻŦāĻ āĻĄāĻžāĻŦāϞ āϏāĻžāĻŦāĻŽāĻŋāĻļāύ āĻŦāύā§āϧ āĻāϰāϤā§āĨ¤ |
đĄ āĻāĻā§āϏāĻĒāĻžāϰā§āĻ āĻāĻŋāĻĒāϏ (revise āĻāϰāĻžāϰ āĻāύā§āϝ):
- Create āĻāϰāĻžāϰ āϏāĻŽāϝāĻŧ āĻĢāϰā§āĻŽ āĻĨāĻžāĻā§ Empty, āϤāĻžāĻ āĻāĻāĻžāύā§
patchValueāĻŦāĻžeffectāĻāϰ āĻĒā§āϰāϝāĻŧā§āĻāύ āĻšāϝāĻŧ āύāĻž (āϝāĻĻāĻŋ āύāĻž āĻāĻĒāύāĻŋ āĻā§āύ⧠āĻĄāĻŋāĻĢāϞā§āĻ āĻā§āϝāĻžāϞ⧠āĻāĻžāύ)āĨ¤ - Relationship āĻšā§āϝāĻžāύā§āĻĄā§āϞ āĻāϰāĻžāϰ āϏāĻŽāϝāĻŧ āϏāĻŦāϏāĻŽāϝāĻŧ āĻŽāύ⧠āϰāĻžāĻāĻŦā§āύâ āĻĢā§āϰāύā§āĻāĻāύā§āĻĄ āĻĨā§āĻā§ āĻļā§āϧ⧠ID-āĻāϰ āĻāĻāĻāĻŋ āϞāĻŋāϏā§āĻ āĻĒāĻžāĻ āĻžāϞā§āĻ āĻŦā§āϝāĻžāĻāĻāύā§āĻĄā§ āϰāĻŋāϞā§āĻļāύ āϤā§āϰāĻŋ āĻāϰāĻž āϏāĻŽā§āĻāĻŦāĨ¤
- āϏāĻžāĻŦāĻŽāĻŋāĻ āĻāϰāĻžāϰ āĻāĻā§
this.addBlogPostForm.validāĻā§āĻ āĻāϰāĻž āĻŽāĻžāϏā§āĻ, āϝāĻžāϤ⧠āĻā§āϞ āĻĄāĻžāĻāĻž āϏāĻžāϰā§āĻāĻžāϰ⧠āύāĻž āϝāĻžāϝāĻŧāĨ¤