Your First Angular Component: The Anatomy of Decorator, Selector and Template
We take apart an Angular component: the @Component decorator, selector, template and styles. We generate one with the CLI and see the component tree and style encapsulation.
Version note: The code and commands in this post are for Angular 12.
Everything on the Screen Is a Component
Once you look at a web page through Angular glasses, you stop seeing one big chunk of HTML. The menu at the top is a component, the category list on the left is a component, and every product card in the middle is a component of its own. They snap together like LEGO bricks, and what we call a page is just those bricks put together.
The welcome page you saw in the project we created earlier was also a single component: AppComponent. In this post we will open it up, see what lives where, and then build our own component from scratch.
A Look Inside AppComponent
Open src/app/app.component.ts in the project. You will see something like this:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'ilk-uygulama';
}
There are three parts, and every component needs all three. At the bottom sits a plain TypeScript class: AppComponent. It has a single property called title. The class is the brain of the component; it holds the data and defines the behavior.
The @Component({...}) line right above the class is a decorator. A decorator is a label that tells a class "you are not an ordinary class, you are a component." On its own, AppComponent is just a TypeScript class; Angular recognizes it because of this label and places it on the screen according to the settings we pass in.
The import line at the top brings the Component decorator in from Angular's core package. Every Angular piece you use comes in this way.
The Three Settings Inside the Decorator
selector is the component's name in HTML. Because we wrote 'app-root', we use this component in a template with the <app-root></app-root> tag. If you open src/index.html, you will find exactly this tag inside the body. When the app starts, Angular finds that tag and renders AppComponent's content in its place.
templateUrl points to the HTML file that defines how the component looks. styleUrls is the list of CSS files for that view; it is an array because you can pass more than one stylesheet.
Think of a restaurant: the class is the kitchen that prepares the food. The template is the plate that decides how the food is served. The stylesheet is the garnish on the plate. The selector is the dish's name on the menu; say that name to the waiter and the plate arrives at your table.
Showing Data in the Template
To carry data from the class into the template, we use double curly braces. Clear the contents of app.component.html and write this:
<h1>{{ title }}</h1>
<p>This page is rendered by AppComponent.</p>
Save it and you will see ilk-uygulama on the screen. The {{ title }} expression prints the value of the class's title property into the template. Change the value in the class and the screen changes too. This is called interpolation, and it is the simplest form of data binding. We will cover the other kinds of data binding one by one in upcoming posts.
Generating Our Own Component
Instead of creating four files by hand and wiring them together, we let the Angular CLI do it. From the project folder, run this in the terminal:
ng generate component product-card
The short form ng g c product-card does the same thing. The CLI output looks like this:
CREATE src/app/product-card/product-card.component.css (0 bytes)
CREATE src/app/product-card/product-card.component.html (27 bytes)
CREATE src/app/product-card/product-card.component.spec.ts (654 bytes)
CREATE src/app/product-card/product-card.component.ts (291 bytes)
UPDATE src/app/app.module.ts (497 bytes)
Four files were created and one was updated. The .ts file holds the class and decorator, .html the template, .css the styles, and .spec.ts the component's tests. We will get to testing in a later post; for now you can leave that file alone.
The line that deserves your attention is the UPDATE at the end. The CLI added the new component to the declarations list in app.module.ts by itself. In Angular 12 every component must be registered in a module; if you try to use an unregistered component in a template, you get an "is not a known element" error. We will dig into what modules are for in the next post.
Reading the Generated Component
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-product-card',
templateUrl: './product-card.component.html',
styleUrls: ['./product-card.component.css']
})
export class ProductCardComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}
A familiar skeleton. The CLI automatically put the app- prefix on the selector; that prefix exists to separate your own components from standard HTML tags and from components of other libraries. The class name became ProductCardComponent, following Angular's naming convention.
You also see two extra things: an empty constructor and an ngOnInit method. ngOnInit is a lifecycle hook that Angular calls while preparing the component for the screen. The difference between it and the constructor matters, but for now you can leave both empty; lifecycle hooks get a post of their own.
Now let's give the component some content. Add two properties to the class:
export class ProductCardComponent implements OnInit {
productName = 'Mechanical Keyboard';
price = 1450;
constructor() { }
ngOnInit(): void {
}
}
And fill in the template like this:
<div class="card">
<h3>{{ productName }}</h3>
<p>{{ price }} TL</p>
</div>
Using the Component: The Component Tree
The component is ready, but nobody is calling it yet. Go back to app.component.html and write the selector like an HTML tag:
<h1>{{ title }}</h1>
<app-product-card></app-product-card>
<app-product-card></app-product-card>
Two product cards appear on the screen. You can use the same component as many times as you like; each usage is its own independent instance. Changing the data of one card does not affect the other.
What we just built is a tree. AppComponent sits at the top, with two ProductCardComponents beneath it. In a real application the tree branches out: a page component, a list component inside it, card components inside the list. The best way to understand an Angular application is to think of it as that tree.
Both cards showing the same product looks a little odd right now. There is a way to send data from a parent component to a child component, and we will cover it when we get to component communication.
Why Don't Styles Leak Into Each Other?
Write this into product-card.component.css:
h3 {
color: crimson;
}
The card titles turned red, but the h1 in AppComponent and every other h3 on the page stayed the same. In plain CSS, an h3 selector would catch every h3 on the page. Angular, however, locks each component's styles inside that component.
If you inspect a card with the browser's developer tools, you will see odd attributes such as _ngcontent-xyz-c12 on the elements. During compilation Angular adds a unique attribute to each component's elements and ties the CSS selectors to that attribute. This behavior is called view encapsulation, and its default mode is ViewEncapsulation.Emulated. Picture each component having its own room: you paint the walls of your room and the hallway stays untouched.
For styles that concern the whole application, there is src/styles.css at the project root. Global things like fonts, the color palette, and reset rules go there.
Components Without Template and Style Files
For very small components, separate HTML and CSS files can feel like overkill. Use template instead of templateUrl and styles instead of styleUrls in the decorator to write everything in one file:
@Component({
selector: 'app-badge',
template: `<span class="badge">New</span>`,
styles: [`.badge { background: gold; padding: 2px 8px; }`]
})
export class BadgeComponent { }
Because we use backticks, the template can span multiple lines as well. The rule of thumb is simple: templates that stay within a few lines can be inline, everything else goes into its own file. As a template grows, reading HTML inside a TypeScript file quickly becomes tiring.
If you want the CLI to generate an inline component from the start, use ng g c badge --inline-template --inline-style.
A Small Experiment
Your turn. Use the CLI to generate a new component called user-profile. Add name, role, and city properties to its class and show all three inside a card in the template. Then place the component three times in AppComponent's template.
Next, break two small things on purpose. First, open app.module.ts, delete the UserProfileComponent line from the declarations list, and read the error in the terminal. After putting the line back, add a p { color: teal; } rule to the component's stylesheet and check whether the paragraphs in AppComponent are affected.
Why does that declarations list matter so much, and what exactly does a module define, and for whom? In the next post we put NgModule, the backbone of an Angular 12 application, on the table.