Mert Özen Her Satırda Daha İleri
Frontend Angular — #04

Angular NgModule Nedir? declarations, imports, exports ve Feature Module

Mert Özen 31 Eki 2026 7 dk 1 görüntülenme
Angular NgModule Nedir? declarations, imports, exports ve Feature Module

Angular 12 uygulamasının iskeleti NgModule'ü açıyoruz: declarations, imports, exports, providers, bootstrap, main.ts'teki başlatma, BrowserModule ile CommonModule farkı, feature ve shared module.

Sürüm notu: Bu yazıdaki kod ve komutlar Angular 12 içindir.

Component'lerin de Bir Adresi Var

Daha önce gördüğümüz ng generate component komutu her çalıştığında sessizce bir dosyayı daha güncelliyordu: app.module.ts. O dosyadaki satırı silince de uygulama "is not a known element" diye itiraz ediyordu. Demek ki bir component'i yazmak yetmiyor; Angular'a "bu component şurada yaşıyor" demek de gerekiyor.

Bunu bir şirket gibi düşün. Component'ler çalışanlar, module'ler departmanlar. Her çalışan tek bir departmanın kadrosundadır. Bir departman başka bir departmandan yardım alabilir, ama ancak o departman bu yardımı dışarıya açmışsa. Angular 12'de bu departmanların adı NgModule. Bu yazıda bir module'ün içinde neyin neden durduğunu tek tek açacağız.

AppModule'ü Satır Satır Okuyalım

Projede src/app/app.module.ts dosyasını aç. ng new ile oluşan hâli şöyle:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Tanıdık bir kalıp: düz bir TypeScript class'ı ve üstünde bir decorator. Component'te @Component vardı, burada @NgModule var. Class'ın içi bomboş; AppModule kendi başına hiçbir iş yapmaz. Bütün bilgi decorator'a verdiğimiz nesnede duruyor.

Bu nesnenin beş temel alanı var: declarations, imports, exports, providers ve bootstrap. AppModule'de exports görünmüyor, çünkü kök module'ün kimseye bir şey vermesine gerek yok. Alanları sırayla ele alalım.

declarations: Module'ün Kadrosu

declarations, bu module'e ait component, directive ve pipe'ların listesidir. Directive ve pipe'ları ilerleyen yazılarda detaylandıracağız; şimdilik hepsinin component gibi template'te kullanılan parçalar olduğunu bilmen yeterli.

Burada iki kural var. Birincisi, bir template'te ancak kendi module'ünde tanımlı ya da başka bir module'den içeri alınmış parçaları kullanabilirsin. İkincisi, bir component yalnızca tek bir module'ün declarations listesinde yer alabilir. Aynı component'i iki module'e birden yazarsan derleme şu hatayla durur:

error NG6007: The Component 'ProductListComponent' is declared by more than one NgModule.

Şirket benzetmesine dönersek: bir çalışan iki departmanın kadrosunda birden olamaz. Başka bir departmanda iş görmesi gerekiyorsa, kendi departmanı onu oraya ödünç verir. Bu ödünç verme işinin adı exports.

imports, exports ve providers

imports, bu module'ün ihtiyaç duyduğu diğer module'lerin listesidir. AppModule'de tek bir satır var: BrowserModule. Uygulamanın tarayıcıda çalışabilmesi ve template'lerde hazır yapıları kullanabilmen bu import sayesinde mümkün oluyor.

exports ise madalyonun öbür yüzü. Bir module, kendi kadrosundaki parçalardan hangilerini dışarıya açacağını burada söyler. Export edilmeyen bir component o module'ün iç meselesidir; başka bir module onu import etse bile göremez.

Yani bir parçanın iki module arasında paylaşılabilmesi için iki tarafın da elini uzatması gerekir. Veren module parçayı exports listesine yazar, alan module de veren module'ü imports listesine ekler. Biri eksikse template yine "is not a known element" hatasında takılır.

