قبل ثمانية أشهر، قمت بإنشاء دورة Crash Course لـ Angular مدتها 60 دقيقة. كانت تلك الدورة موجهة للأشخاص الذين لديهم معرفة بـ React أو Vue أو أي Front End Framework.
أما هذه الدورة فهي موجهة لمن لديه معرفة أساسية فقط بـ HTML و JavaScript و CSS.
سنحكي عن خصائص كثيرة ستساعدك في بداية مسيرتك البرمجية. وفي النهاية، سنبني مشروع عملي: Task Manager.
في هذا المشروع ستتمكن من:
عندما تعمل بـ JavaScript النقي، تفكر بطريقة Imperative (أمرية). تخبر المتصفح خطوة بخطوة ماذا يجب أن يفعل:
// مثال على JavaScript النقي (Imperative)
const container = document.getElementById('container');
const button = document.createElement('button');
button.textContent = 'اضغط هنا';
container.appendChild(button);
button.addEventListener('click', () => {
console.
اشترك في النشرة البريدية
دروس جديدة، مقالات، وأدوات مباشرة لبريدك.
في هذا النمط، تقوم بـ:
بـ Angular، تستخدم طريقة Declarative (وصفية). تصف ما تريده وتترك Angular تتولى الباقي.
// مثال على Angular (Declarative)
export class AppComponent {
count = 0;
increment() {
this.count++;
}
}<!-- القالب (Template) -->
<h1>{{ count }}</h1>
<button (click)="increment()">زيادة</button>الفرق الرئيسي:
عند بناء تطبيق بـ JavaScript النقي، قد تكون البنية كالتالي:
project/
├── index.html
├── style.css
└── main.js
مع نمو التطبيق، تصبح البنية أكثر تعقيدًا:
project/
├── index.html
├── styles/
│ └── style.css
├── scripts/
│ ├── header.js
│ ├── article.js
│ ├── comments.js
│ └── api.js
في Angular، يتم تنظيم الملفات بطريقة مختلفة:
src/
├── app/
│ ├── components/
│ │ ├── button/
│ │ └── card/
│ ├── services/
│ │ └── api.service.ts
│ ├── features/
│ └── templates/
├── index.html
└── styles.css
العناصر الرئيسية:
| العنصر | الوصف |
|---|---|
components/ | مكونات واجهة المستخدم |
services/ | الخدمات والمنطق المشترك |
features/ | الميزات الرئيسية |
templates/ | ملفات HTML منفصلة |
الخطوة الأولى هي تحميل Node.js من الموقع الرسمي.
نصيحة: استخدم nvm أو fnm لإدارة إصدارات Node.js بسهولة.
بعد التثبيت، افتح Terminal وأدخل:
node --version
npm --versionيجب أن ترى إصدار Node.js و npm:
v22.10.0
10.9.0npm هو مدير الحزم (Package Manager). يسمح لك بتحميل مكتبات وأدوات مختلفة.
فكّر به كمستودع حيث يمكنك البحث عن حزم وتحميلها:
npm search reactقم بتثبيت أداة سطر الأوامر الخاصة بـ Angular بشكل عام:
npm install -g @angular/cliللتحقق من التثبيت:
ng versionأنشئ مشروع جديد:
ng new my-first-appستظهر خيارات - اختر CSS للأسلوب والـ No لـ server-side rendering.
انتقل إلى مجلد المشروع:
cd my-first-appشغّل الخادم:
ng serveافتح المتصفح على: http://localhost:4200
عندما يتم إنشاء المشروع، يحويل Angular كل ملفات TypeScript إلى JavaScript:
ng serveما يحدث خلف الكواليس:
عند حفظ ملف، يحدّث المتصفح نفسه تلقائيًا دون الحاجة لإعادة تشغيل الخادم.
المكون هو جزء من واجهة المستخدم يمكن إعادة استخدامه.
// مثال: مكون زر
import { Component } from '@angular/core';
@Component({
selector: 'app-button',
template: '<button>اضغط هنا</button>',
styles: ['button { padding: 10px; }']
})
export class ButtonComponent {}<app-button></app-button>لعرض البيانات في القالب:
export class AppComponent {
name = 'أحمد';
age = 25;
}<h1>اسمي {{ name }}</h1>
<p>عمري {{ age }} سنة</p>استخدم ngModel لربط البيانات بالاتجاهين:
أولاً، استورد FormsModule:
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-my-component',
templateUrl: './my-component.component.html',
imports: [FormsModule]
})
export class MyComponent {}بعد ذلك، استخدم ngModel:
export class AppComponent {
username = '';
}<input [(ngModel)]="username" />
<p>مرحبًا {{ username }}</p>مجرد أن تكتب في الـ input، سيتحدّث الـ username تلقائيًا.
الإشارة هي متغير تفاعلي. عندما تتغير قيمته، يعرف Angular تلقائيًا أن يحدّث الواجهة.
استورد signal:
import { Component } from '@angular/core';
import { signal } from '@angular/core';
@Component({
selector: 'app-counter',
templateUrl: './counter.component.html'
})
export class CounterComponent {
count = signal(0);
increment() {
this.count.update(value => value + 1);
}
}لقراءة قيمة الإشارة، استدعها كدالة:
<h1>{{ count() }}</h1>
<button (click)="increment()">زيادة</button>set و update| الطريقة | الاستخدام |
|---|---|
set(newValue) | عندما تريد تعيين قيمة جديدة كاملة |
update(fn) | عندما تريد تحديث القيمة بناءً على القيمة السابقة |
// set - تعيين مباشر
this.count.set(10);
// update - تحديث بناءً على القيمة السابقة
this.count.update(value => value + 1);استخدم computed لإنشاء قيم مشتقة من الإشارات:
import { Component } from '@angular/core';
import { signal, computed } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
count = signal(5);
// يتحدّث تلقائيًا عند تغير count
doubled = computed(() => this.count() * 2);
}<p>العد: {{ count() }}</p>
<p>الضعف: {{ doubled() }}</p>عندما تتغير count، تُحسب doubled مرة أخرى تلقائيًا.
الخدمات تحفظ البيانات والمنطق المشترك:
ng generate service services/taskimport { Injectable } from '@angular/core';
import { signal } from '@angular/core';
interface Task {
id: number;
title: string;
completed: boolean;
}
@Injectable({
providedIn: 'root'
})
export class TaskService {
private tasks = signal<Task[]>([
{ id: 1, title: 'تعلم Angular', completed: false },
{ id: 2, title: 'بناء مشروع', completed: false }
]);
getTasks() {
return this.tasks.asReadonly();
}
addTask(title: string) {
this.tasks.update(tasks => [
...tasks,
{ id: Math.random(), title, completed: false }
]);
}
}import { Component } from '@angular/core';
import { inject } from '@angular/core';
import { TaskService } from './services/task.service';
@Component({
selector: 'app-task-list',
templateUrl: './task-list.component.html'
})
export class TaskListComponent {
taskService = inject(TaskService);
tasks = this.taskService.getTasks();
}إنشاء ملف الطرق:
// app.routes.ts
import { Routes } from '@angular/router';
import { HomeComponent } from './pages/home/home.component';
import { AboutComponent } from './pages/about/about.component';
export const appRoutes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'about', component: AboutComponent }
];في المكون الرئيسي، أضف <router-outlet>:
<nav>
<a routerLink="/">الرئيسية</a>
<a routerLink="/about">عن البرنامج</a>
</nav>
<router-outlet></router-outlet>للطرق التي تحتوي على معاملات:
export const appRoutes: Routes = [
{ path: 'task/:id', component: TaskDetailsComponent }
];في المكون:
import { Component, OnInit, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { signal } from '@angular/core';
@Component({
selector: 'app-task-details',
templateUrl: './task-details.component.html'
})
export class TaskDetailsComponent implements OnInit {
private route = inject(ActivatedRoute);
taskId = signal<string | null>(null);
ngOnInit() {
const id = this.route.snapshot.paramMap.get('id');
if (id) {
this.taskId.set(id);
}
}
}أولاً، استورد ReactiveFormsModule:
import { Component } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
@Component({
selector: 'app-contact-form',
templateUrl: './contact-form.component.html',
imports: [ReactiveFormsModule]
})
export class ContactFormComponent {}أنشئ نموذج:
import { Component, inject } from '@angular/core';
import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
@Component({
selector: 'app-contact-form',
templateUrl: './contact-form.component.html',
imports: [ReactiveFormsModule]
})
export class ContactFormComponent {
private fb = inject(FormBuilder);
contactForm = this.fb.group({
name: ['', [Validators.required, Validators.minLength(3)]],
message: ['', [Validators.required, Validators.minLength(10)]]
});
onSubmit() {
if (this.contactForm.valid) {
console.log(this.contactForm.value);
}
}
}<form [formGroup]="contactForm" (ngSubmit)="onSubmit()">
<input formControlName="name" type="text" />
<textarea formControlName="message"></textarea>
<button [disabled]="contactForm.invalid">إرسال</button>
</form>سنبني الآن مشروعًا كاملاً يجمع كل ما تعلمناه. المشروع سيحتوي على:
هيكل المشروع سيكون كالتالي:
src/app/
├── pages/
│ ├── home/
│ ├── task-list/
│ ├── task-details/
│ └── task-form/
├── services/
│ └── task.service.ts
└── app.routes.ts
تعلمنا في هذا الكورس:
ننصحك بـ:
بالتوفيق! 🚀