Komponent Yapısı ve Yaşam Döngüsü
Bu bölümde, Angular komponentlerinin yapısını ve yaşam döngüsünü detaylı olarak ele alacağız. Komponentler, Angular uygulamalarının temel yapı taşlarıdır ve kullanıcı arayüzünün farklı bölümlerini oluşturmak için kullanılırlar.
Angular Komponentleri Nedir?
Angular komponentleri, kullanıcı arayüzünün bağımsız ve yeniden kullanılabilir parçalarıdır. Her komponent, HTML şablonu, CSS stilleri ve TypeScript kodu içerir. Komponentler, veri bağlama (data binding), olay dinleme (event listening) ve yaşam döngüsü kancaları (lifecycle hooks) gibi özelliklere sahiptir.
Komponentler, Angular uygulamalarının modüler ve bakımı kolay olmasını sağlar. Bir komponent, kendi işlevselliğine sahip olabilir ve diğer komponentlerle etkileşimde bulunabilir.
Komponent Oluşturma
Angular CLI kullanarak yeni bir komponent oluşturmak için, aşağıdaki komutu kullanabilirsiniz:
ng generate component features/product/product-list
Bu komut, features/product/
klasöründe product-list
adında yeni bir komponent oluşturur. Oluşturulan dosyalar şunlardır:
- product-list.component.ts: Komponentin TypeScript kodu.
- product-list.component.html: Komponentin HTML şablonu.
- product-list.component.scss: Komponentin SCSS stilleri.
- product-list.component.spec.ts: Komponentin test dosyası.
Komponent Dekoratörü
Komponent sınıfı, @Component
dekoratörü ile işaretlenir. Bu dekoratör, komponentin meta verilerini tanımlar:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.scss']
})
export class ProductListComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}
@Component
dekoratörü, aşağıdaki özelliklere sahiptir:
- selector: Komponentin HTML etiketini tanımlar. Bu etiket, komponenti diğer şablonlarda kullanmak için kullanılır.
- templateUrl: Komponentin HTML şablonunun yolunu belirtir.
- styleUrls: Komponentin stil dosyalarının yollarını belirtir.
Alternatif olarak, HTML şablonu ve stiller doğrudan dekoratörde tanımlanabilir:
@Component({
selector: 'app-product-list',
template: `
<div class="product-list">
<h2>Ürün Listesi</h2>
<div *ngFor="let product of products">
{{ product.name }}
</div>
</div>
`,
styles: [`
.product-list {
margin: 20px;
}
h2 {
color: #333;
}
`]
})
Komponent Sınıfı
Komponent sınıfı, komponentin davranışını ve verilerini tanımlar. Bu sınıf, özellikler (properties) ve metodlar içerebilir:
export class ProductListComponent implements OnInit {
products: Product[] = [];
loading = false;
error: string | null = null;
constructor(private productService: ProductService) { }
ngOnInit(): void {
this.loadProducts();
}
loadProducts(): void {
this.loading = true;
this.productService.getProducts()
.subscribe(
products => {
this.products = products;
this.loading = false;
},
error => {
this.error = 'Ürünler yüklenirken bir hata oluştu.';
this.loading = false;
console.error('Ürünler yüklenirken hata:', error);
}
);
}
trackByProductId(index: number, product: Product): number {
return product.id;
}
}
Bu örnekte, ProductListComponent
sınıfı, ürünleri yüklemek ve görüntülemek için gerekli özellikleri ve metodları içerir.
Komponent Şablonu
Komponent şablonu, komponentin görsel yapısını tanımlar. Şablon, HTML, Angular direktifleri ve veri bağlama ifadeleri içerebilir:
<div class="product-list-container">
<h2>Ürün Listesi</h2>
<div *ngIf="loading" class="loading-spinner">
<mat-spinner></mat-spinner>
</div>
<div *ngIf="error" class="error-message">
{{ error }}
</div>
<div *ngIf="!loading && !error" class="product-grid">
<div *ngFor="let product of products; trackBy: trackByProductId" class="product-card">
<img [src]="product.imageUrl" [alt]="product.name" class="product-image">
<div class="product-info">
<h3>{{ product.name }}</h3>
<p>{{ product.description }}</p>
<div class="product-price">{{ product.price | currency:'TRY':'symbol':'1.2-2' }}</div>
<button mat-raised-button color="primary" (click)="addToCart(product)">Sepete Ekle</button>
</div>
</div>
</div>
<div *ngIf="!loading && !error && products.length === 0" class="no-products">
Ürün bulunamadı.
</div>
</div>
Bu şablonda, çeşitli Angular özellikleri kullanılmıştır:
- Yapısal Direktifler:
*ngIf
ve *ngFor
gibi yapısal direktifler, DOM’un yapısını değiştirmek için kullanılır.
- Özellik Bağlama:
[src]
ve [alt]
gibi özellik bağlamaları, HTML özelliklerini komponent özelliklerine bağlamak için kullanılır.
- Olay Bağlama:
(click)
gibi olay bağlamaları, kullanıcı etkileşimlerini komponent metodlarına bağlamak için kullanılır.
- İnterpolasyon:
{{ product.name }}
gibi interpolasyon ifadeleri, komponent özelliklerini şablonda görüntülemek için kullanılır.
- Pipe’lar:
| currency
gibi pipe’lar, veriyi görüntülenmeden önce dönüştürmek için kullanılır.
Komponent Stilleri
Komponent stilleri, komponentin görünümünü tanımlar. Angular, stilleri kapsülleyerek, bir komponentin stillerinin diğer komponentleri etkilemesini önler:
.product-list-container {
padding: 20px;
h2 {
font-size: 24px;
margin-bottom: 20px;
color: #333;
}
.loading-spinner {
display: flex;
justify-content: center;
margin: 50px 0;
}
.error-message {
color: #f44336;
padding: 10px;
margin: 20px 0;
background-color: #ffebee;
border-radius: 4px;
}
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
}
.product-card {
border: 1px solid #ddd;
border-radius: 4px;
overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease;
&:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.product-image {
width: 100%;
height: 200px;
object-fit: cover;
}
.product-info {
padding: 15px;
h3 {
margin-top: 0;
margin-bottom: 10px;
font-size: 18px;
}
p {
color: #666;
margin-bottom: 15px;
height: 60px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
.product-price {
font-size: 20px;
font-weight: bold;
color: #2196f3;
margin-bottom: 15px;
}
}
}
.no-products {
text-align: center;
padding: 50px;
color: #666;
font-style: italic;
}
}
Bu SCSS stillerinde, çeşitli CSS özellikleri ve SCSS özellikleri kullanılmıştır:
- Yuvalanmış Seçiciler: SCSS, seçicileri yuvalamayı destekler, bu da CSS’i daha organize ve okunabilir hale getirir.
- Değişkenler: SCSS, değişkenleri destekler, bu da renk, boyut gibi değerleri bir yerde tanımlamayı ve birden çok yerde kullanmayı sağlar.
- Mixins: SCSS, mixinleri destekler, bu da CSS kodunu yeniden kullanmayı sağlar.
- Operatörler: SCSS, matematiksel operatörleri destekler, bu da değerleri hesaplamayı sağlar.
Komponent Yaşam Döngüsü
Angular komponentleri, oluşturulduklarından yok edildiklerine kadar bir dizi yaşam döngüsü olayından geçer. Bu olaylar, komponentin farklı aşamalarında kod çalıştırmanıza olanak tanır.
Yaşam Döngüsü Kancaları
Angular, aşağıdaki yaşam döngüsü kancalarını sağlar:
- ngOnChanges: Veri bağlı bir özellik değiştiğinde çağrılır.
- ngOnInit: Komponent başlatıldıktan sonra çağrılır.
- ngDoCheck: Her değişiklik algılama döngüsünde çağrılır.
- ngAfterContentInit: Komponentin içeriği başlatıldıktan sonra çağrılır.
- ngAfterContentChecked: Komponentin içeriği kontrol edildikten sonra çağrılır.
- ngAfterViewInit: Komponentin görünümü başlatıldıktan sonra çağrılır.
- ngAfterViewChecked: Komponentin görünümü kontrol edildikten sonra çağrılır.
- ngOnDestroy: Komponent yok edilmeden önce çağrılır.
Bu kancaları kullanmak için, ilgili arayüzleri uygulamanız gerekir:
import { Component, OnInit, OnChanges, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy, SimpleChanges, Input } from '@angular/core';
@Component({
selector: 'app-product-detail',
templateUrl: './product-detail.component.html',
styleUrls: ['./product-detail.component.scss']
})
export class ProductDetailComponent implements OnInit, OnChanges, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy {
@Input() productId: number;
constructor() {
console.log('constructor çağrıldı');
}
ngOnChanges(changes: SimpleChanges): void {
console.log('ngOnChanges çağrıldı', changes);
}
ngOnInit(): void {
console.log('ngOnInit çağrıldı');
}
ngDoCheck(): void {
console.log('ngDoCheck çağrıldı');
}
ngAfterContentInit(): void {
console.log('ngAfterContentInit çağrıldı');
}
ngAfterContentChecked(): void {
console.log('ngAfterContentChecked çağrıldı');
}
ngAfterViewInit(): void {
console.log('ngAfterViewInit çağrıldı');
}
ngAfterViewChecked(): void {
console.log('ngAfterViewChecked çağrıldı');
}
ngOnDestroy(): void {
console.log('ngOnDestroy çağrıldı');
}
}
Yaşam Döngüsü Kancalarının Kullanımı
Yaşam döngüsü kancaları, çeşitli senaryolarda kullanılabilir:
ngOnInit
ngOnInit
, komponentin başlatılması sırasında bir kez çağrılır. Bu kanca, genellikle veri yükleme, abonelikler oluşturma ve diğer başlatma işlemleri için kullanılır:
ngOnInit(): void {
this.loadProduct();
this.subscription = this.route.params.subscribe(params => {
this.productId = +params['id'];
this.loadProduct();
});
}
ngOnChanges
ngOnChanges
, veri bağlı bir özellik değiştiğinde çağrılır. Bu kanca, genellikle giriş özelliklerindeki değişikliklere tepki vermek için kullanılır:
ngOnChanges(changes: SimpleChanges): void {
if (changes['productId'] && !changes['productId'].firstChange) {
this.loadProduct();
}
}
ngOnDestroy
ngOnDestroy
, komponent yok edilmeden önce çağrılır. Bu kanca, genellikle abonelikleri iptal etmek, zamanlayıcıları temizlemek ve diğer temizleme işlemleri için kullanılır:
ngOnDestroy(): void {
this.subscription.unsubscribe();
clearInterval(this.timerInterval);
}
Komponent İletişimi
Angular komponentleri, çeşitli yöntemlerle birbirleriyle iletişim kurabilir.
@Input
dekoratörü, üst komponentten alt komponente veri aktarmak için kullanılır:
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-product-card',
templateUrl: './product-card.component.html',
styleUrls: ['./product-card.component.scss']
})
export class ProductCardComponent {
@Input() product: Product;
}
Üst komponent, alt komponente veri aktarmak için özellik bağlamasını kullanır:
<app-product-card [product]="product"></app-product-card>
@Output
dekoratörü, alt komponentten üst komponente olay göndermek için kullanılır:
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-product-card',
templateUrl: './product-card.component.html',
styleUrls: ['./product-card.component.scss']
})
export class ProductCardComponent {
@Input() product: Product;
@Output() addToCart = new EventEmitter<Product>();
onAddToCart(): void {
this.addToCart.emit(this.product);
}
}
Üst komponent, alt komponentin olaylarını dinlemek için olay bağlamasını kullanır:
<app-product-card
[product]="product"
(addToCart)="onAddToCart($event)">
</app-product-card>
ViewChild ve ContentChild
ViewChild
dekoratörü, bir komponentin şablonundaki bir elemana veya komponente erişmek için kullanılır:
import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
@Component({
selector: 'app-product-table',
templateUrl: './product-table.component.html',
styleUrls: ['./product-table.component.scss']
})
export class ProductTableComponent implements AfterViewInit {
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
dataSource = new MatTableDataSource<Product>();
ngAfterViewInit(): void {
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
}
ContentChild
dekoratörü, bir komponentin içeriğindeki bir elemana veya komponente erişmek için kullanılır:
import { Component, ContentChild, AfterContentInit } from '@angular/core';
import { ProductFilterComponent } from '../product-filter/product-filter.component';
@Component({
selector: 'app-product-container',
templateUrl: './product-container.component.html',
styleUrls: ['./product-container.component.scss']
})
export class ProductContainerComponent implements AfterContentInit {
@ContentChild(ProductFilterComponent) filter: ProductFilterComponent;
ngAfterContentInit(): void {
if (this.filter) {
this.filter.filterChange.subscribe(filters => {
this.applyFilters(filters);
});
}
}
applyFilters(filters: any): void {
// Filtreleri uygula
}
}
Servis Kullanarak İletişim
Komponentler arasında veri paylaşmak için bir servis kullanabilirsiniz:
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { Product } from '../models/product.model';
@Injectable({
providedIn: 'root'
})
export class CartService {
private cartItemsSubject = new BehaviorSubject<Product[]>([]);
cartItems$ = this.cartItemsSubject.asObservable();
addToCart(product: Product): void {
const currentItems = this.cartItemsSubject.value;
this.cartItemsSubject.next([...currentItems, product]);
}
removeFromCart(productId: number): void {
const currentItems = this.cartItemsSubject.value;
const updatedItems = currentItems.filter(item => item.id !== productId);
this.cartItemsSubject.next(updatedItems);
}
clearCart(): void {
this.cartItemsSubject.next([]);
}
getCartItems(): Product[] {
return this.cartItemsSubject.value;
}
}
Komponentler, bu servisi enjekte ederek kullanabilir:
import { Component } from '@angular/core';
import { CartService } from '../../core/services/cart.service';
import { Product } from '../../core/models/product.model';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.scss']
})
export class ProductListComponent {
constructor(private cartService: CartService) { }
addToCart(product: Product): void {
this.cartService.addToCart(product);
}
}
import { Component, OnInit } from '@angular/core';
import { CartService } from '../../core/services/cart.service';
import { Product } from '../../core/models/product.model';
import { Observable } from 'rxjs';
@Component({
selector: 'app-cart',
templateUrl: './cart.component.html',
styleUrls: ['./cart.component.scss']
})
export class CartComponent implements OnInit {
cartItems$: Observable<Product[]>;
constructor(private cartService: CartService) { }
ngOnInit(): void {
this.cartItems$ = this.cartService.cartItems$;
}
removeFromCart(productId: number): void {
this.cartService.removeFromCart(productId);
}
clearCart(): void {
this.cartService.clearCart();
}
}
Komponent Direktifleri
Angular, komponentlerin davranışını ve görünümünü değiştirmek için çeşitli direktifler sağlar.
Yapısal Direktifler
Yapısal direktifler, DOM’un yapısını değiştirerek elementleri ekler, kaldırır veya değiştirir.
ngIf
ngIf
direktifi, bir koşula bağlı olarak bir elementi DOM’a ekler veya DOM’dan kaldırır:
<div *ngIf="product">
<h2>{{ product.name }}</h2>
<p>{{ product.description }}</p>
<div class="price">{{ product.price | currency:'TRY':'symbol':'1.2-2' }}</div>
</div>
<div *ngIf="!product" class="loading">
Ürün yükleniyor...
</div>
ngFor
ngFor
direktifi, bir koleksiyondaki her öğe için bir elementi tekrarlar:
<div *ngFor="let product of products; let i = index; trackBy: trackByProductId">
<div class="product-card">
<h3>{{ i + 1 }}. {{ product.name }}</h3>
<p>{{ product.description }}</p>
<div class="price">{{ product.price | currency:'TRY':'symbol':'1.2-2' }}</div>
<button (click)="addToCart(product)">Sepete Ekle</button>
</div>
</div>
ngFor
direktifi, aşağıdaki yerel değişkenleri sağlar:
- index: Geçerli öğenin dizinini sağlar.
- first: Geçerli öğe ilk öğe ise true, değilse false döndürür.
- last: Geçerli öğe son öğe ise true, değilse false döndürür.
- even: Geçerli öğenin dizini çift ise true, değilse false döndürür.
- odd: Geçerli öğenin dizini tek ise true, değilse false döndürür.
trackBy
fonksiyonu, Angular’ın koleksiyondaki değişiklikleri daha verimli bir şekilde izlemesini sağlar:
trackByProductId(index: number, product: Product): number {
return product.id;
}
ngSwitch
ngSwitch
direktifi, bir değere bağlı olarak birden çok koşulu değerlendirir:
<div [ngSwitch]="product.status">
<div *ngSwitchCase="'available'" class="status available">Stokta</div>
<div *ngSwitchCase="'limited'" class="status limited">Sınırlı Stok</div>
<div *ngSwitchCase="'outOfStock'" class="status out-of-stock">Stokta Yok</div>
<div *ngSwitchDefault class="status unknown">Bilinmiyor</div>
</div>
Özellik Direktifleri
Özellik direktifleri, bir elementin görünümünü veya davranışını değiştirir.
ngClass
ngClass
direktifi, bir elementin sınıflarını dinamik olarak değiştirmek için kullanılır:
<div [ngClass]="{'available': product.inStock, 'out-of-stock': !product.inStock}">
{{ product.name }}
</div>
ngStyle
ngStyle
direktifi, bir elementin stillerini dinamik olarak değiştirmek için kullanılır:
<div [ngStyle]="{'color': product.inStock ? 'green' : 'red', 'font-weight': product.featured ? 'bold' : 'normal'}">
{{ product.name }}
</div>
Komponent Örnekleri
E-ticaret uygulaması için çeşitli komponent örnekleri oluşturalım.
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { AuthService } from '../../core/services/auth.service';
import { CartService } from '../../core/services/cart.service';
import { User } from '../../core/models/user.model';
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss']
})
export class HeaderComponent implements OnInit {
isLoggedIn$: Observable<boolean>;
currentUser$: Observable<User | null>;
cartItemCount$: Observable<number>;
constructor(
private authService: AuthService,
private cartService: CartService,
private router: Router
) { }
ngOnInit(): void {
this.isLoggedIn$ = this.authService.isAuthenticated$;
this.currentUser$ = this.authService.currentUser$;
this.cartItemCount$ = this.cartService.cartItems$.pipe(
map(items => items.length)
);
}
logout(): void {
this.authService.logout();
this.router.navigate(['/auth/login']);
}
}
<header class="header">
<div class="container">
<div class="logo">
<a routerLink="/">E-Ticaret</a>
</div>
<nav class="navigation">
<ul>
<li><a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">Ana Sayfa</a></li>
<li><a routerLink="/products" routerLinkActive="active">Ürünler</a></li>
<li><a routerLink="/about" routerLinkActive="active">Hakkımızda</a></li>
<li><a routerLink="/contact" routerLinkActive="active">İletişim</a></li>
</ul>
</nav>
<div class="user-actions">
<a routerLink="/cart" class="cart-icon">
<i class="fa fa-shopping-cart"></i>
<span class="cart-count" *ngIf="(cartItemCount$ | async) > 0">{{ cartItemCount$ | async }}</span>
</a>
<ng-container *ngIf="isLoggedIn$ | async; else notLoggedIn">
<button mat-button [matMenuTriggerFor]="userMenu">
<i class="fa fa-user"></i>
{{ (currentUser$ | async)?.username }}
</button>
<mat-menu #userMenu="matMenu">
<button mat-menu-item routerLink="/profile">
<i class="fa fa-user-circle"></i>
Profilim
</button>
<button mat-menu-item routerLink="/profile/orders">
<i class="fa fa-list"></i>
Siparişlerim
</button>
<button mat-menu-item (click)="logout()">
<i class="fa fa-sign-out"></i>
Çıkış Yap
</button>
</mat-menu>
</ng-container>
<ng-template #notLoggedIn>
<a routerLink="/auth/login" class="login-button">
<i class="fa fa-sign-in"></i>
Giriş Yap
</a>
</ng-template>
</div>
</div>
</header>
.header {
background-color: #fff;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
position: sticky;
top: 0;
z-index: 1000;
.container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
max-width: 1200px;
margin: 0 auto;
}
.logo {
a {
font-size: 24px;
font-weight: bold;
color: #2196f3;
text-decoration: none;
&:hover {
color: #1976d2;
}
}
}
.navigation {
ul {
display: flex;
list-style: none;
margin: 0;
padding: 0;
li {
margin: 0 10px;
a {
color: #333;
text-decoration: none;
padding: 5px 10px;
border-radius: 4px;
transition: background-color 0.3s ease;
&:hover {
background-color: #f5f5f5;
}
&.active {
color: #2196f3;
font-weight: bold;
}
}
}
}
}
.user-actions {
display: flex;
align-items: center;
.cart-icon {
position: relative;
margin-right: 20px;
color: #333;
text-decoration: none;
i {
font-size: 20px;
}
.cart-count {
position: absolute;
top: -8px;
right: -8px;
background-color: #f44336;
color: #fff;
border-radius: 50%;
width: 18px;
height: 18px;
font-size: 12px;
display: flex;
justify-content: center;
align-items: center;
}
}
.login-button {
color: #2196f3;
text-decoration: none;
i {
margin-right: 5px;
}
}
}
}
@media (max-width: 768px) {
.header {
.container {
flex-direction: column;
padding: 10px;
}
.logo {
margin-bottom: 10px;
}
.navigation {
margin-bottom: 10px;
ul {
flex-wrap: wrap;
justify-content: center;
li {
margin: 5px;
}
}
}
}
}
ProductCard Komponenti
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { Product } from '../../core/models/product.model';
@Component({
selector: 'app-product-card',
templateUrl: './product-card.component.html',
styleUrls: ['./product-card.component.scss']
})
export class ProductCardComponent {
@Input() product: Product;
@Output() addToCart = new EventEmitter<Product>();
onAddToCart(event: Event): void {
event.stopPropagation();
this.addToCart.emit(this.product);
}
}
<div class="product-card" [routerLink]="['/products', product.id]">
<div class="product-image">
<img [src]="product.imageUrl" [alt]="product.name">
<div class="product-badge" *ngIf="product.discount > 0">
{{ product.discount }}% İndirim
</div>
</div>
<div class="product-content">
<h3 class="product-title">{{ product.name }}</h3>
<p class="product-description">{{ product.description }}</p>
<div class="product-price">
<span class="current-price">{{ product.price | currency:'TRY':'symbol':'1.2-2' }}</span>
<span class="original-price" *ngIf="product.discount > 0">
{{ product.originalPrice | currency:'TRY':'symbol':'1.2-2' }}
</span>
</div>
<div class="product-rating">
<i class="fa fa-star" *ngFor="let star of [1, 2, 3, 4, 5]"
[ngClass]="{'filled': star <= product.rating, 'empty': star > product.rating}"></i>
<span class="rating-count">({{ product.ratingCount }})</span>
</div>
<div class="product-actions">
<button class="add-to-cart-button" (click)="onAddToCart($event)">
<i class="fa fa-shopping-cart"></i>
Sepete Ekle
</button>
</div>
</div>
</div>
.product-card {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease;
cursor: pointer;
&:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.product-image {
position: relative;
height: 200px;
overflow: hidden;
img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.product-badge {
position: absolute;
top: 10px;
right: 10px;
background-color: #f44336;
color: #fff;
padding: 5px 10px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
}
}
.product-content {
padding: 15px;
.product-title {
margin-top: 0;
margin-bottom: 10px;
font-size: 18px;
color: #333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.product-description {
color: #666;
font-size: 14px;
margin-bottom: 15px;
height: 60px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
.product-price {
display: flex;
align-items: center;
margin-bottom: 10px;
.current-price {
font-size: 18px;
font-weight: bold;
color: #2196f3;
}
.original-price {
font-size: 14px;
color: #999;
text-decoration: line-through;
margin-left: 10px;
}
}
.product-rating {
display: flex;
align-items: center;
margin-bottom: 15px;
i {
color: #ddd;
margin-right: 2px;
&.filled {
color: #ffc107;
}
}
.rating-count {
font-size: 12px;
color: #666;
margin-left: 5px;
}
}
.product-actions {
.add-to-cart-button {
width: 100%;
padding: 10px;
background-color: #2196f3;
color: #fff;
border: none;
border-radius: 4px;
font-size: 14px;
font-weight: bold;
cursor: pointer;
transition: background-color 0.3s ease;
i {
margin-right: 5px;
}
&:hover {
background-color: #1976d2;
}
}
}
}
}
ProductList Komponenti
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { ProductService } from '../../core/services/product.service';
import { CartService } from '../../core/services/cart.service';
import { Product } from '../../core/models/product.model';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.scss']
})
export class ProductListComponent implements OnInit {
products$: Observable<Product[]>;
categoryId: number | null = null;
loading = false;
error: string | null = null;
constructor(
private productService: ProductService,
private cartService: CartService,
private route: ActivatedRoute
) { }
ngOnInit(): void {
this.loading = true;
this.products$ = this.route.paramMap.pipe(
switchMap(params => {
const categoryId = params.get('categoryId');
this.categoryId = categoryId ? +categoryId : null;
return this.categoryId
? this.productService.getProductsByCategory(this.categoryId)
: this.productService.getProducts();
})
);
this.products$.subscribe(
() => {
this.loading = false;
this.error = null;
},
error => {
this.loading = false;
this.error = 'Ürünler yüklenirken bir hata oluştu.';
console.error('Ürünler yüklenirken hata:', error);
}
);
}
addToCart(product: Product): void {
this.cartService.addToCart(product);
}
trackByProductId(index: number, product: Product): number {
return product.id;
}
}
<div class="product-list-container">
<div class="product-list-header">
<h1>{{ categoryId ? 'Kategori Ürünleri' : 'Tüm Ürünler' }}</h1>
<div class="product-filters">
<mat-form-field appearance="outline">
<mat-label>Sırala</mat-label>
<mat-select>
<mat-option value="price-asc">Fiyat (Artan)</mat-option>
<mat-option value="price-desc">Fiyat (Azalan)</mat-option>
<mat-option value="name-asc">İsim (A-Z)</mat-option>
<mat-option value="name-desc">İsim (Z-A)</mat-option>
<mat-option value="rating-desc">Puan (Yüksek-Düşük)</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<div *ngIf="loading" class="loading-spinner">
<mat-spinner></mat-spinner>
</div>
<div *ngIf="error" class="error-message">
{{ error }}
</div>
<div *ngIf="!(loading || error)" class="product-grid">
<app-product-card
*ngFor="let product of products$ | async; trackBy: trackByProductId"
[product]="product"
(addToCart)="addToCart($event)">
</app-product-card>
</div>
<div *ngIf="!(loading || error) && (products$ | async)?.length === 0" class="no-products">
Ürün bulunamadı.
</div>
</div>
.product-list-container {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
.product-list-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
h1 {
font-size: 24px;
color: #333;
margin: 0;
}
.product-filters {
display: flex;
align-items: center;
mat-form-field {
width: 200px;
}
}
}
.loading-spinner {
display: flex;
justify-content: center;
margin: 50px 0;
}
.error-message {
color: #f44336;
padding: 10px;
margin: 20px 0;
background-color: #ffebee;
border-radius: 4px;
}
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
}
.no-products {
text-align: center;
padding: 50px;
color: #666;
font-style: italic;
}
}
@media (max-width: 768px) {
.product-list-container {
padding: 10px;
.product-list-header {
flex-direction: column;
align-items: flex-start;
h1 {
margin-bottom: 10px;
}
}
.product-grid {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 10px;
}
}
}
Sonuç
Bu bölümde, Angular komponentlerinin yapısını ve yaşam döngüsünü detaylı olarak ele aldık. Komponentler, Angular uygulamalarının temel yapı taşlarıdır ve kullanıcı arayüzünün farklı bölümlerini oluşturmak için kullanılırlar.
Komponentlerin yapısını, yaşam döngüsünü, iletişim yöntemlerini ve direktiflerini inceledik. Ayrıca, e-ticaret uygulaması için çeşitli komponent örnekleri oluşturduk.
Bir sonraki bölümde, Angular servislerini ve bağımlılık enjeksiyonunu ele alacağız.