Home / Articles / Angular Components from the Ground Up: Selectors, Templates, Inputs, Outputs

This article is published in English.

Angular Components from the Ground Up: Selectors, Templates, Inputs, Outputs

A practical tour of modern standalone Angular components: file layout, the decorator, selectors, templates, scoped styles, and parent-child data flow with input() and output().

2504 words

Once an Angular project is created and running, the next question is how the screen itself gets built. The answer is components: small, self-contained units that each own one part of the interface. This guide walks through what a component is made of, how standalone components change the picture compared with older NgModule-based code, and how a parent and a child component exchange data and events using the current input() and output() APIs. By the end you should be able to generate a component, wire it into a page and pass information in both directions.

If you still need a project to experiment in, the walkthrough on setting up a modern Angular 22 project covers the setup and folder structure this article assumes.

Why Angular splits the UI into components

Picture an ordinary web page: a navigation bar across the top, a sidebar, some product content in the middle and a footer underneath. You could put all of that markup, styling and behavior into one enormous file, but it would quickly become hard to read and harder to change. Angular encourages you to carve the page into pieces instead, each with a single job.

Angular Application
│
├── Navbar Component
├── Sidebar Component
├── Product Component
├── Login Component
└── Footer Component

A component typically bundles four things: a TypeScript class holding state and behavior, an HTML template describing the markup, styles for its appearance, and metadata telling Angular how to treat it. Because each piece has a clear responsibility, you can reason about the navbar without thinking about the footer, reuse the same product card in several places, and test parts of the UI in isolation. A login form, a profile panel or a dashboard widget are all natural candidates.

Standalone components are the default

Many Angular tutorials, especially older ones, organize components inside an NgModule, which declares components and lists what they depend on. Modern Angular has moved away from that model. Components are now standalone by default, so a component can stand on its own without being registered in a module. A minimal component looks like this:

import { Component } from '@angular/core';
@Component({
  selector: 'app-welcome',
  templateUrl: './welcome.html',
  styleUrl: './welcome.css'
})
export class WelcomeComponent {
}

You might expect to see a flag marking the component as standalone, like the following line, but it is no longer required:

standalone: true

Leaving it out has the same effect because standalone is the default behavior. When a standalone component needs another component, directive or pipe, it lists that dependency in the imports array of its own @Component configuration. The upside is locality: you can open a single file and see exactly what that component relies on, instead of hunting through a module definition elsewhere in the project.

How a component is laid out on disk

A typical component lives in its own folder with three files side by side:

welcome/
│
├── welcome.ts
├── welcome.html
└── welcome.css

The .ts file holds the logic, the .html file holds the markup and the .css file holds the styles. Here is what each one might contain for a simple welcome component. First the class, which exposes a title property to the template:

import { Component } from '@angular/core';
@Component({
  selector: 'app-welcome',
  templateUrl: './welcome.html',
  styleUrl: './welcome.css'
})
export class WelcomeComponent {
  title = 'Welcome to Angular 22';
}

The template reads that property with double curly braces:

<h1>{{ title }}</h1>
<p>
  This is my first Angular component.
</p>

And the stylesheet targets elements in that template only:

h1 {
  color: #dd0031;
}
p {
  font-size: 18px;
}

The @Component() decorator is the glue. It points Angular at the template and stylesheet so that the three files behave as one unit, as the following sketch shows:

welcome.ts
    │
    ├── Logic
    │
    ├── welcome.html
    │      ↓
    │    UI
    │
    └── welcome.css
           ↓
         Style

Angular's style guide recommends keeping closely related files like these in the same directory, which is exactly what this layout does. When a component grows, you know precisely where its markup, styles and logic live.

What the @Component() decorator tells Angular

The decorator attaches metadata to the class. That metadata is how Angular learns what the class is for and how to render it:

@Component({
  selector: 'app-welcome',
  templateUrl: './welcome.html',
  styleUrl: './welcome.css'
})

Three properties do most of the work here:

  • selector defines the tag name you use to place the component in other templates.
  • templateUrl points to the HTML file with the component's markup.
  • styleUrl points to the component's stylesheet.

There are further options, such as imports for pulling in other components or Angular features, and inline alternatives to templateUrl and styleUrl that appear later in this article. There is no need to learn them all up front; they become familiar as you build more components.

