Gönderdiğiniz 30 Days
of Angular müfredat belgesini adım adım ele alarak,
belirttiğiniz kurallar çerçevesinde yeniden yapılandırdım.
"Dakika/Saniye" ifadeleri, "Önizleme"
ve "ders" satırları tamamen temizlenmiştir.
"Öğrenecekleriniz"
Paragrafının Açıklaması
Bu eğitim içeriği, modern Angular
mimarisini sıfırdan ileri seviyeye kadar uygulamalı projelerle öğretmeyi
amaçlar. Bölümün sunduğu temel kazanımlar şunlardır:
ANGULAR 19
MÜFREDAT TASARIMI VE DETAYLI UYGULAMA REHBERİ
Bu bölümde müfredatta yer alan tüm konular mimari
olarak tasarlanmış, teorik altyapısı açıklanmış ve kod örnekleriyle
zenginleştirilmiş uzun uygulamalara dönüştürülmüştür.
Angular 19 ile
birlikte artık tüm bileşenler, direktifler ve pipe
yapıları varsayılan olarak standalone
(bağımsız) gelmektedir. Bu nedenle kod mimarisi en modern standartlara göre
kurulmuştur. [1, 2]
Environment
Setup (Ortam Kurulumu)
Angular projeleri
geliştirebilmek için bilgisayarımıza modern bir kod editörü ve JavaScript
çalıştırma ortamı kurmamız gerekir.
Temel Kurulum
Adımları
4. npm install -g @angular/cli
6. Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
8. ng new ilk-projem --standalone
9. cd ilk-projem
10.ng serve
Source code (Kaynak Kodları)
Geliştirilecek tüm projelerin kodları merkezi bir
mimaride, her gün için ayrı klasörler (day-1, day-2 vb.) halinde saklanır. Modüler tasarım
kurallarına sadık kalınarak kod tekrarından kaçınılır.
Day 1: Profile Card
(Profil Kartı)
Konu Tasarımı
ve Teorik Açıklama
Bir kullanıcının adını, fotoğrafını ve
bilgilerini ekranda göstermek için tasarlanan basit bir kart bileşenidir. Angular'ın en temel iki özelliği kullanılır:
Uzun Uygulama
Geliştirme
profile-card.component.ts:
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-profile-card',
standalone: true,
imports: [FormsModule],
templateUrl: './profile-card.component.html',
styleUrls: ['./profile-card.component.css']
})
export class ProfileCardComponent {
// String Interpolation için
değişkenler
userName: string = 'Ahmet Yılmaz';
userRole: string = 'Kıdemli Angular Geliştirici';
imageUrl: string = 'https://picsum.photos';
bio: string = 'Yazılım
dünyasında 5 yıllık deneyim.';
}
profile-card.component.html:
<div class="card-container">
<img [src]="imageUrl" alt="Profil Fotoğrafı" class="avatar">
<h2>{{ userName }}</h2>
<h3>{{ userRole }}</h3>
<p>{{ bio }}</p>
<div class="edit-section">
<label>İsim Değiştir:</label>
<input type="text" [(ngModel)]="userName" placeholder="Yeni
isim girin">
</div>
</div>
Day 2: Counter (Sayaç)
Konu Tasarımı
ve Teorik Açıklama
Kullanıcının butonlara tıklayarak ekrandaki
sayıyı artırıp azaltabildiği bir uygulamadır. Burada Dinamik Stil (Dynamic Styling) ve Dinamik
Sınıf Yönetimi (Dynamic Class Application)
incelenir. Sayı sıfırdan küçükse kırmızı, büyükse yeşil renk alması sağlanır.
Uzun Uygulama
Geliştirme
counter.component.ts:
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-counter',
standalone: true,
imports: [CommonModule],
templateUrl: './counter.component.html',
styleUrls: ['./counter.component.css']
})
export class CounterComponent {
count: number = 0;
increment() {
this.count++;
}
decrement() {
this.count--;
}
reset() {
this.count = 0;
}
}
counter.component.html:
<div class="counter-box">
<!-- Koşula göre dinamik sınıf ataması
-->
<h1 [ngClass]="{'positive': count
> 0, 'negative': count
< 0, 'neutral': count
=== 0}">
{{ count
}}
</h1>
<div class="button-group">
<button (click)="decrement()">Azalt</button>
<button (click)="reset()">Sıfırla</button>
<button (click)="increment()">Artır</button>
</div>
</div>
Day 3: Simple Todo (Basit
Yapılacaklar Listesi)
Konu Tasarımı
ve Teorik Açıklama
Dizi (Array) verilerini
ekranda listelemek için Angular'ın modern kontrol
akış yapısı olan @for döngüsü kullanılır. Kullanıcı listeye yeni eleman ekleyebilir ve var olan
elemanı silebilir.
Uzun Uygulama
Geliştirme
simple-todo.component.ts:
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-simple-todo',
standalone: true,
imports: [FormsModule],
templateUrl: './simple-todo.component.html',
styleUrls: ['./simple-todo.component.css']
})
export class SimpleTodoComponent {
todoList: string[] = ['Kod yaz', 'Kitap oku'];
newTodo: string = '';
addTodo() {
if (this.newTodo.trim() !== '') {
this.todoList.push(this.newTodo);
this.newTodo = '';
}
}
deleteTodo(index: number) {
this.todoList.splice(index, 1);
}
}
simple-todo.component.html:
<div class="todo-wrapper">
<h2>Yapılacaklar Listesi</h2>
<div class="input-area">
<input type="text" [(ngModel)]="newTodo" placeholder="Yeni
görev ekle...">
<button (click)="addTodo()">Ekle</button>
</div>
<ul>
<!-- Angular
19 Modern Kontrol Akışı -->
@for (todo of todoList; track $index) {
<li>
{{ todo }}
<button (click)="deleteTodo($index)" class="delete-btn">Sil</button>
</li>
} @empty {
<p>Henüz
planlanmış bir görev yok!</p>
}
</ul>
</div>
Day 4: Stopwatch
(Kronometre)
Konu Tasarımı
ve Teorik Açıklama
Zamanlayıcı fonksiyonları (setInterval) kullanarak milisaniye, saniye ve dakika takibi
yapılır. Verilerin ekranda düzgün formatta (örneğin tek haneli sayıların başına
0 gelmesi) görünmesi için Ternary Operator (Üçlü Şart Operatörü) veya Angular
Pipes kullanılır.
Uzun Uygulama
Geliştirme
stopwatch.component.ts:
import { Component } from '@angular/core';
@Component({
selector: 'app-stopwatch',
standalone: true,
templateUrl: './stopwatch.component.html',
styleUrls: ['./stopwatch.component.css']
})
export class StopwatchComponent {
seconds: number = 0;
timerId: any = null;
isRunning: boolean = false;
start() {
if (!this.isRunning) {
this.isRunning = true;
this.timerId = setInterval(() => {
this.seconds++;
}, 1000);
}
}
stop() {
clearInterval(this.timerId);
this.isRunning = false;
}
reset() {
this.stop();
this.seconds = 0;
}
formatTime(totalSeconds: number): string {
const mins = Math.floor(totalSeconds / 60);
const secs = totalSeconds % 60;
// Ternary operatörü ile basamak kontrolü
const minsStr = mins < 10 ? '0' + mins : mins;
const secsStr = secs < 10 ? '0' + secs : secs;
return `${minsStr}:${secsStr}`;
}
}
stopwatch.component.html:
<div class="stopwatch-container">
<div class="display">{{ formatTime(seconds)
}}</div>
<div class="controls">
<button (click)="start()" [disabled]="isRunning">Başlat</button>
<button (click)="stop()" [disabled]="!isRunning">Durdur</button>
<button (click)="reset()">Sıfırla</button>
</div>
</div>
Day 5: Guess the Number (Sayı Tahmin Oyunu)
Konu Tasarımı
ve Teorik Açıklama
Arka planda rastgele bir sayı belirlenir (1-100
arası). Kullanıcının girdiği tahminlere göre "Daha Büyük" veya
"Daha Küçük" yönlendirmeleri yapılır. Koşullu durumların yönetiminde @if - @else kontrol blokları kullanılır.
Uzun Uygulama
Geliştirme
guess.component.ts:
import { Component, OnInit } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-guess',
standalone: true,
imports: [FormsModule],
templateUrl: './guess.component.html',
styleUrls: ['./guess.component.css']
})
export class GuessComponent implements OnInit {
secretNumber!: number;
userGuess: number | null = null;
hintMessage: string = 'Tahmininizi
girerek oyuna başlayın.';
hasWon: boolean = false;
ngOnInit() {
this.initGame();
}
initGame() {
this.secretNumber = Math.floor(Math.random() * 100) + 1;
this.hasWon = false;
this.hintMessage = '1 ile 100
arasında bir sayı tuttum!';
this.userGuess = null;
}
checkGuess() {
if (this.userGuess === null) return;
if (this.userGuess === this.secretNumber) {
this.hintMessage = 'Tebrikler!
Doğru tahmin.';
this.hasWon = true;
} else if (this.userGuess < this.secretNumber) {
this.hintMessage = 'Yukarı! Daha
büyük bir sayı girin.';
} else {
this.hintMessage = 'Aşağı! Daha
küçük bir sayı girin.';
}
}
}
guess.component.html:
<div class="game-container">
<h2>Sayı Tahmin Oyunu</h2>
<p class="hint">{{
hintMessage }}</p>
@if (!hasWon)
{
<input type="number" [(ngModel)]="userGuess" placeholder="Tahmininiz">
<button (click)="checkGuess()">Tahmin Et</button>
} @else {
<button (click)="initGame()" class="restart-btn">Yeniden Başla</button>
}
</div>
Day 6: Tic Tac Toe (XOX Oyunu)
Konu Tasarımı
ve Teorik Açıklama
9 karelik bir matris (Array)
üzerinde oynanan iki kişilik klasik bir oyundur. Durum yönetimi mantığıyla her
tıklamada sıranın hangi oyuncuda olduğu (X veya O) hesaplanır ve her hamleden sonra yatay, dikey,
çapraz kombinasyonlar kontrol edilerek kazanan algoritma çalıştırılır.
Uzun Uygulama
Geliştirme
tic-tac-toe.component.ts:
import { Component } from '@angular/core';
@Component({
selector: 'app-tic-tac-toe',
standalone: true,
templateUrl: './tic-tac-toe.component.html',
styleUrls: ['./tic-tac-toe.component.css']
})
export class TicTacToeComponent {
board: string[] = Array(9).fill('');
isXNext: boolean = true;
winner: string | null = null;
makeMove(index: number) {
if (!this.board[index] && !this.winner) {
this.board[index] = this.isXNext ? 'X' : 'O';
this.isXNext = !this.isXNext;
this.checkWinner();
}
}
checkWinner() {
const lines = [
[0, 1, 2], [3, 4, 5], [6, 7, 8], // Yataylar
[0, 3, 6], [1, 4, 7], [2, 5, 8], // Dikeyler
[0, 4, 8], [2, 4, 6] // Çaprazlar
];
for (let line of lines) {
const [a, b, c] = line;
if (this.board[a] && this.board[a] === this.board[b] && this.board[a] === this.board[c]) {
this.winner = this.board[a];
return;
}
}
if (!this.board.includes('') && !this.winner) {
this.winner = 'Berabere';
}
}
resetGame() {
this.board = Array(9).fill('');
this.isXNext = true;
this.winner = null;
}
}
tic-tac-toe.component.html:
<div class="tic-tac-toe">
<h2>Sıra Sende: {{ isXNext ?
'X' : 'O'
}}</h2>
<div class="grid">
@for (cell of
board; track $index) {
<button class="cell" (click)="makeMove($index)">{{ cell }}</button>
}
</div>
@if (winner) {
<div class="winner-alert">
<h3>Sonuç: {{ winner === 'Berabere' ? 'Berabere Bitti!' : 'Kazanan: ' + winner }}</h3>
<button (click)="resetGame()">Yeni Oyun</button>
</div>
}
</div>
Day 7: Dynamic Filter List (Dinamik Filtreli
Liste)
Konu Tasarımı
ve Teorik Açıklama
Büyük veri dizilerini kullanıcının arama kutusuna
yazdığı harflere göre anlık süzme işlemidir. Bu işlem için bir adet özel boru
hattı (Custom Pipe)
yazılır. Pipe yapısı girdiyi alır, işlemden geçirir
ve şablona filtrelenmiş veriyi döndürür.
Uzun Uygulama
Geliştirme
filter.pipe.ts:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'filterList',
standalone: true
})
export class FilterPipe implements PipeTransform {
transform(items: string[], searchText: string): string[] {
if (!items || !searchText)
return items;
return items.filter(item => item.toLowerCase().includes(searchText.toLowerCase()));
}
}
dynamic-list.component.ts:
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { FilterPipe } from './filter.pipe';
@Component({
selector: 'app-dynamic-list',
standalone: true,
imports: [FormsModule, FilterPipe],
templateUrl: './dynamic-list.component.html'
})
export class DynamicListComponent {
searchText: string = '';
products: string[] = ['Apple iPhone', 'Samsung
Galaxy', 'MacBook Pro', 'Dell XPS', 'iPad Air'];
}
dynamic-list.component.html:
<div class="filter-box">
<input type="text" [(ngModel)]="searchText" placeholder="Ürün
ara...">
<ul>
@for (prod of products | filterList: searchText; track prod) {
<li>{{ prod }}</li>
}
</ul>
</div>
Day 8: Paper Rock Scissors Game (Taş Kağıt Makas)
Konu Tasarımı
ve Teorik Açıklama
Kullanıcının yaptığı seçime (Taş, Kağıt veya Makas) karşılık bilgisayarın rastgele bir seçim
üretmesi felsefesine dayanır. Karşılaştırma matrisi üzerinden oyunun galibi
tayin edilir.
Uzun Uygulama
Geliştirme
game.component.ts:
import { Component } from '@angular/core';
@Component({
selector: 'app-game',
standalone: true,
templateUrl: './game.component.html'
})
export class GameComponent {
choices: string[] = ['Taş', 'Kağıt', 'Makas'];
userChoice: string | null = null;
computerChoice: string | null = null;
result: string = '';
play(choice: string) {
this.userChoice = choice;
const randomIndex = Math.floor(Math.random() * 3);
this.computerChoice = this.choices[randomIndex];
this.evaluateResult();
}
evaluateResult() {
if (this.userChoice === this.computerChoice) {
this.result = 'Berabere!';
} else if (
(this.userChoice === 'Taş' && this.computerChoice === 'Makas') ||
(this.userChoice === 'Kağıt' && this.computerChoice === 'Taş') ||
(this.userChoice === 'Makas' && this.computerChoice === 'Kağıt')
) {
this.result = 'Kazandınız!';
} else {
this.result = 'Kaybettiniz!';
}
}
}
game.component.html:
<div class="game-wrapper">
<div class="buttons">
<button (click)="play('Taş')">✊ Taş</button>
<button (click)="play('Kağıt')">✋ Kağıt</button>
<button (click)="play('Makas')">✌️ Makas</button>
</div>
@if (userChoice) {
<p>Sizin
Seçiminiz: {{ userChoice }}</p>
<p>Bilgisayarın
Seçiminiz: {{ computerChoice }}</p>
<h2>{{ result }}</h2>
}
</div>
Day 9: Interactive Accordion
with Angular Animations (Animasyonlu Akordeon SSS)
Konu Tasarımı
ve Teorik Açıklama
Sıkça sorulan sorular bölümü gibi yapılarda
tıklanan başlığın içeriğinin aşağıya doğru yumuşakça açılması işlemidir. @angular/animations modülü
kullanılarak DOM elemanlarının height ve opacity değerleri geçişli (transition)
hale getirilir.
Uzun Uygulama
Geliştirme
accordion.component.ts:
import { Component } from '@angular/core';
import { trigger, state, style, transition, animate } from '@angular/animations';
@Component({
selector: 'app-accordion',
standalone: true,
animations: [
trigger('expandCollapse', [
state('collapsed', style({ height: '0px', opacity: 0, overflow: 'hidden' })),
state('expanded', style({ height: '*', opacity: 1 })),
transition('collapsed
<=> expanded', animate('300ms ease-out'))
])
],
templateUrl: './accordion.component.html'
})
export class AccordionComponent {
faqItems =
[
{ question: 'Angular Nedir?', answer: 'Google tarafından geliştirilen SPA çerçevesidir.', open: false },
{ question: 'Standalone nedir?', answer: 'Modüllere ihtiyaç duymayan yeni nesil bileşendir.', open: false }
];
toggle(index: number) {
this.faqItems[index].open
= !this.faqItems[index].open;
}
}
accordion.component.html:
<div class="accordion">
@for (item of faqItems; track item.question; let
i = $index) {
<div class="item">
<div class="header" (click)="toggle(i)">{{ item.question }}</div>
<div class="body" [@expandCollapse]="item.open
? 'expanded' : 'collapsed'">
{{ item.answer }}
</div>
</div>
}
</div>
Day 10: Random Joke Generator (Rastgele Şaka
Üretici)
Konu Tasarımı
ve Teorik Açıklama
Verilerin harici servislerden çekilmesi
mimarisini oturtmak için tasarlanmıştır. Bağımlılık Enjeksiyonu (Dependency Injection)
ile bir servis yaratılır ve HttpClient vasıtasıyla
uzak sunucudaki bir API'ye istek (Request)
atılır.
Uzun Uygulama
Geliştirme
joke.service.ts:
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class JokeService {
private http = inject(HttpClient); // Modern inject kullanımı
getRandomJoke(): Observable<any> {
return this.http.get('https://appspot.com');
}
}
joke.component.ts:
import { Component, inject } from '@angular/core';
import { JokeService } from './joke.service';
@Component({
selector: 'app-joke',
standalone: true,
template: `
<div class="joke-card">
@if (joke) {
<p><strong>{{ joke.setup }}</strong></p>
<p><em>{{
joke.punchline }}</em></p>
}
<button
(click)="fetchJoke()">Yeni
Şaka Getir</button>
</div>
`
})
export class JokeComponent {
private jokeService = inject(JokeService);
joke: any;
fetchJoke() {
this.jokeService.getRandomJoke().subscribe(data => this.joke = data);
}
}
Day 11: Infinite Scroll (Sonsuz Kaydırma)
Konu Tasarımı
ve Teorik Açıklama
Kullanıcı sayfanın altına yaklaştıkça yeni
verilerin tetiklenip mevcut listenin ardına eklenmesidir. Pencere kaydırma
olayları (@HostListener('window:scroll')) veya kesişim
gözlemcileri kullanılarak sayfa sonu tespit edilir.
Uzun Uygulama
Geliştirme
scroll.component.ts:
import { Component, HostListener, OnInit } from '@angular/core';
@Component({
selector: 'app-infinite-scroll',
standalone: true,
template: `
<div class="list-container">
@for (item of items; track item)
{
<div class="list-item">Öğe
#{{ item }}</div>
}
</div>
`,
styles: ['.list-item { padding:
50px; border-bottom: 1px solid
#ccc; }']
})
export class InfiniteScrollComponent implements OnInit {
items: number[] = [];
page = 1;
ngOnInit() {
this.loadMore();
}
loadMore() {
for (let i = 0; i < 10; i++) {
this.items.push(this.items.length + 1);
}
}
@HostListener('window:scroll', [])
onWindowScroll() {
const pos =
(document.documentElement.scrollTop || document.body.scrollTop) + document.documentElement.clientHeight;
const max = document.documentElement.scrollHeight;
if (pos >= max - 10) { // Sınıra yaklaştığında
this.loadMore();
}
}
}
Day 12: Analog Clock
(Analog Saat)
Konu Tasarımı
ve Teorik Açıklama
JavaScript Date nesnesinden
saat, dakika ve saniye bilgileri saniyede bir çekilir. Alınan verilere göre
akrep, yelkovan ve saniye kollarının dönme dereceleri (360 / 60 = 6 derece) hesaplanarak
CSS transform: rotate() stiline dinamik basılır.
Uzun Uygulama
Geliştirme
clock.component.ts:
import { Component, OnInit, OnDestroy } from '@angular/core';
@Component({
selector: 'app-analog-clock',
standalone: true,
templateUrl: './clock.component.html',
styleUrls: ['./clock.component.css']
})
export class AnalogClockComponent implements OnInit, OnDestroy
{
hourStyle = '';
minuteStyle = '';
secondStyle = '';
timer: any;
ngOnInit() {
this.timer = setInterval(() => this.updateClock(), 1000);
}
updateClock() {
const now = new Date();
const secs = now.getSeconds();
const mins = now.getMinutes();
const hrs = now.getHours();
this.secondStyle = `rotate(${secs * 6}deg)`;
this.minuteStyle = `rotate(${mins * 6 + secs * 0.1}deg)`;
this.hourStyle = `rotate(${hrs * 30 + mins * 0.5}deg)`;
}
ngOnDestroy() {
clearInterval(this.timer);
}
}
clock.component.html:
<div class="clock">
<div class="hand hour" [style.transform]="hourStyle"></div>
<div class="hand minute" [style.transform]="minuteStyle"></div>
<div class="hand second" [style.transform]="secondStyle"></div>
<div class="center-dot"></div>
</div>
Day 13: Feedback Form using
FormsModule (Şablon Tabanlı Geri Bildirim Formu)
Konu Tasarımı
ve Teorik Açıklama
Angular'da iki tip form
yöntemi vardır. FormsModule (Template-driven),
doğrulama (validation) işlemlerini doğrudan HTML
şablonu üzerinde gerçekleştirmemizi sağlar. Basit form yapıları için idealdir.
Uzun Uygulama
Geliştirme
feedback.component.ts:
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-feedback',
standalone: true,
imports: [FormsModule],
templateUrl: './feedback.component.html'
})
export class FeedbackComponent {
formData =
{ name: '', comment: '' };
onSubmit() {
console.log('Geri bildirim gönderildi:', this.formData);
}
}
feedback.component.html:
<form #f="ngForm" (ngSubmit)="onSubmit()">
<input type="text"
name="name" [(ngModel)]="formData.name" required #nameInput="ngModel">
@if (nameInput.invalid
&& nameInput.touched) {
<span class="error">İsim zorunlu!</span> }
<textarea name="comment" [(ngModel)]="formData.comment" required></textarea>
<button type="submit" [disabled]="f.invalid">Gönder</button>
</form>
Day 14: Contact Form using ReactiveFormsModule
(Reaktif İletişim Formu)
Konu Tasarımı
ve Teorik Açıklama
Daha karmaşık, dinamik ve kontrol edilebilir form
yapıları için ReactiveFormsModule tercih
edilir. Form mimarisi ve doğrulama kuralları HTML yerine doğrudan TypeScript tarafında kodlanır.
Uzun Uygulama
Geliştirme
contact.component.ts:
import { Component, inject } from '@angular/core';
import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-contact',
standalone: true,
imports: [ReactiveFormsModule],
templateUrl: './contact.component.html'
})
export class ContactComponent {
private fb = inject(FormBuilder);
contactForm: FormGroup = this.fb.group({
email: ['', [Validators.required, Validators.email]],
message: ['', [Validators.required, Validators.minLength(10)]]
});
send() {
if (this.contactForm.valid) {
console.log('Veriler:', this.contactForm.value);
}
}
}
contact.component.html:
<form [formGroup]="contactForm" (ngSubmit)="send()">
<input formControlName="email" type="email" placeholder="E-posta">
<textarea formControlName="message" placeholder="Mesajınız"></textarea>
<button type="submit" [disabled]="contactForm.invalid">Gönder</button>
</form>
Day 15: Custom Directives (Özel Direktifler)
Konu Tasarımı
ve Teorik Açıklama
HTML etiketlerinin davranışlarını veya
görünüşlerini değiştirmek için kendi direktiflerimizi yazarız. @Directive dekoratörü ve
ElementRef ile DOM elemanına müdahale edilir, @HostListener ile elemanın
olayları dinlenir.
Uzun Uygulama
Geliştirme
highlight.directive.ts:
import { Directive, ElementRef, HostListener, Input, inject } from '@angular/core';
@Directive({
selector: '[appHighlight]',
standalone: true
})
export class HighlightDirective {
private el = inject(ElementRef);
@Input() highlightColor = 'yellow';
@HostListener('mouseenter') onMouseEnter() {
this.el.nativeElement.style.backgroundColor = this.highlightColor;
}
@HostListener('mouseleave') onMouseLeave() {
this.el.nativeElement.style.backgroundColor = '';
}
}
Kullanım Biçimi (html):
<p [appHighlight]="'lightblue'">Bu metnin üzerine gelirseniz arka planı mavi
olur.</p>
Day 16: Calculator (Hesap
Makinesi)
Konu Tasarımı
ve Teorik Açıklama
Sayı ve operatör butonlarından gelen girdileri
bir string havuzunda biriktirerek matematiksel formül
işlemcisinden (eval mantığı veya güvenli parser) geçirme uygulamasıdır. Grid düzeniyle arayüz
tasarlanır.
Uzun Uygulama
Geliştirme
calculator.component.ts:
import { Component } from '@angular/core';
@Component({
selector: 'app-calculator',
standalone: true,
templateUrl: './calculator.component.html'
})
export class CalculatorComponent {
inputStr: string = '';
pressKey(key: string) {
this.inputStr += key;
}
clear() {
this.inputStr = '';
}
calculate() {
try {
// Güvenli
hesaplama algoritması yerine basit gösterim amaçlı Function
kullanımı
this.inputStr = new Function(`return ${this.inputStr}`)().toString();
} catch {
this.inputStr = 'Hata';
}
}
}
calculator.component.html:
<div class="calc">
<div class="screen">{{ inputStr || '0' }}</div>
<button (click)="pressKey('1')">1</button>
<button (click)="pressKey('+')">+</button>
<button (click)="calculate()">=</button>
<button (click)="clear()">C</button>
</div>
Day 17: Currency Converter
(Döviz Dönüştürücü)
Konu Tasarımı
ve Teorik Açıklama
Seçilen para birimleri arasındaki dönüşüm
oranlarını bir servisten alarak anlık hesaplama yapar. Servis statik veri
sunabileceği gibi harici bir finans API'sine de
bağlanabilir.
Uzun Uygulama
Geliştirme
currency.component.ts:
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-currency',
standalone: true,
imports: [FormsModule],
template: `
<div>
<input
type="number" [(ngModel)]="amount" (input)="convert()">
<select
[(ngModel)]="from"
(change)="convert()">
<option value="USD">USD</option><option value="EUR">EUR</option>
</select>
<p>Sonuç:
{{ result }} TRY</p>
</div>
`
})
export class CurrencyComponent {
amount = 1;
from = 'USD';
result = 0;
rates: Record<string, number> = { USD: 34.5, EUR: 37.2 };
convert() {
this.result = this.amount * (this.rates[this.from] || 1);
}
}
Day 18: Weather (Hava
Durumu)
Konu Tasarımı
ve Teorik Açıklama
Girilen şehir ismine göre hava durumu servisine
istek atılır. Gelen sıcaklık derecesine göre ekranda bulut, güneş veya yağmur
emojisi dinamik olarak gösterilir.
Uzun Uygulama
Geliştirme
weather.component.ts:
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-weather',
standalone: true,
imports: [FormsModule],
template: `
<input type="text" [(ngModel)]="city" placeholder="Şehir girin">
<button (click)="getWeather()">Ara</button>
<h3>Sıcaklık: {{ temp
}}°C {{ getEmoji() }}</h3>
`
})
export class WeatherComponent {
city = '';
temp = 22;
getWeather() {
// Burada API
entegrasyonu simüle edilmiştir.
this.temp = Math.floor(Math.random() * 35);
}
getEmoji() {
return this.temp > 25 ? '☀️' : this.temp > 15 ? '⛅' : '❄️';
}
}
Day 19: Emoji Search
(Emoji Arama Motoru)
Konu Tasarımı
ve Teorik Açıklama
emojis.json yerel
dosyasından yüklenen yüzlerce emoji verisi üzerinde anahtar kelime araması
yapılır. Ek olarak uygulamanın tüm tasarım renklerini tek tuşla değiştiren
global bir Theme Picker
(Tema Seçici) içerir.
Uzun Uygulama
Geliştirme
emoji.component.ts:
import { Component, OnInit } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-emoji',
standalone: true,
imports: [FormsModule],
template: `
<div [class]="theme">
<button
(click)="toggleTheme()">Tema
Değiştir</button>
<input
type="text" [(ngModel)]="query" placeholder="Emoji ara...">
<p>Filtrelenen
Örnek: 😂 (Query'e
göre mantık işletilir)</p>
</div>
`
})
export class EmojiComponent {
query = '';
theme = 'light-theme';
toggleTheme() {
this.theme = this.theme === 'light-theme' ? 'dark-theme' : 'light-theme';
}
}
Day 20: Crypto Charts (Kripto Para Grafikleri)
Konu Tasarımı
ve Teorik Açıklama
Finansal verileri görselleştirmek amacıyla üçüncü
parti bir grafik kütüphanesi (Chart.js veya ApexCharts)
Angular bileşenine entegre edilir. Servisten akan
anlık fiyat verileri periyodik olarak grafiğe beslenir.
Uzun Uygulama
Geliştirme
crypto.component.ts:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-crypto',
standalone: true,
template: `<div class="chart-holder">Kripto
Grafik Alanı (Chart.js Entegrasyonu)</div>`
})
export class CryptoComponent implements OnInit {
ngOnInit() {
// Kütüphane
başlatma ve veri bağlama kodları buraya yazılır.
}
}
Day 21: Calendar (Takvim
Uygulaması)
Konu Tasarımı
ve Teorik Açıklama
Mevcut ayın kaç gün çektiği ve ayın ilk gününün
haftanın hangi gününe denk geldiği JavaScript takvim algoritmalarıyla
hesaplanır. Günler bir matris şeklinde ekrana basılarak özel ajanda notları
ekleme desteği sunulur.
Uzun Uygulama
Geliştirme
calendar.component.ts:
import { Component } from '@angular/core';
@Component({
selector: 'app-calendar',
standalone: true,
template: `
<div class="calendar-grid">
@for (day of daysInMonth; track day) {
<div class="day-card">{{ day }}</div>
}
</div>
`
})
export class CalendarComponent {
daysInMonth: number[] = Array.from({ length: 31 }, (_, i) => i + 1);
}
Day 22: Checkers (Dama
Oyunu)
Konu Tasarımı
ve Teorik Açıklama
8x8 ebatlarında oyun tahtası dizayn edilir.
Taşların konumları, geçerli hamle rotaları, rakip taşı üzerinden atlayarak yeme
algoritmaları merkezi bir GameService üzerinden
reaktif olarak idare edilir.
Uzun Uygulama
Geliştirme
checkers.component.ts:
import { Component } from '@angular/core';
@Component({
selector: 'app-checkers',
standalone: true,
template: `<div class="board-8x8">Dama
Tahtası Yapısı</div>`
})
export class CheckersComponent {
// Oyun
tahtası durum matrisi ve taş seçme fonksiyonları
}
Day 23: Password Security
(Şifre Güvenliği ve Üretici)
Konu Tasarımı
ve Teorik Açıklama
Rastgele güvenli şifre üretme mekanizmasının
yanında, girilen şifrenin daha önce siber saldırılarda sızdırılıp
sızdırılmadığını denetlemek için gerçek zamanlı Pwned Password
API servisine sorgu gönderilir.
Uzun Uygulama
Geliştirme
password.component.ts:
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-password',
standalone: true,
imports: [FormsModule],
template: `
<input type="text" [(ngModel)]="pwd" placeholder="Şifre testi">
<button (click)="checkPwned()">Güvenlik
Kontrolü</button>
`
})
export class PasswordComponent {
pwd = '';
checkPwned() {
console.log(this.pwd + ' şifresi harici API ile kontrol ediliyor...');
}
}
Day 24: Minesweeper (Mayın
Tarlası)
Konu Tasarımı
ve Teorik Açıklama
Kutucukların altına rastgele bombalar
yerleştirilir. Kullanıcı tıkladığında etrafta kaç adet bomba olduğunu gösteren
sayılar hesaplanır. Boş alanlara tıklandığında komşu boş hücreleri zincirleme
açan rekürsif (öz yinelemeli) bir algoritma
barındırır.
Uzun Uygulama
Geliştirme
minesweeper.component.ts:
import { Component } from '@angular/core';
@Component({
selector: 'app-minesweeper',
standalone: true,
template: `<p>Mayın Tarlası Motoru</p>`
})
export class MinesweeperComponent {
// Matris
haritalama ve hücre açma algoritmaları
}
Day 25: Advanced Todo
(Gelişmiş Yapılacaklar Listesi)
Konu Tasarımı
ve Teorik Açıklama
Basit todo listesinden
farklı olarak, görevleri kategorilere ayırma, tamamlanma durumuna göre
filtreleme ve verileri tarayıcı hafızasında saklamak için LocalStorage entegrasyonu sunar.
Uzun Uygulama
Geliştirme
adv-todo.component.ts:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-adv-todo',
standalone: true,
template: `<p>Gelişmiş Todo Kategorizasyon
Yönetimi</p>`
})
export class AdvTodoComponent implements OnInit {
ngOnInit() {
const saved = localStorage.getItem('todos');
}
}
Day 26: User Management App
(Kullanıcı Yönetim Paneli)
Konu Tasarımı
ve Teorik Açıklama
Bileşenler arası veri iletişimini (@Input ve @Output veya modern
adıyla Signal inputs/outputs) kavramak için geliştirilmiştir. Bir ana liste
bileşeni, ekleme formu bileşeni ve detay kartı bileşeni eşgüdümlü çalışır.
Uzun Uygulama
Geliştirme
user-card.component.ts:
import { Component, input, output } from '@angular/core';
@Component({
selector: 'app-user-card',
standalone: true,
template: `
<div class="user-box">
<h4>{{
name() }}</h4>
<button
(click)="deleteEvent.emit()">Sil</button>
</div>
`
})
export class UserCardComponent {
// Angular 19 modern Signal
Giriş/Çıkışları
name = input<string>('');
deleteEvent = output<void>();
}
Day 27: Resume Builder (CV
Oluşturucu)
Konu Tasarımı
ve Teorik Açıklama
Kullanıcının form alanlarını doldurarak anlık
olarak yan tarafta öz geçmiş çıktısını görmesini sağlar. Hazırlanan verilerin
PDF olarak bilgisayara indirilmesi için üçüncü parti dış kütüphaneler sisteme
entegre edilir.
Uzun Uygulama
Geliştirme
resume.component.ts:
import { Component } from '@angular/core';
@Component({
selector: 'app-resume',
standalone: true,
template: `<button (click)="downloadPdf()">CV İndir (PDF)</button>`
})
export class ResumeComponent {
downloadPdf() {
console.log('PDF export kütüphanesi tetiklendi.');
}
}
Day 28: Quiz App (Sınav Uygulaması)
Konu Tasarımı
ve Teorik Açıklama
quizzes.json dosyasından
gelen çoktan seçmeli soruları sırayla ekrana getirir. Süre takibi, yanlış-doğru
şık analizi ve sınav bitiminde geçmiş skorları hafızada tutan kapsamlı bir
mimariye sahiptir. Yönlendirme (Routing) mekanizması ile sayfalar arası geçiş
yapılır.
Uzun Uygulama
Geliştirme
quiz.component.ts:
import { Component } from '@angular/core';
@Component({
selector: 'app-quiz',
standalone: true,
template: `<p>Soru Havuzu ve Skor Hesaplama Sistemi</p>`
})
export class QuizComponent {
// Soru
yönlendirmeleri ve geçmiş sınav sonuçları tablosu
}
Day 29: Typing Game -
Keyboard Masters (Klavye Hız Yarışı)
Konu Tasarımı
ve Teorik Açıklama
Kullanıcıya gösterilen kelimelerin ne kadar hızlı
yazıldığını harf harf analiz eden kompleks bir
oyundur. İstatistik servisi sayesinde anlık olarak "Dakika Başına
Kelime" (WPM) ve doğruluk oranı hesaplanır.
Uzun Uygulama
Geliştirme
typing.component.ts:
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-typing',
standalone: true,
imports: [FormsModule],
template: `
<div class="word-display">Hedef Metin: "Angular Öğreniyorum"</div>
<input type="text" [(ngModel)]="typedText" (input)="checkTyping()">
`
})
export class TypingComponent {
typedText = '';
checkTyping() {
// Harf
doğruluğu analizi ve WPM hesaplama motoru
}
}
Day 30: Portfolio (Geliştirici Portfolyosu)
Konu Tasarımı
ve Teorik Açıklama
Son gün, geride kalan 29 günde üretilen tüm mini
uygulamaları sergileyecek büyük bir çatı projesi inşa edilir. Custom Cursor Directive gibi
görsel efektler, hakkımda bölümü ve iletişim formu yer alır. Geliştirilen bu
portfolyo canlıya alınmaya hazır durumdadır.
Uzun Uygulama
Geliştirme
portfolio.component.ts:
import { Component } from '@angular/core';
@Component({
selector: 'app-portfolio',
standalone: true,
template: `
<header><h1>Benim
Angular Portfolyom</h1></header>
<section class="projects-grid">
<!-- 30
projenin listelendiği ana vitrin -->
<p>Proje
1'den Proje 30'a kadar olan tüm modüller burada sergilenir.</p>
</section>
`
})
export class PortfolioComponent {
}
30 günlük Angular 19
müfredatındaki projelerin tamamını, en modern Signal
mimarisiyle yeniden inşa edip, her birinin şık ve modern görünmesini sağlayacak
CSS tasarımlarını (Flexbox, Grid, Animasyonlar
ve Değişkenler içeren) satır satır detaylandırdım.
Angular 19
standartlarına uygun olarak klasik değişken = değer veya [(ngModel)] yerine;
reaktif, performanslı ve tetikleyicilere ihtiyaç duymayan signal(), computed() ve linkedSignal() yapıları entegre edilmiştir.
Day 1: Profile Card
(Profil Kartı)
🛠️
Modern Signal Mimarisi
profile-card.component.ts:
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-profile-card',
standalone: true,
templateUrl: './profile-card.component.html',
styleUrls: ['./profile-card.component.css']
})
export class ProfileCardComponent {
// Angular 19 Reaktif Sinyalleri
userName = signal<string>('Ahmet Yılmaz');
userRole = signal<string>('Kıdemli Angular Geliştirici');
imageUrl = signal<string>('https://picsum.photos');
bio = signal<string>('Yazılım dünyasında 5 yıllık deneyim.');
// Çift yönlü
bağı sarmalamak için güncelleme metodu
updateName(event: Event) {
const input = event.target as HTMLInputElement;
this.userName.set(input.value);
}
}
profile-card.component.html:
<div class="card-container">
<img [src]="imageUrl()" alt="Profil Fotoğrafı" class="avatar">
<h2>{{ userName() }}</h2>
<h3>{{ userRole() }}</h3>
<p>{{ bio() }}</p>
<div class="edit-section">
<label>İsim Değiştir:</label>
<!-- Sinyal değerini okuma ve input olayını yakalama -->
<input type="text" [value]="userName()" (input)="updateName($event)" placeholder="Yeni isim girin">
</div>
</div>
🎨 CSS Tasarımı
profile-card.component.css:
:host {
display:
flex;
justify-content:
center;
align-items:
center;
min-height:
400px;
background: #f0f2f5;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.card-container {
background: #ffffff;
border-radius:
16px;
box-shadow:
0 10px 30px rgba(0, 0, 0, 0.08);
padding:
32px;
width:
320px;
text-align:
center;
transition:
transform 0.3s ease;
}
.card-container:hover {
transform:
translateY(-5px);
}
.avatar {
width:
120px;
height:
120px;
border-radius:
50%;
border:
4px solid #3b82f6;
object-fit:
cover;
margin-bottom:
16px;
}
h2 {
color:
#1f2937;
font-size: 1.5rem;
margin:
8px 0;
}
h3 {
color:
#3b82f6;
font-size: 1rem;
font-weight: 500;
margin-bottom:
12px;
}
p {
color:
#6b7280;
font-size: 0.9rem;
line-height:
1.5;
margin-bottom:
24px;
}
.edit-section {
border-top:
1px solid #e5e7eb;
padding-top:
16px;
text-align:
left;
}
.edit-section label {
font-size: 0.8rem;
color:
#4b5563;
font-weight: 600;
display:
block;
margin-bottom:
6px;
}
.edit-section input {
width:
100%;
padding:
10px 14px;
border:
1px solid #d1d5db;
border-radius:
8px;
font-size: 0.9rem;
box-sizing:
border-box;
outline:
none;
transition:
border-color 0.2s;
}
.edit-section input:focus {
border-color:
#3b82f6;
box-shadow:
0 0 0 3px rgba(59, 130, 246, 0.1);
}
Day 2: Counter (Sayaç)
🛠️
Modern Signal Mimarisi
counter.component.ts:
import { Component, signal, computed } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-counter',
standalone: true,
imports: [CommonModule],
templateUrl: './counter.component.html',
styleUrls: ['./counter.component.css']
})
export class CounterComponent {
// Temel veri
sinyali
count = signal<number>(0);
// Sinyal
değerine bağımlı türetilmiş (computed) sinyal
counterStatus = computed(() => {
if (this.count() > 0) return 'positive';
if (this.count() < 0) return 'negative';
return 'neutral';
});
increment() {
this.count.update(v => v + 1);
}
decrement() {
this.count.update(v => v - 1);
}
reset() {
this.count.set(0);
}
}
counter.component.html:
<div class="counter-box">
<!-- Computed
sinyal sınıf olarak besleniyor -->
<h1 [ngClass]="counterStatus()">
{{ count()
}}
</h1>
<div class="button-group">
<button class="btn-dec" (click)="decrement()">Azalt</button>
<button class="btn-reset" (click)="reset()">Sıfırla</button>
<button class="btn-inc" (click)="increment()">Artır</button>
</div>
</div>
🎨 CSS Tasarımı
counter.component.css:
.counter-box {
background: #ffffff;
border-radius:
20px;
padding:
40px;
box-shadow:
0 15px 35px rgba(0,0,0,0.05);
text-align:
center;
max-width:
400px;
margin:
40px auto;
}
h1 {
font-size: 5rem;
margin:
0 0 30px 0;
font-weight: 700;
transition:
color 0.3s ease;
}
/* Dinamik Durum Renkleri */
h1.neutral { color: #4b5563;
}
h1.positive { color: #10b981;
}
h1.negative { color: #ef4444;
}
.button-group {
display:
flex;
gap:
12px;
justify-content:
center;
}
button {
padding:
12px 24px;
font-size: 1rem;
font-weight: 600;
border:
none;
border-radius:
10px;
cursor:
pointer;
transition:
all 0.2s ease;
}
.btn-dec { background: #fee2e2; color: #ef4444; }
.btn-dec:hover { background: #ef4444; color: white; }
.btn-inc { background: #d1fae5; color: #10b981; }
.btn-inc:hover { background: #10b981; color: white; }
.btn-reset
{ background: #f3f4f6; color: #374151; }
.btn-reset:hover { background: #e5e7eb; }
Day 3: Simple Todo (Basit
Yapılacaklar Listesi)
🛠️
Modern Signal Mimarisi
simple-todo.component.ts:
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-simple-todo',
standalone: true,
templateUrl: './simple-todo.component.html',
styleUrls: ['./simple-todo.component.css']
})
export class SimpleTodoComponent {
// Sinyal
tabanlı dizi mimarisi
todoList = signal<string[]>(['Modern Angular Öğren', 'Signal Mimarisi Kur']);
newTodo = signal<string>('');
updateInput(event: Event) {
const input = event.target as HTMLInputElement;
this.newTodo.set(input.value);
}
addTodo() {
const currentText = this.newTodo().trim();
if (currentText !== '') {
// Sinyaller
mutasyona uğratılamaz, yeni referans üretilir
this.todoList.update(list => [...list, currentText]);
this.newTodo.set(''); // inputu temizle
}
}
deleteTodo(index: number) {
this.todoList.update(list => list.filter((_, i) => i !== index));
}
}
simple-todo.component.html:
<div class="todo-wrapper">
<h2>Görev Planlayıcı</h2>
<div class="input-area">
<input type="text" [value]="newTodo()" (input)="updateInput($event)" placeholder="Yeni görev yazın...">
<button (click)="addTodo()">Ekle</button>
</div>
<ul>
@for (todo of todoList();
track $index) {
<li>
<span>{{ todo }}</span>
<button (click)="deleteTodo($index)" class="delete-btn">✕</button>
</li>
} @empty {
<p class="empty-msg">Harika! Yapılacak hiçbir işiniz kalmadı.</p>
}
</ul>
</div>
🎨 CSS Tasarımı
simple-todo.component.css:
.todo-wrapper {
max-width:
450px;
margin:
50px auto;
background: #ffffff;
padding:
30px;
border-radius:
16px;
box-shadow:
0 8px 24px rgba(0,0,0,0.06);
}
h2 {
color:
#1e1b4b;
margin-top:
0;
font-size: 1.6rem;
border-bottom:
2px solid #f4f4f5;
padding-bottom:
12px;
}
.input-area {
display:
flex;
gap:
10px;
margin-bottom:
20px;
}
.input-area input {
flex:
1;
padding:
12px;
border:
2px solid #e4e4e7;
border-radius:
8px;
outline:
none;
font-size: 0.95rem;
}
.input-area input:focus {
border-color:
#6366f1;
}
.input-area button {
background: #6366f1;
color:
white;
border:
none;
padding:
0 20px;
border-radius:
8px;
font-weight: 600;
cursor:
pointer;
}
.input-area button:hover {
background: #4f46e5;
}
ul {
list-style:
none;
padding:
0;
margin:
0;
}
li {
display:
flex;
justify-content:
space-between;
align-items:
center;
padding:
14px;
background: #f9fafb;
margin-bottom:
8px;
border-radius:
8px;
border-left:
4px solid #6366f1;
}
.delete-btn {
background: transparent;
color:
#a1a1aa;
border:
none;
font-size: 1.1rem;
cursor:
pointer;
transition:
color 0.2s;
}
.delete-btn:hover {
color:
#ef4444;
}
.empty-msg {
text-align:
center;
color:
#71717a;
font-style: italic;
margin:
20px 0;
}
Day 4: Stopwatch
(Kronometre)
🛠️
Modern Signal Mimarisi
stopwatch.component.ts:
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-stopwatch',
standalone: true,
templateUrl: './stopwatch.component.html',
styleUrls: ['./stopwatch.component.css']
})
export class StopwatchComponent {
seconds = signal<number>(0);
isRunning = signal<boolean>(false);
private timerId: any = null;
// Saniyeyi
sürekli gözlemleyip formatlayan computed sinyal
formattedTime = computed(() => {
const totalSecs = this.seconds();
const mins = Math.floor(totalSecs / 60);
const secs = totalSecs % 60;
return `${mins < 10 ? '0' : ''}${mins}:${secs < 10 ? '0' : ''}${secs}`;
});
start() {
if (!this.isRunning()) {
this.isRunning.set(true);
this.timerId = setInterval(() => {
this.seconds.update(s => s + 1);
}, 1000);
}
}
stop() {
clearInterval(this.timerId);
this.isRunning.set(false);
}
reset() {
this.stop();
this.seconds.set(0);
}
}
stopwatch.component.html:
<div class="stopwatch-container">
<!-- Reaktif zaman gösterimi -->
<div class="display">{{ formattedTime() }}</div>
<div class="controls">
<button class="start-btn" (click)="start()" [disabled]="isRunning()">Başlat</button>
<button class="stop-btn" (click)="stop()" [disabled]="!isRunning()">Durdur</button>
<button class="reset-btn" (click)="reset()">Sıfırla</button>
</div>
</div>
🎨 CSS Tasarımı
stopwatch.component.css:
.stopwatch-container {
background: #111827;
padding:
40px;
border-radius:
24px;
text-align:
center;
max-width:
350px;
margin:
60px auto;
box-shadow:
0 20px 40px rgba(0,0,0,0.15);
}
.display {
font-family: 'Courier New', Courier, monospace;
font-size: 4rem;
color:
#10b981;
text-shadow:
0 0 10px rgba(16, 185, 129, 0.4);
font-weight: 700;
margin-bottom:
30px;
letter-spacing:
2px;
}
.controls {
display:
flex;
gap:
10px;
justify-content:
center;
}
button {
padding:
10px 18px;
border:
none;
font-weight: 600;
border-radius:
8px;
cursor:
pointer;
transition:
all 0.2s;
}
button:disabled {
opacity:
0.4;
cursor:
not-allowed;
}
.start-btn { background: #10b981; color: white; }
.stop-btn { background: #f59e0b; color: white; }
.reset-btn { background: #374151; color: white; }
Day 5: Guess the Number (Sayı Tahmin Oyunu)
🛠️
Modern Signal Mimarisi
guess.component.ts:
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-guess',
standalone: true,
templateUrl: './guess.component.html',
styleUrls: ['./guess.component.css']
})
export class GuessComponent {
secretNumber = signal<number>(Math.floor(Math.random() * 100) + 1);
hintMessage = signal<string>('1 ile 100 arasında bir sayı tuttum!');
hasWon = signal<boolean>(false);
currentGuess = signal<string>('');
onInputChange(event: Event) {
const value = (event.target as HTMLInputElement).value;
this.currentGuess.set(value);
}
checkGuess() {
const num = Number(this.currentGuess());
if (!num) return;
if (num === this.secretNumber()) {
this.hintMessage.set('🎉 Tebrikler! Tam isabet.');
this.hasWon.set(true);
} else if (num < this.secretNumber())
{
this.hintMessage.set('🔺 Daha büyük bir sayı girmelisin!');
} else {
this.hintMessage.set('🔻 Daha küçük bir sayı girmelisin!');
}
}
restart() {
this.secretNumber.set(Math.floor(Math.random() * 100) + 1);
this.hintMessage.set('Yeni bir sayı tuttum, tahmin et!');
this.hasWon.set(false);
this.currentGuess.set('');
}
}
guess.component.html:
<div class="game-container">
<h2>Sayı Tahmin Arenası</h2>
<p class="hint">{{
hintMessage() }}</p>
@if (!hasWon())
{
<div class="action-box">
<input type="number" [value]="currentGuess()" (input)="onInputChange($event)" placeholder="00">
<button (click)="checkGuess()">Dene</button>
</div>
} @else {
<button (click)="restart()" class="restart-btn">Tekrar Oyna</button>
}
</div>
🎨 CSS Tasarımı
guess.component.css:
.game-container {
max-width:
400px;
margin:
50px auto;
background: linear-gradient(135deg, #6ee7b7 0%, #3b82f6 100%);
padding:
40px 30px;
border-radius:
20px;
color:
white;
text-align:
center;
box-shadow:
0 12px 30px rgba(59, 130, 246, 0.3);
}
h2 { margin-top: 0; font-size: 1.8rem; }
.hint {
font-size: 1.1rem; min-height: 50px; font-weight: 500; }
.action-box {
display:
flex;
justify-content:
center;
gap:
10px;
}
input {
width:
80px;
padding:
12px;
font-size: 1.2rem;
border:
none;
border-radius:
10px;
text-align:
center;
color:
#1e3a8a;
font-weight: bold;
}
button {
background: #1e3a8a;
color:
white;
border:
none;
padding:
0 25px;
font-weight: 600;
border-radius:
10px;
cursor:
pointer;
}
.restart-btn {
background: white;
color:
#3b82f6;
padding:
12px 30px;
font-size: 1rem;
border-radius:
10px;
border:
none;
font-weight: 700;
cursor:
pointer;
}
Day 6: Tic Tac Toe (XOX Oyunu)
🛠️
Modern Signal Mimarisi
tic-tac-toe.component.ts:
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-tic-tac-toe',
standalone: true,
templateUrl: './tic-tac-toe.component.html',
styleUrls: ['./tic-tac-toe.component.css']
})
export class TicTacToeComponent {
board = signal<string[]>(Array(9).fill(''));
isXNext = signal<boolean>(true);
winner = computed(() => {
const b = this.board();
const lines = [, [3,4,5], [6,7,8],
, [1,4,7], [2,5,8],
, [2,4,6]
];
for (let line of lines) {
const [a, b_idx, c] = line;
if (b[a] && b[a] === b[b_idx] &&
b[a] === b[c]) return b[a];
}
return b.includes('') ? null : 'Berabere';
});
makeMove(idx: number) {
if (!this.board()[idx] && !this.winner()) {
this.board.update(b => {
const next = [...b];
next[idx] = this.isXNext() ? 'X' : 'O';
return next;
});
this.isXNext.update(val => !val);
}
}
reset() {
this.board.set(Array(9).fill(''));
this.isXNext.set(true);
}
}
tic-tac-toe.component.html:
<div class="xox-container">
<h3>Sıradaki Oyuncu: {{ isXNext()
? 'X' : 'O'
}}</h3>
<div class="grid">
@for (cell of board(); track $index) {
<button class="cell" (click)="makeMove($index)">{{ cell }}</button>
}
</div>
@if (winner()) {
<div class="overlay">
<h2>Sonuç: {{ winner()
}}</h2>
<button (click)="reset()">Yeniden
Başla</button>
</div>
}
</div>
🎨 CSS Tasarımı
tic-tac-toe.component.css:
.xox-container {
max-width:
320px;
margin:
40px auto;
text-align:
center;
position:
relative;
}
.grid {
display:
grid;
grid-template-columns: repeat(3, 1fr);
gap:
8px;
background: #e2e8f0;
padding:
8px;
border-radius:
12px;
}
.cell {
height:
90px;
background: white;
border:
none;
font-size: 2.5rem;
font-weight: bold;
color:
#0f172a;
border-radius:
8px;
cursor:
pointer;
}
.cell:hover { background: #f8fafc; }
.overlay {
position:
absolute;
top: 0; left: 0; right: 0; bottom:
0;
background: rgba(15, 23, 42, 0.95);
border-radius:
12px;
display:
flex;
flex-direction:
column;
justify-content:
center;
align-items:
center;
color:
white;
}
.overlay button {
background: #3b82f6;
color:
white;
border:
none;
padding:
10px 20px;
border-radius:
6px;
font-weight: 600;
cursor:
pointer;
}
Day 7: Dynamic Filter List (Dinamik Filtreli
Liste)
🛠️
Modern Signal Mimarisi
dynamic-list.component.ts:
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-dynamic-list',
standalone: true,
templateUrl: './dynamic-list.component.html',
styleUrls: ['./dynamic-list.component.css']
})
export class DynamicListComponent {
searchText = signal<string>('');
products = signal<string[]>(['Apple iPhone', 'Samsung Galaxy', 'MacBook Pro', 'Dell XPS', 'iPad Air']);
// Pipe yerine reaktif computed
sinyal ile veri süzme (Önerilen yöntem)
filteredProducts = computed(() => {
const text = this.searchText().toLowerCase();
return this.products().filter(p => p.toLowerCase().includes(text));
});
onSearch(event: Event) {
this.searchText.set((event.target as HTMLInputElement).value);
}
}
dynamic-list.component.html:
<div class="filter-box">
<input type="text" [value]="searchText()" (input)="onSearch($event)" placeholder="Cihaz modeli ara...">
<ul>
@for (prod of filteredProducts();
track prod) {
<li>🔍 {{ prod }}</li>
}
</ul>
</div>
🎨 CSS Tasarımı
dynamic-list.component.css:
.filter-box {
max-width:
400px;
margin:
40px auto;
background: white;
padding:
24px;
border-radius:
12px;
box-shadow:
0 4px 20px rgba(0,0,0,0.05);
}
input {
width:
100%;
padding:
12px;
border:
1px solid #cbd5e1;
border-radius:
8px;
font-size: 1rem;
box-sizing:
border-box;
}
ul {
padding:
0;
margin:
16px 0 0 0;
list-style:
none;
}
li {
padding:
12px;
border-bottom:
1px solid #f1f5f9;
color:
#334155;
}
Day 8: Paper Rock Scissors Game (Taş Kağıt Makas)
🛠️
Modern Signal Mimarisi
game.component.ts:
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-game',
standalone: true,
templateUrl: './game.component.html',
styleUrls: ['./game.component.css']
})
export class GameComponent {
choices = ['Taş', 'Kağıt', 'Makas'];
userChoice = signal<string | null>(null);
computerChoice = signal<string | null>(null);
result = signal<string>('');
play(choice: string) {
this.userChoice.set(choice);
const rand = this.choices[Math.floor(Math.random() * 3)];
this.computerChoice.set(rand);
const u = choice;
if (u === rand) this.result.set('Berabere!');
else if ((u==='Taş' && rand==='Makas') || (u==='Kağıt' && rand==='Taş') || (u==='Makas' && rand==='Kağıt')) {
this.result.set('Kazandın! 🎉');
} else {
this.result.set('Kaybettin! 😢');
}
}
}
game.component.html:
<div class="game-wrapper">
<div class="buttons">
<button (click)="play('Taş')">✊ Taş</button>
<button (click)="play('Kağıt')">✋ Kağıt</button>
<button (click)="play('Makas')">✌️ Makas</button>
</div>
@if (userChoice()) {
<div class="board">
<p>Sen: <strong>{{ userChoice() }}</strong> vs Bot: <strong>{{ computerChoice()
}}</strong></p>
<h2>{{ result() }}</h2>
</div>
}
</div>
🎨 CSS Tasarımı
game.component.css:
.game-wrapper { text-align: center; margin: 50px auto; max-width:
400px; }
.buttons { display: flex; gap: 15px; justify-content: center; }
.buttons button { font-size: 1.5rem; padding: 15px; border-radius: 12px; border: 2px solid #e2e8f0; background: white; cursor: pointer; }
.buttons button:hover { background: #f8fafc; }
.board { margin-top: 30px; background:
#f1f5f9; padding: 20px; border-radius: 12px; }
Day 9: Interactive Accordion
with Angular Animations (Animasyonlu SSS)
🛠️
Modern Signal Mimarisi
accordion.component.ts:
import { Component, signal } from '@angular/core';
import { trigger, state, style, transition, animate } from '@angular/animations';
@Component({
selector: 'app-accordion',
standalone: true,
animations: [
trigger('openClose', [
state('open', style({ height: '*', opacity: 1 })),
state('closed', style({ height: '0px', opacity: 0, overflow: 'hidden' })),
transition('open
<=> closed', [animate('250ms ease-in-out')])
])
],
templateUrl: './accordion.component.html',
styleUrls: ['./accordion.component.css']
})
export class AccordionComponent {
// Sinyal
dizisi içinde durum kontrolü
faqItems = signal([
{ q: 'Angular 19 Kararlı mı?', a: 'Evet, en son
performans iyileştirmelerini barındırır.', open: false },
{ q: 'Sinyaller
neden önemli?', a: 'Zoneless mimariye geçişi ve yüksek hızı
sağlarlar.', open: false }
]);
toggle(idx: number) {
this.faqItems.update(items => items.map((item, i) => i === idx
? { ...item, open: !item.open } : item));
}
}
accordion.component.html:
<div class="accordion">
@for (item of faqItems();
track item.q; let i = $index) {
<div class="item">
<div class="header" (click)="toggle(i)">{{ item.q }}</div>
<div class="body" [@openClose]="item.open
? 'open' : 'closed'">{{ item.a }}</div>
</div>
}
</div>
🎨 CSS Tasarımı
accordion.component.css:
.accordion { max-width: 500px; margin: 40px auto; }
.item { border: 1px solid #e2e8f0; margin-bottom: 8px; border-radius: 6px; background: white; }
.header { padding: 16px; font-weight: 600; cursor: pointer; background:
#f8fafc; }
.body { padding: 0 16px; color: #475569; line-height: 1.6; }
Day 10: Random Joke Generator (Rastgele Şaka
Üretici)
🛠️
Modern Signal Mimarisi
joke.component.ts:
import { Component, signal, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-joke',
standalone: true,
template: `
<div class="joke-card">
@if (joke()) {
<p><strong>{{ joke().setup }}</strong></p>
<p><em>{{
joke().punchline
}}</em></p>
}
<button
(click)="fetchJoke()">Şaka
Üret</button>
</div>
`,
styleUrls: ['./joke.component.css']
})
export class JokeComponent {
private http = inject(HttpClient);
joke = signal<any>(null);
fetchJoke() {
this.http.get('https://appspot.com')
.subscribe(res => this.joke.set(res));
}
}
🎨 CSS Tasarımı
joke.component.css:
.joke-card { max-width: 400px; margin: 50px auto; padding: 24px; border-radius: 12px; box-shadow: 0 10px 25px rgba(0,0,0,0.05); text-align: center; background: #fffdf5; border:
1px solid #fef08a; }
button { background: #eab308; color:
white; border: none; padding: 10px 20px; border-radius: 6px; cursor: pointer; font-weight: bold; }
Day 11: Infinite Scroll (Sonsuz Kaydırma)
🛠️
Modern Signal Mimarisi
scroll.component.ts:
import { Component, HostListener, signal, OnInit } from '@angular/core';
@Component({
selector: 'app-infinite-scroll',
standalone: true,
template: `
<div class="container">
@for (item of items(); track item) {
<div class="box">Gönderi
Blok İçeriği #{{ item }}</div>
}
</div>
`,
styleUrls: ['./scroll.component.css']
})
export class InfiniteScrollComponent implements OnInit {
items = signal<number[]>([]);
ngOnInit() { this.load(); }
load() {
this.items.update(arr => {
const next = [...arr];
for (let i = 0; i < 8; i++) next.push(next.length + 1);
return next;
});
}
@HostListener('window:scroll', [])
onScroll() {
if ((window.innerHeight + window.scrollY)
>= document.body.offsetHeight - 50) {
this.load();
}
}
}
🎨 CSS Tasarımı
scroll.component.css:
.container { max-width: 600px; margin: 20px auto; }
.box {
background: white; margin-bottom:
16px; padding: 40px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.03); border: 1px solid #e2e8f0; }
Day 12: Analog Clock
(Analog Saat)
🛠️
Modern Signal Mimarisi
clock.component.ts:
import { Component, OnInit, OnDestroy, signal } from '@angular/core';
@Component({
selector: 'app-analog-clock',
standalone: true,
templateUrl: './clock.component.html',
styleUrls: ['./clock.component.css']
})
export class AnalogClockComponent implements OnInit, OnDestroy
{
hTransform = signal<string>('rotate(0deg)');
mTransform = signal<string>('rotate(0deg)');
sTransform = signal<string>('rotate(0deg)');
private id: any;
ngOnInit() {
this.id = setInterval(() => {
const n = new Date();
this.sTransform.set(`rotate(${n.getSeconds() * 6}deg)`);
this.mTransform.set(`rotate(${n.getMinutes() * 6}deg)`);
this.hTransform.set(`rotate(${(n.getHours() % 12) * 30 + n.getMinutes() * 0.5}deg)`);
}, 1000);
}
ngOnDestroy() { clearInterval(this.id); }
}
clock.component.html:
<div class="clock-circle">
<div class="line hr" [style.transform]="hTransform()"></div>
<div class="line mn" [style.transform]="mTransform()"></div>
<div class="line sc" [style.transform]="sTransform()"></div>
<div class="pin"></div>
</div>
🎨 CSS Tasarımı
clock.component.css:
.clock-circle { width: 200px; height: 200px; border: 8px solid #1e293b; border-radius: 50%; position: relative; margin: 50px auto; background: #fff; }
.line { position: absolute; bottom: 50%; left: 50%; transform-origin: bottom center; border-top-left-radius: 4px; border-top-right-radius: 4px; }
.hr { width:
6px; height: 50px; background: #1e293b; margin-left:
-3px; }
.mn { width: 4px; height: 75px; background: #64748b; margin-left:
-2px; }
.sc { width:
2px; height: 85px; background: #ef4444; margin-left:
-1px; }
.pin { width: 12px; height: 12px; background: #1e293b; border-radius:
50%; position: absolute; top: 50%; left: 50%; margin: -6px 0 0 -6px; }
Day 13: Feedback Form (Şablon Tabanlı Sinyal Formu)
🛠️
Modern Signal Mimarisi
feedback.component.ts:
import { Component, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-feedback',
standalone: true,
imports: [FormsModule],
templateUrl: './feedback.component.html',
styleUrls: ['./feedback.component.css']
})
export class FeedbackComponent {
// Form
alanlarını nesne halinde bir sinyalde tutma
comment = signal<string>('');
submitForm() {
console.log('Geri bildirim:', this.comment());
}
}
feedback.component.html:
<div class="form-container">
<textarea [value]="comment()" (input)="comment.set($any($event.target).value)" placeholder="Görüşleriniz..."></textarea>
<button (click)="submitForm()" [disabled]="!comment()">Gönder</button>
</div>
🎨 CSS Tasarımı
feedback.component.css:
.form-container { max-width: 400px; margin: 40px auto; }
textarea { width: 100%; height: 100px; padding: 12px; border-radius: 8px; border: 1px solid #ccc; box-sizing: border-box; }
button { width: 100%; margin-top: 10px; padding: 12px; background:
#0284c7; color: white; border: none; border-radius:
8px; cursor: pointer; }
button:disabled { background: #cbd5e1; }
Day 14: Contact Form using ReactiveFormsModule
(Reaktif Sinyal Formu)
🛠️
Modern Signal Mimarisi
contact.component.ts:
import { Component, inject } from '@angular/core';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
@Component({
selector: 'app-contact',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="fg" (ngSubmit)="send()" class="box">
<input
formControlName="mail" placeholder="E-mail">
<button
type="submit" [disabled]="fg.invalid">İletişime
Geç</button>
</form>
`,
styleUrls: ['./contact.component.css']
})
export class ContactComponent {
private fb = inject(FormBuilder);
fg = this.fb.group({
mail: ['', [Validators.required, Validators.email]]
});
send() { if (this.fg.valid) console.log(this.fg.value); }
}
🎨 CSS Tasarımı
contact.component.css:
.box { max-width: 400px; margin: 40px auto; display: flex; flex-direction: column; gap: 10px; }
input { padding: 12px; border-radius: 6px; border: 1px solid #cbd5e1; }
button { padding: 12px; background:
#10b981; color: white; border: none; border-radius:
6px; cursor: pointer; }
Day 15: Custom Directives (Özel Sinyal Direktifleri)
🛠️
Modern Signal Mimarisi
highlight.directive.ts:
import { Directive, ElementRef, HostListener, input, inject } from '@angular/core';
@Directive({
selector: '[appHighlight]',
standalone: true
})
export class HighlightDirective {
private el = inject(ElementRef);
// Angular 19 modern signal input yapısı
appHighlight = input<string>('yellow');
@HostListener('mouseenter') onIn() {
this.el.nativeElement.style.backgroundColor = this.appHighlight();
}
@HostListener('mouseleave') onOut() {
this.el.nativeElement.style.backgroundColor = '';
}
}
🎨 CSS Tasarımı
Herhangi bir ekstra CSS gerektirmez; direktif DOM
ögesinin arka plan rengine doğrudan reaktif müdahale eder.
Day 16: Calculator (Hesap
Makinesi)
🛠️
Modern Signal Mimarisi
calculator.component.ts:
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-calculator',
standalone: true,
templateUrl: './calculator.component.html',
styleUrls: ['./calculator.component.css']
})
export class CalculatorComponent {
displayValue = signal<string>('');
press(key: string) { this.displayValue.update(v => v + key); }
clear() { this.displayValue.set(''); }
calc() {
try {
this.displayValue.set(new Function(`return ${this.displayValue()}`)().toString());
} catch { this.displayValue.set('Hata'); }
}
}
calculator.component.html:
<div class="calc-shell">
<div class="screen">{{ displayValue() || '0' }}</div>
<div class="btns">
<button (click)="press('1')">1</button>
<button (click)="press('+')">+</button>
<button (click)="calc()">=</button>
<button (click)="clear()">C</button>
</div>
</div>
🎨 CSS Tasarımı
calculator.component.css:
.calc-shell { max-width: 240px; margin: 40px auto; background: #334155; padding:
16px; border-radius: 12px; }
.screen { background: #0f172a; color: #38bdf8; padding: 16px; text-align: right; font-size: 1.5rem; font-family: monospace; border-radius: 8px; margin-bottom: 12px; }
.btns { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; }
.btns button { padding: 16px; font-size: 1.2rem; border-radius: 6px; border: none; cursor: pointer;
}
Day 17: Currency Converter
(Döviz Dönüştürücü)
🛠️
Modern Signal Mimarisi
currency.component.ts:
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-currency',
standalone: true,
template: `
<div class="converter">
<input
type="number" [value]="amt()" (input)="amt.set(+$any($event.target).value)">
<p>Dolar
Karşılığı (34.50 TL Oranıyla): <strong>{{ tryTotal() }} TRY</strong></p>
</div>
`,
styleUrls: ['./currency.component.css']
})
export class CurrencyComponent {
amt = signal<number>(1);
tryTotal = computed(() => this.amt() * 34.50);
}
🎨 CSS Tasarımı
currency.component.css:
.converter { max-width: 400px; margin: 40px auto; padding: 20px; border: 1px solid #e2e8f0; border-radius: 8px; }
input { padding: 8px; font-size: 1rem; width: 100%; box-sizing: border-box; }
Day 18: Weather (Hava
Durumu)
🛠️
Modern Signal Mimarisi
weather.component.ts:
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-weather',
standalone: true,
template: `
<div class="weather-box">
<button
(click)="check()">Sıcaklık
Ölç</button>
<h2>Derece:
{{ temp() }}°C {{ emoji() }}</h2>
</div>
`,
styleUrls: ['./weather.component.css']
})
export class WeatherComponent {
temp = signal<number>(20);
emoji = computed(() => this.temp() > 25 ? '☀️' : '❄️');
check() { this.temp.set(Math.floor(Math.random()
* 40));
}
}
🎨 CSS Tasarımı
weather.component.css:
.weather-box { max-width: 300px; margin: 40px auto; text-align: center; background: #e0f2fe; padding:
20px; border-radius: 12px; }
Day 19: Emoji Search &
Theme Picker (Tema Seçimli
Emoji Motoru)
🛠️
Modern Signal Mimarisi
emoji.component.ts:
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-emoji',
standalone: true,
template: `
<div [class]="themeClass()">
<button
(click)="toggle()">Tema
Değiştir</button>
<p>Aktif
Mod Sinyal Kontrolü</p>
</div>
`,
styleUrls: ['./emoji.component.css']
})
export class EmojiComponent {
themeClass = signal<string>('light-mode');
toggle() { this.themeClass.update(t => t === 'light-mode' ? 'dark-mode' : 'light-mode');
}
}
🎨 CSS Tasarımı
emoji.component.css:
.light-mode { background: #fff; color: #000; padding: 30px; }
.dark-mode { background: #1e293b; color: #fff; padding: 30px; }
Day 20: Crypto Charts (Kripto Para Grafikleri)
🛠️
Modern Signal Mimarisi
crypto.component.ts:
import { Component, signal, effect } from '@angular/core';
@Component({
selector: 'app-crypto',
standalone: true,
template: `<div class="chart">Güncel
BTC Değeri Sinyali: $ {{ price() }}</div>`,
styleUrls: ['./crypto.component.css']
})
export class CryptoComponent {
price = signal<number>(92000);
constructor() {
// Sinyal
değiştiğinde grafik loglayan yan etki (effect)
mekanizması
effect(() => {
console.log(`Grafik güncellendi: ${this.price()}`);
});
}
}
🎨 CSS Tasarımı
crypto.component.css:
.chart { border: 2px dashed #f59e0b; padding: 30px; text-align: center; margin: 40px auto; max-width: 400px; font-weight: bold; }
Day 21: Calendar (Takvim
Uygulaması)
🛠️
Modern Signal Mimarisi
calendar.component.ts:
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-calendar',
standalone: true,
template: `
<div class="cal">
@for(day of matrix(); track day) {
<div class="day">{{ day }}</div>
}
</div>
`,
styleUrls: ['./calendar.component.css']
})
export class CalendarComponent {
matrix = signal<number[]>(Array.from({ length: 30 }, (_, i) => i + 1));
}
🎨 CSS Tasarımı
calendar.component.css:
.cal { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; max-width: 350px; margin: 40px auto; }
.day { padding: 12px; background: #f1f5f9; text-align:
center; border-radius: 4px; }
Day 22: Checkers (Dama
Oyunu)
🛠️
Modern Signal Mimarisi
checkers.component.ts:
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-checkers',
standalone: true,
template: `
<div class="grid8x8">
@for(cell of cells(); track $index) {
<div class="square" [style.background]="( $index
+ Math.floor($index/8) ) %
2 === 0 ? '#f0d9b5' : '#b58863'"></div>
}
</div>
`,
styleUrls: ['./checkers.component.css']
})
export class CheckersComponent {
cells = signal<number[]>(Array(64).fill(0));
protected readonly Math = Math;
}
🎨 CSS Tasarımı
checkers.component.css:
.grid8x8 { display: grid; grid-template-columns: repeat(8, 1fr); width: 320px; height: 320px; margin: 40px auto; border: 4px solid #334155; }
.square { width: 40px; height: 40px; }
Day 23: Password Security
(Şifre Güvenliği ve Üretici)
🛠️
Modern Signal Mimarisi
password.component.ts:
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-password',
standalone: true,
template: `
<div class="pwd-box">
<input
[value]="pass()"
(input)="pass.set($any($event.target).value)" type="text">
<p>Durum:
<strong>{{ strength()
}}</strong></p>
</div>
`,
styleUrls: ['./password.component.css']
})
export class PasswordComponent {
pass = signal<string>('');
strength = computed(() => this.pass().length > 8 ? 'Güçlü Şifre'
: 'Zayıf Şifre');
}
🎨 CSS Tasarımı
password.component.css:
.pwd-box { max-width: 400px; margin: 40px auto; text-align: center; }
input { width: 100%; padding: 10px; border-radius: 6px; border: 1px solid #cbd5e1; }
Day 24: Minesweeper (Mayın
Tarlası)
🛠️
Modern Signal Mimarisi
minesweeper.component.ts:
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-minesweeper',
standalone: true,
template: `
<div class="mine-field">
@for(m of field(); track $index) {
<button class="cell">?</button>
}
</div>
`,
styleUrls: ['./minesweeper.component.css']
})
export class MinesweeperComponent {
field = signal<number[]>(Array(16).fill(0)); // 4x4 mini alan
}
🎨 CSS Tasarımı
minesweeper.component.css:
.mine-field { display:
grid; grid-template-columns:
repeat(4, 1fr); gap: 6px; width: 160px; margin: 40px auto; }
.cell { width: 35px; height: 35px; background:
#cbd5e1; border: none;
font-weight: bold; cursor: pointer; }
Day 25: Advanced Todo
(Gelişmiş Yapılacaklar Listesi)
🛠️
Modern Signal Mimarisi
adv-todo.component.ts:
import { Component, signal, effect } from '@angular/core';
@Component({
selector: 'app-adv-todo',
standalone: true,
template: `<div class="box">Gelişmiş
Sinyalli LocalStorage Todo
Motoru Aktif</div>`,
styleUrls: ['./adv-todo.component.css']
})
export class AdvTodoComponent {
todos = signal<string[]>(JSON.parse(localStorage.getItem('adv_todos') || '[]'));
constructor() {
// Sinyal her
değiştiğinde localstorage'a otomatik yazar
effect(() => {
localStorage.setItem('adv_todos', JSON.stringify(this.todos()));
});
}
}
🎨 CSS Tasarımı
adv-todo.component.css:
.box { max-width: 400px; margin: 40px auto; padding: 20px; background:
#fafafa; border-radius: 8px; }
Day 26: User Management App
(Kullanıcı Paneli)
🛠️
Modern Signal Mimarisi
usercard.component.ts:
import { Component, input, output } from '@angular/core';
@Component({
selector: 'app-user-card',
standalone: true,
template: `
<div class="u-card">
<h4>{{ username() }}</h4>
<button
(click)="onRemove.emit()">Kaldır</button>
</div>
`,
styleUrls: ['./usercard.component.css']
})
export class UserCardComponent {
// Angular 19 modern girdi ve çıktı sinyal mimarileri
username = input.required<string>();
onRemove = output<void>();
}
🎨 CSS Tasarımı
usercard.component.css:
.u-card { display:
flex; justify-content: space-between; background: #fff; padding:
12px; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.05); margin-bottom: 8px; }
button { background: #ef4444; color:
white; border: none; padding: 4px 8px; border-radius: 4px; cursor: pointer; }
Day 27: Resume Builder (CV
Oluşturucu)
🛠️
Modern Signal Mimarisi
resume.component.ts:
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-resume',
standalone: true,
template: `
<div class="cv-layout">
<input
[value]="fullName()"
(input)="fullName.set($any($event.target).value)" placeholder="Ad
Soyad">
<div class="preview"><h1>{{
fullName() }}</h1></div>
</div>
`,
styleUrls: ['./resume.component.css']
})
export class ResumeComponent {
fullName = signal<string>('John Doe');
}
🎨 CSS Tasarımı
resume.component.css:
.cv-layout { max-width: 500px; margin: 40px auto; }
input { padding: 10px; width: 100%; box-sizing: border-box; margin-bottom: 20px; }
.preview { border: 2px solid #000; padding: 30px; background: white; }
Day 28: Quiz App (Sınav Uygulaması)
🛠️
Modern Signal Mimarisi
quiz.component.ts:
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-quiz',
standalone: true,
template: `
<div class="quiz-box">
<h3>Soru
Puanı Sinyal Takibi</h3>
<button
(click)="addScore()">Doğru
Cevap (+10 Puan)</button>
<h2>Toplam
Skor: {{ score() }}</h2>
</div>
`,
styleUrls: ['./quiz.component.css']
})
export class QuizComponent {
score = signal<number>(0);
addScore() { this.score.update(s => s + 10); }
}
🎨 CSS Tasarımı
quiz.component.css:
.quiz-box { max-width: 400px; margin: 50px auto; padding: 24px; text-align: center; background:
#f8fafc; border-radius: 12px; }
button { background: #4f46e5; color:
white; padding: 12px; border: none; border-radius:
6px; cursor: pointer; }
Day 29: Typing Game -
Keyboard Masters (Hız Yarışı)
🛠️
Modern Signal Mimarisi
typing.component.ts:
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-typing',
standalone: true,
template: `
<div class="wrapper">
<p>Yazılacak
Metin: <strong>{{ target()
}}</strong></p>
<input
[value]="typed()"
(input)="typed.set($any($event.target).value)" placeholder="Yazmaya
başla...">
@if(isCompleted())
{ <p class="success">Harika!
Doğru tamamlandı.</p> }
</div>
`,
styleUrls: ['./typing.component.css']
})
export class TypingComponent {
target = signal<string>('angular');
typed = signal<string>('');
isCompleted = computed(() => this.target() === this.typed().trim().toLowerCase());
}
🎨 CSS Tasarımı
typing.component.css:
.wrapper { max-width: 400px; margin: 40px auto; text-align: center; }
input { width: 100%; padding: 12px; font-size: 1.1rem; border-radius: 8px; border: 2px solid #cbd5e1; box-sizing: border-box; }
.success { color: #10b981; font-weight:
bold; margin-top: 10px; }
Day 30: Portfolio (Geliştirici Portfolyosu)
🛠️
Modern Signal Mimarisi
portfolio.component.ts:
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-portfolio',
standalone: true,
template: `
<div class="portfolio-container">
<header>
<h1>{{ devName() }} - Proje Vitrini</h1>
<p>30
günlük Angular 19 Sinyal maratonunda üretilen
projelerin merkezi yönetim üssü.</p>
</header>
<div class="showcase">
<div class="p-card">🚀 Proje Sayacı Sinyali: {{ totalProjects() }}
Hazır</div>
</div>
</div>
`,
styleUrls: ['./portfolio.component.css']
})
export class PortfolioComponent {
devName = signal<string>('Usta Angular Geliştiricisi');
totalProjects = signal<number>(30);
}
🎨 CSS Tasarımı
portfolio.component.css:
.portfolio-container { max-width: 800px; margin: 0 auto; padding: 40px 20px; font-family: system-ui, sans-serif; }
header { text-align: center; margin-bottom: 40px; background: linear-gradient(135deg, #1e1b4b 0%, #312e81 100%); color: white; padding: 40px; border-radius: 16px; }
header h1 { margin: 0 0 10px 0; font-size: 2.2rem; }
header p { margin: 0; opacity:
0.8;
font-size: 1rem; }
.showcase { display: grid; grid-template-columns: 1fr; gap: 20px; }
.p-card { background: white; border: 1px solid #e2e8f0; padding: 24px; border-radius: 12px; font-weight: 600; text-align: center; box-shadow: 0 4px 12px rgba(0,0,0,0.02); color: #4f46e5; }
Angular 19
projelerinizi bilgisayarınızda derlerken (build veya serve adımlarında) karşınıza çıkabilecek TypeScript katı kurallarını (strict mode) nasıl
güvenli bir şekilde yöneteceğinizi ve ardından portfolyo projeniz için çok
kritik olan modern RxJS'ten Signal'e (toSignal) dönüşüm
entegrasyonunu aşağıda detaylandırdım.
BÖLÜM 1:
TYPESCRIPT KATI KURALLARINI (STRICT MODE) YÖNETMEK
Yeni bir Angular 19
projesi oluşturduğunuzda, tsconfig.json dosyasında
kod kalitesini artırmak için katı kurallar varsayılan olarak açık gelir. Bu
durum, özellikle hızlı prototip geliştirirken veya öğrenme aşamasında derleme
hatalarına (Compilation Errors) yol
açabilir.
Bu kuralları tamamen kapatmak kod güvenliğini
düşüreceği için, onları "nasıl aşacağımızı ve kod içinde nasıl doğru
temizleyeceğimizi" bilmek en doğrusudur.
1. strictNullChecks (Boş/Null Değer Kontrolü) Hatası
Sorun: Bir değişkenin veya sinyalin başlangıçta null veya undefined olabilme
ihtimali varsa ve siz direkt kullanmaya çalışırsanız TypeScript
hata verir.
·
user = signal<User>(null); // Hata: 'null' tipi 'User' tipine atanamaz.
·
user = signal<User>(null!);
·
user = signal<User | null>(null);
2. noImplicitAny (Gizli 'any'
Tipi) Hatası
Sorun: Bir fonksiyon parametresine veya değişkene
açıkça bir tip belirtmediğinizde TypeScript bunun any (her şey olabilir) olduğunu tahmin eder ve katı
modda hata fırlatır.
·
updateSearch(event) { // Hata: 'event' parametresi gizli bir 'any' tipine sahip.
·
this.searchText.set(event.target.value);
·
}
·
updateSearch(event: Event) {
·
const inputElement = event.target as HTMLInputElement;
·
this.searchText.set(inputElement.value);
·
}
3. tsconfig.json Üzerinden Kuralları Esnetmek (Derleyiciyi Rahatlatmak)
Eğer öğrenme sürecinde bu hatalarla vakit
kaybetmek istemiyorsanız, projenizin kök dizinindeki tsconfig.json (veya tsconfig.app.json) dosyasını açarak katı mod ayarlarını
esnetebilirsiniz:
{
"compilerOptions": {
"strict": false,
/* Tüm katı kuralları genel olarak kapatır */
"strictNullChecks": false,
/* Null ve undefined kontrollerini
gevşetir */
"noImplicitAny": false,
/* Açıkça tip
belirtilmeyen yerlerde hata vermez */
"strictPropertyInitialization": false /* Constructor içinde değişken başlatma
zorunluluğunu kaldırır */
}
}
Not: Profesyonel canlı (production)
projelerinde bu ayarların true kalması,
çalışma zamanı (runtime) hatalarını engellemek için
şiddetle tavsiye edilir.
BÖLÜM 2:
MODERN RxJS'TEN SIGNAL'E DÖNÜŞÜM (toSignal) ENTEGRASYONU
Portfolyo projenizde (Day
30), dış dünyadan (örneğin bir kripto fiyat API'sinden
veya hava durumu servisinden) RxJS Observable yapısıyla akan asenkron verileri, Angular 19 bileşenlerinizde en modern ve performanslı
şekilde Signal olarak tüketmeniz gerekir.
Bunu yapmak için @angular/core/rxjs-interop paketinden
gelen toSignal fonksiyonunu kullanırız. Bu sayede HTML
şablonunda async pipe kullanma
zorunluluğu ortadan kalkar ve bileşen tamamen Zoneless
(Tetikleyicisiz/Doğal reaktif) çalışabilir.
🏗️
Gerçek Zamanlı Veri Akış Servisi Tasarımı
Öncelikle portfolyonuzda sergileyeceğiniz,
saniyede bir rastgele kripto (BTC) fiyatı üreten (gerçek hayatta WebSockets veya Long-Polling API'yi temsil eden) bir servis yazalım.
crypto-data.service.ts:
import { Injectable } from '@angular/core';
import { Observable, timer } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class CryptoDataService {
// RxJS Observable: Saniyede bir
tetiklenir ve 90.000$ ile 95.000$ arası rastgele fiyat üretir
getBtcPriceStream(): Observable<number> {
return timer(0, 1000).pipe(
map(() => Math.floor(Math.random() * (95000 - 90000 + 1)) + 90000)
);
}
}
💻 Portfolyo
Vitrini İçin Entegrasyon Bileşeni
Şimdi bu asenkron RxJS
akışını, bileşen içinde toSignal ile reaktif bir Sinyale dönüştürelim ve computed sinyallerle zenginleştirelim.
portfolio-showcase.component.ts:
import { Component, inject, computed } from '@angular/core';
import { CommonModule } from '@angular/common';
import { toSignal } from '@angular/core/rxjs-interop';
import { CryptoDataService } from './crypto-data.service';
@Component({
selector: 'app-portfolio-showcase',
standalone: true,
imports: [CommonModule],
templateUrl: './portfolio-showcase.component.html',
styleUrls: ['./portfolio-showcase.component.css']
})
export class PortfolioShowcaseComponent {
private cryptoService = inject(CryptoDataService);
// 1. RxJS Akışını Sinyale Dönüştürme
// 'initialValue' belirtmek, strict mode altında şablonda null
kontrolü yapma zorunluluğunu aşar.
btcPriceSignal = toSignal(this.cryptoService.getBtcPriceStream(), {
initialValue: 90000
});
// 2.
Türetilmiş Sinyal (Computed Signal)
// Gelen ham
fiyatı yerel para birimine (TRY) çeviren reaktif hesaplama
tryPriceSignal = computed(() => {
const currentUsd = this.btcPriceSignal();
const exchangeRate = 34.50; // Örnek Dolar
Kuru
return currentUsd * exchangeRate;
});
// 3. Durum
Sinyali
// Fiyatın
kritik bir eşiği geçip geçmediğini denetleyen koşul
isBullMarket = computed(() => this.btcPriceSignal() > 93000);
}
🎨 Portfolyo
Vitrini Arayüzü ve CSS Tasarımı
portfolio-showcase.component.html:
<div class="portfolio-widget">
<div class="widget-header">
<h3>📈 Canlı RxJS ➔ Signal Entegrasyon Vitrini</h3>
<span class="badge" [class.bull]="isBullMarket()">
{{ isBullMarket()
? 'Boğa Piyasası' : 'Stabil
Piyasa' }}
</span>
</div>
<div class="price-cards">
<div class="card usd">
<label>Bitcoin (USD)</label>
<!-- Sinyaller normal bir fonksiyon
gibi () ile çağrılır, async pipe
gerekmez -->
<h2>$ {{ btcPriceSignal() |
number:'1.0-0' }}</h2>
</div>
<div class="card try">
<label>Bitcoin (TRY)</label>
<h2>₺ {{ tryPriceSignal()
| number:'1.0-0' }}</h2>
</div>
</div>
<p class="footer-note">*Bu panel RxJS Observable
akışını toSignal() ile anlık tüketmektedir.</p>
</div>
portfolio-showcase.component.css:
.portfolio-widget {
background: #ffffff;
border-radius:
16px;
padding:
24px;
box-shadow:
0 10px 25px rgba(0, 0, 0, 0.05);
border:
1px solid #e2e8f0;
max-width:
550px;
margin:
30px auto;
font-family: system-ui, -apple-system, sans-serif;
}
.widget-header {
display:
flex;
justify-content:
space-between;
align-items:
center;
border-bottom:
2px solid #f1f5f9;
padding-bottom:
16px;
margin-bottom:
20px;
}
.widget-header h3 {
margin:
0;
color:
#0f172a;
font-size: 1.1rem;
}
.badge {
padding:
6px 12px;
border-radius:
9999px;
font-size: 0.75rem;
font-weight: 700;
background: #64748b;
color:
white;
transition:
background 0.3s ease;
}
.badge.bull {
background: #10b981;
/* Yeşil */
animation:
pulse 2s infinite;
}
.price-cards {
display:
grid;
grid-template-columns: 1fr 1fr;
gap:
16px;
}
.card {
padding:
16px;
border-radius:
12px;
color:
white;
}
.card label {
font-size: 0.8rem;
text-transform:
uppercase;
letter-spacing:
0.5px;
opacity:
0.9;
display:
block;
margin-bottom:
6px;
}
.card h2
{
margin:
0;
font-size: 1.6rem;
font-weight: 700;
}
.usd {
background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
}
.try {
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
}
.footer-note {
font-size: 0.75rem;
color:
#64748b;
text-align:
center;
margin-top:
16px;
font-style: italic;
}
@keyframes pulse {
0% { transform:
scale(1); }
50% { transform:
scale(1.05); }
100% { transform:
scale(1); }
}