Küçük ama sık karıştırılan bir ayrıntı: @NgModule içindeki imports, dosyanın en üstündeki TypeScript import satırlarıyla aynı şey değil. Üstteki satırlar dosyayı TypeScript'e tanıtır, decorator'daki liste ise module'ü Angular'a tanıtır. İkisine de ihtiyacın var.

Dördüncü alan providers, module seviyesinde kayıt edilen service'lerin listesidir. Yeni projede boş geliyor ve çoğu zaman öyle kalıyor, çünkü Angular 12'de service'ler genellikle kendilerini @Injectable({ providedIn: 'root' }) ile kaydediyor. Service ve dependency injection konusunu ilerleyen yazılarda detaylandıracağız.

bootstrap ve main.ts: Uygulama Nasıl Başlıyor?

Son alan bootstrap, yalnızca kök module'de bulunur ve uygulama açılırken ekrana ilk basılacak component'i söyler. AppModule'de bu AppComponent. Angular, index.html içinde bu component'in selector'ını, yani <app-root> etiketini arar ve AppComponent'i oraya yerleştirir. Component ağacının kökü tam olarak burada belirlenir.

Peki AppModule'ü kim çalıştırıyor? Cevap src/main.ts dosyasında:

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));

İşin kalbi son satır. platformBrowserDynamic() uygulamanın çalışacağı tarayıcı platformunu hazırlar. bootstrapModule(AppModule) ise bu platforma "başlangıç noktası bu module" der. Angular AppModule'ün decorator'ını okur, bootstrap listesindeki AppComponent'i bulur ve ekrana çizer. Yolda bir hata çıkarsa catch onu konsola yazar.

Üstteki if bloğu, production build'de geliştirme moduna ait ek kontrolleri kapatan enableProdMode() çağrısı. Environment dosyalarına build ve deploy konusunda ayrıca döneceğiz.

Zinciri baştan sona kurarsak: tarayıcı index.html dosyasını yükler, main.ts çalışır, AppModule başlatılır, AppComponent de <app-root> etiketinin yerine çizilir. Bir arabanın kontağı gibi; anahtar main.ts, motor AppModule, ilk dönen tekerlek AppComponent.

BrowserModule mü, CommonModule mü?

Birazdan yeni bir module oluşturduğumuzda içinde BrowserModule değil CommonModule göreceksin. İlk başta kafa karıştıran bir ikili.

CommonModule, template'lerde sürekli kullanacağın temel directive ve pipe'ları getirir: *ngIf, *ngFor, date pipe'ı gibi. BrowserModule ise CommonModule'ü olduğu gibi dışarı aktarır ve üstüne uygulamanın tarayıcıda çalışması için gereken altyapıyı ekler.

Altyapı bir kez kurulur. Bu yüzden kural basit: BrowserModule yalnızca AppModule'de, diğer bütün module'lerde CommonModule. Bu kuralı çiğneyip sonradan yüklenen bir module'e BrowserModule eklersen Angular "BrowserModule has already been loaded" hatası verir ve sana açıkça CommonModule kullanmanı söyler.

Kendi Feature Module'ümüzü Oluşturalım

Uygulama büyüdükçe her şeyi AppModule'e yığmak, bütün çalışanları tek bir odaya doldurmaya benzer. Bunun yerine birbiriyle ilgili parçaları bir feature module'de toplarız. Ürünlerle ilgili her şey için bir module oluşturalım:

ng generate module products

Kısaltması ng g m products. CLI tek bir dosya oluşturur:

CREATE src/app/products/products.module.ts (194 bytes)

Şimdi bu module'ün içine bir component üretelim. Component adını klasör yoluyla birlikte verdiğimize dikkat et:

ng g c products/product-list

Bu kez çıktının son satırı UPDATE src/app/products/products.module.ts oldu. CLI component'i en yakın module'e kaydetti, AppModule'e hiç dokunmadı. Dosyanın son hâli şöyle:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProductListComponent } from './product-list/product-list.component';

