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ı

  1. Visual Studio Code Kurulumu: Kodlarımızı rahatça yazmak, renklendirmek ve terminali kullanmak için resmi web sitesinden VS Code indirilir.
  2. VS Code Eklentileri: Angular dil desteği (Angular Language Service) ve kod kısayolları için popüler uzantılar yüklenir.
  3. Node.js ve Angular 19 Kurulumu: JavaScript kodlarının bilgisayarda yerel olarak çalışmasını sağlayan Node.js kurulur. Ardından terminal açılarak global Angular CLI yüklenir:

4.  npm install -g @angular/cli

  1. PowerShell Politikası Hatalarını Çözmek: Windows üzerinde betik çalıştırma kısıtlamasını kaldırmak için PowerShell yönetici olarak açılır ve şu komut yürütülür:

6.  Set-ExecutionPolicy RemoteSigned -Scope CurrentUser

  1. İlk Angular Projesini Oluşturmak: Terminal üzerinden yeni bir proje ayağa kaldırılır:

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); }

}