Chrome DevTools'a Giriş ve Elements Paneli: DOM, Styles, Box Model ve Angular Component'lerini İncelemek
Bir stil kuralı uygulanmıyorsa Elements panelinde element'e uyan kuralları, specificity'yi ve Computed değeri okursun; Angular'da sebep çoğu zaman _ngcontent attribute'uyla kilitlenen ViewEncapsulation'dır. Command Menu, Device Mode, :hov, box model, Flexbox ve anchor positioning araçları, ng.getComponent($0) ve ::ng-deep yerine CSS custom property, Chrome 156 ve Angular 22 ile gerçek çıktılarla.
Sürüm notu: Bu yazıdaki örnekler Chrome 156 (156.0.8078.80), Angular 22.2 ve .NET 10 ile denenmiştir. Konsol ve stil çıktıları aynı Chrome sürümünde, headless modda Chrome DevTools Protocol (CDP) üzerinden alındı; panel adımları ve kısayollar Chrome DevTools dokümanından aktarıldı.
Önce Tahmin Et
Bookstore'un kitap listesinde fiyatlar kalın görünsün istiyorsun. Liste component'i BookList, her satırı ise BookCard çiziyor. Fiyat BookCard'ın template'inde duruyor ama sen stili listeye yazıyorsun:
@Component({
selector: 'app-book-list',
imports: [BookCard],
template: `
<input class="filter" placeholder="Filter books" [value]="query()" (input)="query.set($any($event.target).value)" />
<p class="count">{{ visible().length }} books</p>
@for (book of visible(); track book.id) {
<app-book-card [book]="book" (addToCart)="cart.add($event)" />
}
`,
styles: `
.price { font-weight: 700; }
`,
})
export class BookList { /* ... */ }
Sayfayı açıyorsun, fiyatlar hâlâ normal kalınlıkta. CSS (Cascading Style Sheets) dosyası yüklenmiş, selector doğru yazılmış, .price class'ı element'in üzerinde. Sence kural neden uygulanmadı? Ve DevTools'ta Styles bölümünde kuralı elle ekleyip sorunu "çözdüğünde" sayfayı yenilersen ne olur? Cevabı tahmin etmeden önce bu yazının sonundaki çıktılara bakma; Elements paneli sana cevabı iki tıkta veriyor.
Bu seri, Chrome DevTools'un panellerini bir Angular geliştiricisinin gözünden ele alıyor. DevTools'la ilk tanışma JavaScript Temel serisinin "JavaScript ile İlk Script: script Etiketi, defer, async ve DevTools" yazısındaydı; burada aracı günlük işin merkezine koyuyoruz. Örnekler yerel bir Angular 22 uygulaması ve onun ASP.NET Core 10 API'si (Application Programming Interface) üzerinde.
DevTools'u Açmak, Yerleştirmek ve Command Menu
DevTools'u açmanın üç yolu var ve her biri farklı bir yere götürür. F12 ya da Ctrl+Shift+I (Mac'te Cmd+Option+I) son kullandığın paneli açar. Ctrl+Shift+J (Cmd+Option+J) doğrudan Console'a gider. Ctrl+Shift+C (Cmd+Shift+C) ise inspect modunu açar: sayfada üzerine geldiğin element vurgulanır, tıkladığında Elements panelinde seçili olur. Günlük işte en çok kullanacağın bu sonuncusu.
DevTools'un nerede duracağını sağ üstteki üç nokta menüsünden (Customize and control DevTools) Dock side ile seçersin: sağ, alt, sol ya da ayrı pencere. Ctrl+Shift+D (Cmd+Shift+D) son iki konum arasında geçiş yapar. Responsive bir layout'la uğraşıyorsan ayrı pencere iyi bir seçim, çünkü yanda duran DevTools sayfanın viewport genişliğini daraltır ve media query'leri istemeden tetikler.
Asıl hızlandırıcı Command Menu: Ctrl+Shift+P (Cmd+Shift+P). Visual Studio Code'daki komut paletinin aynısı. "dock" yazıp yerleşimi değiştirebilir, "screenshot" yazıp tam sayfa ekran görüntüsü alabilir, "disable JavaScript" ile sayfayı script'siz deneyebilir, "show rendering" ile Rendering sekmesini açabilirsin. Başındaki > işaretini silersen aynı kutu dosya açma moduna geçer; : satıra gitmek, @ sembole gitmek, ? diğer modları listelemek içindir. Paneller arasında fareyle gezinmek yerine bu kutuyu kullanmaya alışırsan DevTools'ta geçirdiğin süre belirgin şekilde kısalır.
Esc tuşu Drawer'ı açıp kapatır: hangi paneldeysen alt yarıda Console, Rendering, Network conditions, Sensors gibi ikincil araçları açık tutabilirsin. Elements'te stil incelerken altta Console açık durması bu serinin en sık kullanacağı düzen.
Device Mode: Mobil Görünümü Emüle Etmek
Ctrl+Shift+M (Cmd+Shift+M) Device Mode'u açar. Üst araç çubuğundan hazır bir cihaz seçer ya da Responsive modunda genişliği sürükleyerek değiştirirsin. Chrome 153–154 ile cihaz listesi form factor'e göre gruplandı: Mobile, Foldables, Tablets & Desktops ve Smart Displays. Eski tanımlar kaldırıldı, iPhone 16, Pixel 9, Pixel 10, Galaxy Z Fold 6 ve iPad Pro 13" gibi güncel preset'ler eklendi. Chrome 155–156'da ise her emüle edilen cihazın zoom tercihi ayrı saklanıyor, Save-Data override'ı da device toolbar'ı açıp kapattığında kaybolmuyor.
Device Mode'un ne olmadığını bilmek önemli: viewport boyutunu, device pixel ratio'yu, user agent'ı ve touch event'lerini taklit eder, ama telefonun CPU'sunu (Central Processing Unit), GPU'sunu (Graphics Processing Unit) ve gerçek ağını taklit etmez. Masaüstü işlemcide akıcı görünen bir liste, orta segment bir telefonda takılabilir. Performans sorularında CPU throttling'i (Performance panelinde, bu serinin 06. yazısında) ekle; son kararı ise gerçek cihazda, USB (Universal Serial Bus) üzerinden remote debugging ile ver.
Elements Paneli: Canlı DOM'u Okumak ve Düzenlemek
Önce şu ayrımı netleştirelim: View Source sunucudan gelen HTML'i (HyperText Markup Language), Elements paneli ise tarayıcının o anki DOM'unu (Document Object Model) gösterir. Bir Angular uygulamasında bu ikisi arasındaki fark çok büyük. Sunucunun gönderdiği index.html'in gövdesi şu kadar:
<body>
<app-root></app-root>
<noscript>Please enable JavaScript to continue using this application.</noscript>
<script src="main.js" type="module"></script></body>
Angular render ettikten sonra ilk app-book-card element'inin canlı DOM'daki hâli ise bu (CDP ile DOM.getOuterHTML, satırlara biz böldük):
<app-book-card _ngcontent-ng-c1407352934="" _nghost-ng-c1135625248="">
<article _ngcontent-ng-c1135625248="" class="card">
<a _ngcontent-ng-c1135625248="" class="title" href="/books/1">Compilers Volume 1</a>
<span _ngcontent-ng-c1135625248="" class="author">Alan Turing</span>
<span _ngcontent-ng-c1135625248="" class="price">$21.00</span>
<button _ngcontent-ng-c1135625248="" type="button">Add to cart</button>
</article>
</app-book-card>
Panelde bir node seçiliyken çift tıklayarak attribute ya da metin düzenlersin, F2 (Mac'te Fn+F2) ile Edit as HTML açılır, H element'i gizler (visibility: hidden ekler), Delete siler, Ctrl+Z geri alır. Node'ları sürükleyip başka yere taşıyabilirsin; Chrome 153–154'ten beri bu sürükleme shadow DOM sınırlarını da aşıyor. Ctrl+F panelde arama yapar ve yalnız metin değil CSS selector ve XPath de kabul eder: app-book-card:nth-child(5) .price yazınca doğrudan beşinci kartın fiyatına gidersin. Ok ikonuna Ctrl+Alt (Mac'te Option) basılıyken tıklamak alt ağacın tamamını açar.
Bir node'a sağ tıklayınca çıkan menüde iki şeyi hatırla: Copy > Copy selector test ya da konsol için hazır bir CSS selector'ü verir, JavaScript (JS) ifadesi gerekiyorsa yanındaki Copy JS path var; Break on > subtree modifications ise o alt ağaç değiştiğinde JavaScript'i durdurur. Bu ikincisini "bu element'i hangi kod değiştiriyor" sorusu için Sources yazısında kullanacağız.
Styles: Cascade, Specificity ve Bir Kuralın Neden Uygulanmadığı
Styles bölümü, seçili element'e uyan kuralları cascade sırasıyla gösterir: en üstte inline style, sonra specificity ve kaynak sırasına göre kazananlar, en altta user agent stilleri ve "Inherited from" başlıkları altında miras gelenler. Üstü çizili bir property başka bir kural tarafından ezilmiştir. Selector'ün üzerine gelince specificity'sini görürsün. Önce Tahmin Et'teki fiyat span'ini seçip CDP'den aynı bilgiyi istediğimizde (CSS.getMatchedStylesForNode) gelen tek kural şu:
regular .price[_ngcontent-ng-c1135625248] [0,2,0] MATCH { color: #0a7d4f; }
Element'e uyan tek .price kuralı BookCard'ınki. Specificity (0,2,0): bir class ve bir attribute selector. Peki BookList'in kuralı nerede? Sayfadaki <style> element'lerine bakınca cevap çıkıyor:
.price[_ngcontent-ng-c1407352934] {
font-weight: 700;
}
/*# sourceMappingURL=/book-list.css.map */
.price[_ngcontent-ng-c1135625248] {
color: #0a7d4f;
}
Angular'ın varsayılan ViewEncapsulation.Emulated modu her component'in stilini o component'in template'indeki element'lere kilitliyor. Kilit, derleme sırasında selector'e eklenen [_ngcontent-ng-c...] attribute'u. BookList'in .price kuralı yalnız _ngcontent-ng-c1407352934 taşıyan element'lere uyar; fiyat span'i ise BookCard'ın template'inden geldiği için _ngcontent-ng-c1135625248 taşıyor. Kural yanlış değil, başka bir component'in element'lerine bakıyor. Computed sekmesi de aynı şeyi söylüyor: font-weight: 400, color: rgb(10, 125, 79).
Computed sekmesi "son değer ne ve nereden geldi" sorusunun cevabıdır. Bir property'yi açınca o değeri hangi kuralın hangi dosyanın hangi satırından verdiğini görürsün; "Show all" kapalıyken yalnız ayarlanmış property'ler listelenir. Styles'ta ise bir property'nin yanındaki bilgi ikonu inactive CSS uyarısıdır: kural uyuyor ama etkisi yok. Klasik örnek inline bir element'e verilen width. Bizim kartta .author { width: 160px; } var ve .author bir span; buna rağmen kutu genişliği 160 çıkıyor ve computed display değeri block. Sebep: .card bir flex container ve flex item'ları blockify edilir. DevTools'un uyarı göstermemesi doğru davranış; uyarıyı gördüğünde ise önce parent'ın layout'una bak. Chrome 153–154 ile, daha önce eşleşip artık eşleşmeyen kuralların da inactive işaretiyle görünür kalması bir ayarla açılabiliyor; 155–156'da @starting-style kuralları ve miras gelen kurallar için de inactive gösterimi geldi.
:hov, .cls ve Box Model ile Durum ve Ölçü
Hover stilini incelemek istediğinde fareyi butondan çektiğin an stil kaybolur. Styles araç çubuğundaki :hov düğmesi :hover, :focus, :focus-visible, :active gibi durumları zorlar. Aynı işi CDP'nin CSS.forcePseudoState komutuyla yaptığımızda butonun arka planı şöyle değişti:
button background before :hover force: rgb(239, 239, 239) after: rgb(56, 189, 248)
Element'in kenarında beliren turuncu nokta bir durumun zorlandığını gösterir; işin bitince kapatmayı unutma, yoksa yarım saat sonra "bu buton neden hep mavi" diye kendi kendini debug edersin. Yanındaki .cls class ekleyip çıkarmana yarar; Chrome 155–156'dan beri öneri listesi element'te zaten olan class'ları göstermiyor.
Computed sekmesinin üstündeki box model şeması content, padding, border ve margin'i katman katman gösterir, değerlere çift tıklayıp düzenleyebilirsin. Kartın kutusunu CDP'nin DOM.getBoxModel komutuyla ölçtüğümüzde content genişliği 1244, padding dahil 1264 piksel çıktı: padding: 6px 10px sağdan ve soldan 10'ar piksel ekliyor. Styles'ta sayısal bir değeri seçip ok tuşlarıyla 1, Alt (Mac'te Option) ile 0.1, Shift ile 10 birim değiştirebilirsin; tasarımcının "biraz daha nefes alsın" dediği yerde değeri gözle ayarlayıp sonra koda yazmak böyle yapılır.
Flexbox, Grid ve Anchor Positioning Araçları
Elements ağacında display: flex ya da grid olan element'lerin yanında flex ve grid rozetleri çıkar. Rozete tıklamak sayfaya bir overlay çizer: track'ler, gap'ler, satır ve sütun numaraları görünür olur. Styles'ta display: flex satırının yanındaki ikon Flexbox editörünü açar; flex-direction, flex-wrap, align-items, justify-content değerlerini görsel düğmelerle denersin. Chrome 155–156 bu editöre wrap-reverse seçeneğini ekledi.
Yeni CSS anchor positioning için de araçlar geldi. Chrome 155–156'da Styles'taki position-area bildiriminin yanındaki küçük kareye tıklayınca bölgeleri seçebileceğin görsel bir grid açılıyor. Anchor ile konumlanmış bir element seçiliyken viewport overlay'i, inset-modified containing block'un (IMCB) margin, padding, border ve content kutularını çiziyor. DOM ağacındaki popover rozetine sağ tıklamak element'in implicit anchor'larını listeliyor ve birine atlamanı sağlıyor. Angular'da bir dropdown ya da tooltip'i anchor positioning'e taşıyorsan "neden yanlış köşede açılıyor" sorusunun cevabı artık bu overlay'de.
Angular Component'lerini İncelemek: _nghost, _ngcontent ve ng.getComponent
Yukarıdaki DOM çıktısını tekrar oku. app-book-card iki attribute taşıyor: _nghost-ng-c1135625248 onun kendi component'inin host'u olduğunu, _ngcontent-ng-c1407352934 ise BookList'in template'inde durduğunu söylüyor. İçerideki her element BookCard'ın _ngcontent değerini taşıyor. Yani Elements panelinde bir element'in hangi component'in template'inden geldiğini bu attribute'lardan okuyabilirsin.
Daha hızlı yol Console'da. Elements'te seçtiğin node konsolda $0 adıyla durur. Development build'de Angular, window.ng altında hata ayıklama fonksiyonları açar:
$0.tagName
// "APP-BOOK-CARD"
ng.getComponent($0).constructor.name
// "_BookCard"
ng.getComponent($0).book()
// {"id":1,"title":"Compilers Volume 1","author":"Alan Turing","price":21,"stock":1}
ng.getOwningComponent($0).constructor.name
// "_BookList"
getComponent host element'inin component instance'ını, getOwningComponent ise element'i template'inde barındıran component'i döndürür. book bir signal input olduğu için değeri fonksiyon gibi çağırarak okuyoruz. Class adının başındaki alt çizgi development build'deki derleme çıktısından geliyor, kod tarafında bir anlamı yok. ng global'inin tamamı bir sonraki yazının konusu.
Şimdi fiyat sorununa dönelim. İlk akla gelen çözüm ::ng-deep:
styles: `
:host ::ng-deep .price { font-weight: 700; }
`,
Derlenen kural ve ölçülen etkisi:
[_nghost-ng-c1407352934] .price {
font-weight: 700;
}
app-book-list attributes: _nghost-ng-c1407352934
BookCard .price font-weight: 700 | any other .price under BookList: 700
Fiyat kalınlaştı, ama kural artık BookList'in altındaki her .price'a uyuyor: test için listeye eklediğimiz alakasız bir span.price da 700 oldu. :host olmadan yazarsan kural tüm uygulamaya sızar. Angular dokümanı ::ng-deep'i yalnız geriye dönük uyumluluk için tutuyor ve kullanılmamasını öneriyor. Daha temiz yol, child component'in kendi stilini bir CSS custom property üzerinden açması:
/* BookCard: dışarıya açılan ayar noktası, varsayılanı 400 */
.price { color: #0a7d4f; font-weight: var(--book-price-weight, 400); }
/* BookList: yalnız değeri veriyor */
app-book-card { --book-price-weight: 700; }
font-weight: 700, --book-price-weight on host: "700"
Custom property'ler encapsulation sınırını kalıtımla geçer; ama BookCard hangi değerin dışarıdan ayarlanabileceğine kendisi karar verir. Elements panelinde de bu açıkça görünür: Styles'ta var(--book-price-weight, 400) değerinin üzerine gelince çözümlenen değeri ve tanımlandığı kuralı gösterir.
Üretimde Dikkat
Production build'de ng global'i yok. Aynı uygulamanın ng build çıktısını ASP.NET Core'un static file'larından sunup konsolda baktığımızda typeof ng sonucu "undefined" oldu. Canlı ortamda bir component'i incelemen gerekiyorsa ya hata ayıklama için optimization: false ile ayrı bir build alırsın ya da source map'lerle Sources panelinde çalışırsın (bu serinin 03. yazısı).
_ngcontent-ng-c... değerleri Angular'ın ürettiği şeyler. Bizim denemede development ve production build'de aynı çıktı, ama component'in içeriği değişince değişebilir. Bu attribute'ları ne global CSS'te ne de E2E (end-to-end) test selector'lerinde kullan; testte data-testid gibi kendi attribute'unu koy.
DevTools'ta yaptığın her stil ve DOM değişikliği yalnız o sekmenin belleğinde yaşar. Sayfayı yenilediğinde, Angular'ın dev server'ı hot reload yaptığında ya da Angular bir @if bloğunu yeniden oluşturduğunda kaybolur. Bulduğun değeri hemen koda taşı; DevTools'u kaynak dosyaya yazdırmak için Workspaces'i Sources yazısında kuracağız.
Extension'lar sayfaya kendi stillerini ve DOM element'lerini ekleyebilir. "Bende bozuk, ekip arkadaşımda düzgün" durumunda ilk deneme, sayfayı extension'sız bir profil ya da Incognito penceresiyle açmak.
Sık Düşülen Tuzaklar
- DevTools'ta düzeltip yenilemek: Önce Tahmin Et'teki ikinci sorunun cevabı bu; Styles'a eklediğin kural yenilemeyle gider ve kaynakta hiçbir şey değişmemiştir.
- View Source'a bakıp DOM'u okuduğunu sanmak: Angular uygulamasında View Source yalnız boş bir
<app-root>gösterir. - Encapsulation sorununu
!importantile çözmeye çalışmak: kural element'e hiç uymuyorsa önceliği artırmak hiçbir şey değiştirmez; Styles'ta kuralın listede olup olmadığına bak. ::ng-deep'i:hostolmadan yazmak: kural global olur ve başka sayfaları bozar.- Zorlanan
:hovdurumunu açık unutmak ve davranışı yanlış yorumlamak. - Device Mode'daki akıcılığı gerçek cihaz performansı sanmak.
Küçük Bir Deneme
★☆☆ Bookstore listesinde inspect modunu (Ctrl+Shift+C) açıp bir fiyata tıkla. Styles'ta kuralı bul, renk değerine tıklayıp color picker ile değiştir, sonra sayfayı yenile ve değişikliğin gittiğini gör. Aynı element için Computed sekmesinde color'u açıp değerin hangi dosyadan geldiğini oku.
★★☆ BookCard'a .title:hover { text-decoration: underline; } ekle. :hov ile hover'ı zorla, sonra Console'da ng.getOwningComponent($0) ile başlığın hangi component'e ait olduğunu doğrula. $0'ın sen Elements'te başka bir node seçince değiştiğini gözle.
★★★ Fiyat kalınlığını ::ng-deep olmadan, --book-price-weight custom property'siyle çöz ve BookList dışında bir yerde, örneğin sepet sayfasında, aynı kartın kalınlığını farklı bir değere ayarla. Styles'ta var() değerinin üzerine gelip hangi kuraldan çözümlendiğini her iki sayfada da kontrol et.
Elements'te gördüğün her şeyi konsolda da sorgulayabildiğini fark ettin: $0 ve ng.getComponent buzdağının görünen kısmı. Bir sonraki yazıda Console panelini derinlemesine açıyoruz: console.table'dan Live Expression'a, getEventListeners'tan Angular'ın ng global'lerinin tamamına.