Selectors turn a class into a custom element

The selector is the identifier Angular looks for inside templates. In this configuration it is set to app-welcome:

@Component({
  selector: 'app-welcome',
  ...
})

So the selector value itself is simply:

app-welcome

Anywhere you want the component to appear, you write that name as if it were an HTML tag:

<app-welcome></app-welcome>

When Angular encounters <app-welcome> while rendering a template, it creates an instance of the matching component and renders its template inside that element. The element that matches the selector is called the component's host element, and it remains in the DOM, which matters later when you want to style or attach behavior to the component as a whole.

The Angular CLI generates selectors with the prefix configured for your application, usually app-, although you can choose a different selector when generating a component. The prefix is more than cosmetic: custom element names should contain a hyphen, and a project-specific prefix keeps your components from colliding with native elements or with tags from third-party libraries.

Templates connect markup to component data

The template defines what the user actually sees. At its simplest it is plain HTML:

<h1>Welcome!</h1>
<p>We are learning Angular components.</p>

What makes Angular templates more capable than static HTML is that they can read data and call behavior defined on the component class. Suppose the class declares a property:

name = 'Mubbassir';

The template can then display it through interpolation:

<h1>Hello {{ name }}!</h1>

Angular keeps the rendered output in sync with that data: when the bound value changes, the page updates without you touching the DOM yourself. This link between the TypeScript class and its template is the idea everything else in Angular builds on, from event handling to forms.

Component styles stay scoped by default

Each component can carry its own styles:

.card {
  padding: 20px;
  border-radius: 10px;
}
h2 {
  color: #dd0031;
}

By default Angular applies emulated view encapsulation to these rules. At build time it rewrites the selectors and adds generated attributes to the component's elements, so a rule such as h2 { ... } only matches the h2 elements in this component's template rather than every heading in the application. In an app with hundreds of components, that protection prevents a style tweak in one place from quietly breaking the look of another.

Encapsulation does not rule out shared styling. Rules that should apply everywhere, such as typography, resets or layout utilities, belong in the application's global stylesheet, while component files hold the styles specific to that component.

Generating a component with the Angular CLI

Creating the files by hand works, but the CLI does it faster and with consistent naming. From the project root, run:

ng generate component user-card

or the abbreviated form:

ng g c user-card

The generator produces a folder like this one (the stray = after the spec filename is a typo in the listing, not part of the name):

user-card/
├── user-card.ts
├── user-card.html
├── user-card.css
└── user-card.spec.ts=

The newer file naming style is shorter than the older user-card.component.ts convention. Whether a .spec.ts test file is created depends on the project's testing configuration. Recent CLI versions following the updated style guide also tend to generate class names without the Component suffix (for example UserCard); the examples below keep the suffix, and either naming works as long as imports match.

How parent and child components talk to each other

Real screens are trees of components, and they need to share information. Consider a parent component that knows about a user and a child component whose job is to display that user. The parent needs a way to hand data down, and the child needs a way to report back when something happens, such as a button click.

Angular handles the downward direction with inputs and the upward direction with outputs:

Parent Component
      │
      │  Data
      │
      ▼
Child Component
      │
      │  Event
      │
      ▼
Parent Component

For new code, Angular recommends the signal-based input() function and the output() function. The decorator-based @Input() and @Output() APIs remain fully supported, so you will still see them in existing codebases.

Passing data down with input()

Start with the child component class:

import { Component, input } from '@angular/core';
@Component({
  selector: 'app-user-card',
  templateUrl: './user-card.html',
  styleUrl: './user-card.css'
})
export class UserCardComponent {
  name = input.required<string>();
}

The line that matters is the input declaration:

name = input.required<string>();

It declares that UserCardComponent expects a name string from whoever uses it. Using input.required() makes that expectation strict: if a parent forgets to bind name, Angular reports an error instead of silently rendering an empty value. For optional inputs, plain input() accepts a default value instead.

Because name is a signal rather than a plain property, you read it by calling it. In the class you write:

this.name()

and in the template:

{{ name() }}

Forgetting the parentheses is a common early mistake; without them the template renders the signal function itself rather than its value. The child's template uses the input like this:

<div class="card">
  <h2>{{ name() }}</h2>
  <p>Welcome to Angular!</p>
</div>

On the parent side, you bind a value to the input when placing the child:

<app-user-card [name]="userName" />

If the parent class holds:

userName = 'Yuvaraj';

the child receives that string and renders it:

Yuvaraj

The square brackets are what make this a property binding:

[name]="userName"

They instruct Angular to evaluate userName as an expression on the parent and feed the result into the child's name input. Without the brackets, Angular would pass the literal text userName instead. Because the input is a signal, the child re-renders automatically whenever the parent's value changes.

Sending events up with output()

Now give the child a button, and let the parent find out when a user is selected. The child declares an output and a method that emits through it:

import { Component, input, output } from '@angular/core';
@Component({
  selector: 'app-user-card',
  templateUrl: './user-card.html',
  styleUrl: './user-card.css'
})
export class UserCardComponent {
  name = input.required<string>();
  selected = output<string>();
  selectUser() {
    this.selected.emit(this.name());
  }
}

The output declaration defines a custom event that carries a string:

selected = output<string>();

The emit call fires that event and passes the current name as its payload:

this.selected.emit(this.name());

The child's template calls selectUser() when the button is clicked. Note the (click) syntax, which is Angular's event binding:

<div class="card">
 <h2>{{ name() }}</h2>
  <button (click)="selectUser()">
    Select User
  </button>
</div>

The parent listens for the custom event using the same parentheses syntax, this time with the output's name:

<app-user-card
  [name]="userName"
  (selected)="userSelected($event)"
/>

Round brackets mean "listen to this event", and $event holds whatever value the child emitted, here the user's name. The parent handles it in an ordinary method:

userSelected(name: string) {
  console.log('Selected user:', name);
}

This division of labor keeps the child reusable. It does not know or care what the parent does with a selection; it just announces that one happened. The parent decides whether to log it, navigate, or update its own state.

Putting a component on the page

The final example ties the pieces together. This version of the welcome component uses inline template and styles in the decorator instead of separate files, which is handy for very small components:

import { Component } from '@angular/core';

@Component({
  selector: 'app-welcome',
  template: `
    <div class="welcome-card">
      <h2>Welcome Component</h2>
      <p>This component was created using the <strong>app-welcome</strong> selector.</p>
    </div>
  `,
  styles: `
    .welcome-card {
      padding: 25px;
      margin-top: 20px;
      border-radius: 12px;
      background: #f5f5f5;
      text-align: center;
      font-family: Arial, sans-serif;
    }
    h2 {
      color: #dd0031;
    }
    p {
      font-size: 18px;
    }
  `
})
export class WelcomeComponent {}

The root component imports WelcomeComponent in its imports array, which is what makes the app-welcome selector usable in its template. Leaving it out is one of the most frequent causes of an "is not a known element" error in standalone apps:

import { Component } from '@angular/core';
import { WelcomeComponent } from './welcome';

@Component({
  selector: 'app-root',
  imports: [WelcomeComponent],
  templateUrl: './app.html',
  styleUrl: './app.css'
})
export class App {}

Finally, the root template places the component using its selector:

<div class="container">
<h1>Angular Components</h1>
  <p>
    Below is our custom Angular component:
  </p>
  <app-welcome></app-welcome>
</div>

Inline templates keep everything in one file, but they become awkward once the markup grows beyond a few lines. A reasonable rule is to start inline for tiny presentational pieces and move to separate .html and .css files as soon as the template needs real structure.

Key takeaways

  • A component combines a TypeScript class, a template and optional styles, tied together by the @Component() decorator.
  • Standalone is the default in modern Angular; declare dependencies in each component's imports array rather than in an NgModule.
  • The selector is the custom tag you use to place a component, and the matching element becomes its host.
  • Templates bind to class data, and Angular updates the view when that data changes.
  • Component styles are scoped through emulated encapsulation, while app-wide rules go in the global stylesheet.
  • Use input() (or input.required()) to pass data down and output() to send events up; read inputs by calling them as signals.

Seen this way, an Angular application is a tree of components, each responsible for its own slice of the interface and communicating through explicit inputs and outputs. That structure is what keeps large Angular apps understandable as they grow.