@NgModule({
  declarations: [
    ProductListComponent
  ],
  imports: [
    CommonModule
  ]
})
export class ProductsModule { }

ProductListComponent artık ProductsModule'ün kadrosunda. CommonModule import edildiği için template'inde *ngIf ya da *ngFor kullanmak isterse hazır.

Bir Component'i Dışarıya Açmak

Şimdi app.component.html dosyasına <app-product-list></app-product-list> yazalım. Derleme şu hatayla durur:

error NG8001: 'app-product-list' is not a known element:
1. If 'app-product-list' is an Angular component, then verify that it is part of this module.

Çünkü iki el de henüz uzanmadı. Önce ProductsModule component'i dışarıya açmalı:

@NgModule({
  declarations: [
    ProductListComponent
  ],
  imports: [
    CommonModule
  ],
  exports: [
    ProductListComponent
  ]
})
export class ProductsModule { }

Sonra AppModule, ProductsModule'ü içeri almalı:

import { ProductsModule } from './products/products.module';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    ProductsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Kaydettiğinde "product-list works!" yazısı AppComponent'in içinde belirir. Dikkat edersen AppModule'ün declarations listesinde ProductListComponent yok. AppComponent onu doğrudan tanımıyor; ProductsModule'ün açtığı kapıdan görüyor.

Shared Module Fikri

Bazı parçalar tek bir özelliğe ait değildir. Bir yükleniyor göstergesi, bir sayfa başlığı, bir boş liste mesajı; ürünlerde de lazım, siparişlerde de. Bunları her feature module'de ayrı ayrı tanımlayamazsın, çünkü bir component yalnızca tek bir module'e ait olabilir. Çözüm, ortak parçaları bir shared module'de toplamak:

ng g m shared
ng g c shared/loading-spinner --export

--export bayrağı, CLI'ye component'i hem declarations hem de exports listesine eklemesini söyler. Ardından exports listesine elle bir satır daha ekliyoruz:

@NgModule({
  declarations: [
    LoadingSpinnerComponent
  ],
  imports: [
    CommonModule
  ],
  exports: [
    LoadingSpinnerComponent,
    CommonModule
  ]
})
export class SharedModule { }

Bir module, import ettiği başka bir module'ü olduğu gibi dışarıya aktarabilir. Böylece SharedModule'ü import eden bir feature module, hem LoadingSpinnerComponent'i hem de CommonModule'ün directive'lerini tek satırla alır.

Shared module'ü ofisteki ortak alet dolabı gibi düşün. Her departman kendi matkabını almak yerine dolaptan alır. Yalnız dolaba her şeyi atma; oraya sadece gerçekten birden fazla yerde kullanılan parçalar girsin, yoksa dolap kısa sürede depoya döner.

Feature module'lerin bir faydası daha var: uygulama açılırken hepsinin birden yüklenmesi gerekmez, kullanıcı ilgili sayfaya gittiğinde yüklenebilirler. Buna lazy loading denir; routing'den sonra ilerleyen yazılarda detaylandıracağız.

Küçük Bir Deneme

CLI ile orders adında bir feature module ve onun içinde order-summary adında bir component üret. Component'in template'ine birkaç satırlık sabit bir sipariş özeti yaz. Ardından component'i export et, OrdersModule'ü AppModule'e import et ve <app-order-summary> etiketini AppComponent'in template'inde kullan.

Sonra iki şeyi bilerek boz. Önce OrdersModule'deki exports satırını sil ve terminaldeki hatayı oku. Satırı geri koyduktan sonra bu kez OrderSummaryComponent'i AppModule'ün declarations listesine de ekle ve "declared by more than one NgModule" hatasını kendi gözünle gör.

Module'ler parçaları doğru yerlere oturttu, ama ekranda hâlâ sabit yazılar duruyor. Class'taki veriyi template'e taşımanın ve kullanıcının tıklamasını class'a geri getirmenin bütün yolları bir sonraki konumuz: data binding.