9. Module Introduction
In this section, we will explore the essential Angular concepts every developer needs to master. We’ll do this by building a complete demo application from the ground up.
As we develop this demo app and implement its features, you will gain a clear understanding of the Angular project structure—how Angular projects are organized and the purpose of the various files and folders.
You will also dive deeper into the fundamental concept of working with components, which was introduced in the first course section. Additionally, we’ll cover other core topics such as TypeScript fundamentals and writing declarative code.
Specifically, you’ll learn how to handle user events like clicks and respond to them effectively. Moreover, you’ll discover how to write Angular code that enables dynamic rendering and updating of the user interface.
By the end of this section, you will be equipped to build dynamic, interactive web applications—just like the demo app we create together.
10. A New Starting Project & Analyzing The Project Structure
npm install -g @angular/cli
ng new prompts-app
cd prompts-app
The content explains the setup and structure of a new Angular project created with the Angular CLI, recommending the use of a provided starter project to ensure a consistent starting point. It highlights that different CLI versions may create slightly different project structures, such as the location of the favicon file, but the Angular code remains the same.
Key points covered include:
-
The root-level files are mostly configuration files for TypeScript, Angular CLI, package management, code formatting, and version control.
-
The SRC folder is the main area where development happens, especially the app folder where Angular components are built.
-
File naming conventions for components have evolved, with older projects using "app.component.ts" and newer Angular versions possibly omitting ".component" in filenames, but this does not affect functionality.
-
Important files in SRC include global styles (style.css), the main HTML file (index.html), the favicon, the main TypeScript entry file (main.ts), and an assets folder for images.
-
To resolve initial errors due to missing dependencies, users should run
npm install
in the project folder once to install all dependencies. -
After installation, running
npm start
will launch the Angular development server, allowing preview of the app in a browser. -
The next step will be to explore how the app content is rendered on the screen.
Overall, the explanation provides a foundational understanding of the Angular project structure, configuration, and initial setup steps for development.
|
11. Understanding Components & How Content Ends Up On The Screen
The content explains how Angular renders content on the screen starting from an almost empty index.html
file containing a custom <app-root>
element. This element is not standard HTML, so Angular takes over by executing code in main.ts
, which is compiled from TypeScript to JavaScript by the Angular CLI. When the app runs (e.g., via ng serve
), the CLI injects necessary script tags into the HTML automatically.
The key function executed is bootstrapApplication
, which takes an Angular Component as an argument. This Component corresponds to the custom element tag in the HTML (app-root
). The Component is defined as a TypeScript class decorated with @Component
, a decorator that adds metadata to the class, turning it into an Angular Component.
The @Component
decorator specifies a selector (app-root
), a template URL (pointing to an external HTML file with the component’s markup), and styles scoped to the component. Angular replaces the <app-root>
tag in the index.html
with the component’s template content, rendering the title, subtitle, and image seen on the screen.
In summary, Angular compiles and injects scripts, bootstraps a root component linked to a custom HTML tag, and replaces that tag with the component’s template and styles, enabling dynamic content rendering in the browser.
|
12. Creating a First Custom Component
The content explains how to build an Angular demo application by breaking the UI into multiple components, such as a header, sidebar, and dialog. Angular encourages creating these UI building blocks as individual components and composing them together. To start, the example focuses on creating a header component.
Key points include:
-
Angular components typically consist of multiple files working together, commonly named with a pattern like
header.component.ts
. However, with Angular 20, the recommended naming has simplified to justheader.ts
. -
The naming convention is flexible and does not affect functionality, but descriptive names are preferred for clarity.
-
Components are defined as exported TypeScript classes enhanced with a
@Component
decorator imported from Angular’s core package. -
The example shows creating a
HeaderComponent
class with an empty body initially, decorated with@Component()
to mark it as an Angular component.
Overall, the approach emphasizes modular UI design in Angular by creating reusable components, starting with the header as the first step in building the demo app.
|
14. Configuring the Custom Component
The text explains how to create a custom Angular component, specifically a header component, focusing on key configuration aspects:
-
Selector: Should be a tag with at least two words separated by a dash (e.g.,
app-header
) to avoid conflicts with built-in HTML elements like<header>
. The prefix (likeapp
) is customizable. -
Template: While you can define a template inline as a string in the TypeScript file, it is recommended to use an external HTML file for anything beyond very simple templates. This is done via the
templateUrl
property, pointing to a relative path like./header.component.html
. -
Standalone Property: The
standalone
property should be set totrue
to mark the component as a Standalone Component, which is the modern Angular approach. In Angular 19+, this is true by default and can be omitted; for earlier versions, it must be explicitly set. -
Component Types: Angular supports both module-based components (older style) and standalone components (newer, simpler to use). The recommendation is to use standalone components going forward.
The example includes creating the external HTML file with basic markup (a <header>
element containing an <h1>
), and notes that styles and further content can be added later. The explanation ends by posing the question of how to use the newly created header component.
|
15. Using the Custom Component
The explanation covers how to properly use a custom Angular header component within an application:
-
Simply adding the custom component’s tag (e.g.,
<app-header>
) in theindex.html
won’t render it because Angular doesn’t automatically detect or render components placed directly in the HTML. -
Angular requires explicit registration of components. The
bootstrapApplication
function is used to tell Angular which root component to render. -
While you can bootstrap multiple components separately, the typical Angular approach is to have a single root component (usually
AppComponent
) and build a tree of nested components. -
To use the header component inside the app component’s template, you add its selector tag there.
-
However, this causes an error ("not a known element") unless you explicitly import the header component into the app component.
-
This is done by importing the header component class in the app component’s TypeScript file and adding it to the
imports
array of the app component’s configuration (leveraging Angular’s standalone components feature). -
Once imported properly, Angular recognizes the header component in the app component’s template, and it renders correctly without errors.
-
This approach enables components to be part of the same Angular application tree, allowing them to communicate and share data effectively.
In summary, Angular requires explicit component registration and encourages building a component tree with a single root component, importing child components where needed to render them properly.
|
16. Styling the Header Component & Adding An Image
The content explains how to style an Angular header component by creating a separate CSS file (header.component.css
) and linking it via the styleUrl
or styleUrls
property in the component’s TypeScript file. Inline styles are possible but discouraged. It provides prepared CSS and assets (like a logo image) to be added to the project, including updating the global styles.css
and index.html
to import Google Fonts. The header.component.html
is updated to include an image from the assets folder, with instructions to ensure the angular.json
file properly references the assets path so images load correctly. Additional markup changes include wrapping the header text in a div and adding a descriptive paragraph. Once these changes are made and the development server is running, the styled header component will display correctly, marking the completion of the first custom component.
|
17. Managing & Creating Components with the Angular CLI
The content explains the process of creating and managing Angular components efficiently. Initially, it describes building a custom header component manually and highlights that as the number of components grows, organizing component files into feature-based subfolders (e.g., a "header" folder) inside the app folder is a common practice to maintain a clean structure. After moving files, import paths should be updated accordingly.
Next, it introduces the Angular CLI as a tool to streamline component creation. Instead of manually creating folders and files, developers can use commands like ng generate component
(or the shorthand ng g c
) followed by the component name (e.g., "user") to automatically generate the component files in a new folder. The CLI creates the standard files (HTML, TypeScript, CSS, and a test spec file) following naming conventions and sets up the component with a selector, external style links, standalone configuration, and an imports array for dependencies. The test file can be deleted if not needed immediately. This approach saves time and ensures consistency in component setup.
|
18. Styling & Using Our Next Custom Component
The user component was updated to include a div containing a button with a user image and a span for the user’s name. CSS styles were provided to improve its appearance. The user component’s TypeScript file required no changes. To use this component in the app component’s template, it was imported and added to the imports array, with Visual Studio Code offering a quick fix to automate this. The app component template was refined by wrapping the user component inside a main element and an unordered list with styling applied via updated CSS. The user component now displays but lacks the actual user image and name, which will be addressed next.
|
19. Preparing User Data (To Output Dynamic Content)
The current app uses placeholder images and names, which are not final and the image isn’t displaying yet. The goal is to support multiple users by using a provided dummy.users.ts
file containing an array of user data (ID, name, image identifier). User images are supplied in a downloadable zip file, which should be extracted and placed into an assets/users folder, matching the image identifiers in the dummy data. The next step is to randomly select a user from this list and display their name and image dynamically in the user component. This requires learning Angular features to render dynamic content, moving beyond the previously static markup.
|
20. Storing Data in a Component Class
Goal: display a randomly chosen user’s data in an Angular component.
In user.component.ts
-
Add a class property (e.g.
selectedUser
) directly in the component class body. -
Import the
DUMMY_USERS
array from../dummy-users.ts
. -
Define a helper constant outside the class:
const randomIndex = Math.floor(Math.random() * DUMMY_USERS.length);
-
Initialize your property with a random entry:
selectedUser = DUMMY_USERS[randomIndex];
Because it’s a class property, Angular will expose selectedUser
to the template.
In user.component.html
-
Use interpolation to output fields of the randomly chosen user, for example:
<h2>{{ selectedUser.name }}</h2>
<p>{{ selectedUser.email }}</p>
That’s all it takes to bind dynamic (random) user data from your TypeScript class into the component’s HTML.
|
21. Outputting Dynamic Content with String Interpolation
Angular lets you bind dynamic data from your component class into your templates in (at least) two ways. The most straightforward is string interpolation: wrap any public (not private) component property in double curly braces, e.g.
{{ selectedUser.name }}
Here, Angular’s tooling (for example in VS Code) will even auto-complete available properties and types. In our example, dummyUsers
is an array of objects each with id
, name
, and avatar
, so selectedUser.name
inserts that user’s name into the view. Because the component picks a random user on each reload, you’ll see different names appearing whenever you refresh.
|
22. Property Binding & Outputting Computed Values
Angular provides two primary ways to insert dynamic data into your templates:
-
String interpolation (
{{ … }}
)-
Ideal for embedding values in text nodes or between HTML tags.
-
You can even include simple expressions (e.g.
1 + 1
).
-
-
Property binding (
[property]="…"
)-
The recommended way to set element attributes or DOM properties (e.g.
<img>
’ssrc
oralt
). -
Syntax: enclose the element’s property name in square brackets and assign it a JavaScript expression—no curly braces.
-
Allows you to build dynamic strings on the fly, for example:
<img [src]="'assets/users/' + selectedUser.avatar" [alt]="selectedUser.name">
-
By combining these techniques, you can display text and configure element attributes dynamically—essential for building interactive Angular applications.
|
24. Using Getters For Computed Values
Instead of building complex strings or computations directly in your Angular templates, it’s better to move that logic into your component class via a getter. For example:
-
In your component class, define
get imagePath() { return '/assets/avatars/' + this.selectedUser.avatar + '.png'; }
– note theget
keyword makes it behave like a property rather than a method.
– inside the class you refer to other properties withthis.selectedUser
. -
In your template, bind to it just like any other property:
<img [src]="imagePath">
– no parentheses needed.
This keeps your template markup simpler and delegates all string‐construction or other computations to the class.
|
25. Listening to Events with Event Binding
The content explains how to handle user input events in Angular by adding event listeners to elements in templates. Specifically, it shows how to listen for a button’s click event by using Angular’s syntax: placing the event name (e.g., "click") inside parentheses on the element, followed by an equal sign and a method call in quotes. The method, defined in the component class (commonly prefixed with "on" like onSelectUser), contains the code to execute when the event occurs. For example, logging "Clicked" to the console. When the button is clicked, the method runs, demonstrating how to respond to user interactions and update the UI accordingly.
|
26. Managing State & Changing Data
The excerpt explains how to combine event handling and dynamic data binding in Angular to update the UI whenever a user is clicked. Instead of logging to the console, you store the clicked user in a component property (often called “state,” here selectedUser
). To pick a different user on each click, you move the random‐index calculation into the click handler method so it runs every time. Assigning the newly selected user to the component property automatically updates the rendered template—no extra setup needed.
|
27. A Look Behind The Scenes Of Angular’s Change Detection Mechanism
Angular automatically updates the UI whenever component data (state) changes. It does this by running its change-detection process, which compares the component’s template against the current data and applies any necessary DOM updates. Under the hood, Angular uses zone.js to hook into browser events (user interactions, timers, etc.). Whenever such an event fires, zone.js notifies Angular to run change detection, so you don’t have to manually tell the framework when to refresh the view.
|
28. Introducing Signals
Here’s a concise summary of the key points:
-
Traditional Angular state updates
– Since Angular 2, components have used plain properties and Zone.js–driven change detection.
– Zone.js tracks all async events and then diffs every component to see what needs re-rendering. -
Signals: a new reactive primitive (Angular 16/17)
– Importsignal
from@angular/core
and create one via
selectedUser = signal(initialUser)
– Read in a template or code by calling it as a function:
{{ selectedUser() }}
– Update by calling.set(newValue)
, e.g.
selectedUser.set(newUser)
-
Benefits of Signals
– Fine-grained tracking: Angular knows exactly which template bindings depend on which signals and only updates those.
– No more Zone.js overhead—more efficient change detection. -
Computed values
– Use the computed
helper from @angular/core
:
imagePath = computed(() => `assets/users/${selectedUser().avatar}`)
– Under the hood, computed
returns a signal that re-evaluates only
when its dependent signals change.
– You also read computed signals by calling them (imagePath()
).
-
Compatibility
– Signals are optional and require Angular 16+ (fully stable in 17).
– Older codebases or teams not ready for Signals can continue using the classic zone-based approach.
– This course will cover both approaches, with a deeper dive into Signals later.
|
29. We Need More Flexible Components!
The instructor recaps that they’ve already gone through all the core Angular building blocks, but the demo app’s UI still only supports a single, randomly chosen user. The next goal is to turn the existing UserComponent into a truly reusable piece:
-
Remove the random-index logic, the selection state, and the related imports (compute, signal, etc.).
-
Expose the user data via @Input() properties on the UserComponent so that each instance can be fed a different user.
-
In the AppComponent template, render the UserComponent multiple times (or via *ngFor) with different inputs, so clicking any user item loads that user’s tasks on the right.
This approach leverages Angular’s component inputs to keep each user item simple, configurable, and reusable.
|
30. Defining Component Inputs
Here’s a concise summary of the steps and concepts covered:
-
Exposing a component property as an input
-
In the child component, add a class property (e.g.
avatar
) and decorate it with@Input()
(imported from@angular/core
). -
This tells Angular that the property’s value will be provided from the parent.
-
-
Binding data in the parent component
-
Import your data (e.g.
dummyUsers
) into the parent’s TypeScript file. -
Expose it via a property (e.g.
users = dummyUsers;
) so the template can access it. -
In the parent template, use property binding to pass each user’s data into the child:
[avatar]="users[0].avatar"
,[name]="users[0].name"
, etc.
-
-
Dealing with TypeScript’s strict checks
-
Annotate your input property with a type, e.g.
@Input() avatar: string;
. -
To satisfy “definitely assigned” checks, use the non-null assertion:
@Input() avatar!: string;
-
-
Adding more inputs
-
Repeat the process for additional fields like
name
. -
Bind them similarly in the parent template.
-
-
Computing derived values in the child
-
Use a getter to build the full image path, for example:
get imagePath() { return 'assets/users/' + this.avatar; }
-
-
Final template tweaks
-
Remove any leftover signal-style parentheses on property interpolations.
-
Ensure event bindings (e.g.
(click)
) remain unchanged.
-
Result: a reusable user component that takes avatar
, name
(and any other inputs) from its parent and renders a list of users dynamically.
|
31. Required & Optional Inputs
The speaker is refactoring an Angular component to accept its avatar and name via @Input properties rather than hard-coding them. They initially used TypeScript’s non-null assertion (!
) to convince the compiler those values would always be present—but that’s unsafe, since omitting one of these inputs at runtime (e.g. forgetting to pass name
) would lead to a missing-data error. Angular’s @Input
decorator can take a configuration object with a required: true
option. By adding required: true
, the framework and IDE will issue a compile-time/error if a caller fails to provide that input, aligning TypeScript’s guarantees with actual usage and catching mistakes earlier in development.
|
32. Using Signal Inputs
Angular lets you accept component inputs in two ways: the classic @Input decorator and the newer “signal”–based approach. Here’s a high-level overview of the signal approach and how it compares to the decorator approach:
-
Defining a signal input
-
Import the lowercase input function from @angular/core.
-
Instead of decorating a property, assign it a signal:
– Optional with a default:
avatar = input('')
– Required (no default):
name = input.required()
-
The generic
<string>
tells TypeScript (and Angular) what type the signal will carry.
-
-
Reading signal inputs
-
In your component’s template or code, call the signal as a function to get its current value, e.g.
name()
orimagePath()
. -
Use the computed() function to derive other reactive values (e.g. an image path) — Angular will only recompute when its dependent signals change.
-
-
Read-only nature
-
Input signals are read-only. You cannot call set() on them inside the component; they update only when the parent changes the bound value.
-
-
How it’s used from the outside
-
Parents bind to signal inputs exactly as they do with decorator inputs—property binding or literal values. The parent doesn’t need to use signals itself.
-
-
Pros and cons
-
Pros: built-in reactivity, fine-grained updates, more efficient UI updates where it matters.
-
Cons: signals are a newer Angular feature not yet ubiquitous in large codebases, so you’ll still see and often need to use @Input.
-
Because of widespread legacy code and to cover both styles, the course will continue primarily with the decorator-based @Input approach, but you’ll now know how to do both.
|
33. We Need Custom Events!
Angular components don’t just consume data via @Input; they can also emit events back to their parent using @Output properties. In the example:
-
The UserComponent receives a user via an input.
-
When its button is clicked, it needs to tell its parent (AppComponent) “this user was clicked.”
-
Rather than rendering tasks itself, the UserComponent emits a custom event carrying the clicked user.
-
The AppComponent, which instantiated UserComponent, listens for that event and then displays the corresponding tasks alongside the user list.
This child-to-parent communication is made possible by defining an @Output property (an EventEmitter) in the child component and binding to it in the parent.
|
34. Working with Outputs & Emitting Data
Here’s a concise rundown of how to emit and handle a custom event in Angular:
-
Define the Output in the child component
-
Import and use the @Output decorator and EventEmitter from @angular/core
-
Give the property a name (e.g. select) and type it:
@Input() id!: string; @Output() select = new EventEmitter<string>();
-
In your click handler (e.g. onSelectUser()), call this.select.emit(this.id) to fire the event.
-
-
Bind to the custom event in the parent template
-
Pass in the child’s inputs and listen for the output with standard event binding:
<app-user [id]="user.id" (select)="onSelectUser($event)"> </app-user>
-
$event holds the value you emitted (here, the user ID).
-
-
Handle it in the parent component class
-
Declare the handler with the correct type:
onSelectUser(id: string) { console.log('Selected user with id', id); }
-
And don’t forget to add any necessary type annotations so TypeScript stays happy.
|
35. Using the output() Function
Here’s a concise summary of the key points:
-
Traditional @Output decorator
– You declare an EventEmitter manually and decorate it with @Output.
– You call its emit() method to fire events and listen via (eventName) in parent templates. -
New output() function
– Imported from @angular/core, it replaces both the decorator and manual EventEmitter creation.
– Usage:
select = output();
// then call select.emit(value) as usual
– You must specify a generic type (e.g.<string>
) so TypeScript knows what you’ll emit.
– Under the hood it still creates an EventEmitter, not a signal. -
Why it exists
– Lets you avoid any decorators (similar to the input() function for @Input) if you prefer “decorator-free” components.
– Slightly more concise syntax. -
Adoption
– Functionally equivalent to @Output + new EventEmitter
– Not yet widely used—most Angular codebases still use @Output
– You’ll see both approaches, but the decorator remains the most common today.
|
36. Adding Extra Type Information To EventEmitter
-
You can declare component outputs with either the @Output decorator or the output(…) helper.
-
When you manually instantiate an EventEmitter, you can still add a generic type—e.g.
new EventEmitter<string>()
—to tell TypeScript/Angular exactly what value type you’ll emit. -
It isn’t strictly required (the code will run without it), but adding it prevents accidentally emitting the wrong type (e.g. emitting a number when your parent expects a string), giving you extra compile-time safety.
|
37. Exercise - Create a Configurable Component
Here’s a concise summary of the exercise and solution:
-
Exercise goal
-
Extract the “tasks” section in AppComponent into its own TasksComponent
-
Pass in and display the currently selected user’s name via an @Input()
-
-
Generate the new component
-
Run
ng g c tasks --skip-tests
to scaffold TasksComponent without test files
-
-
Implement TasksComponent
-
In tasks.component.ts import
Input
from@angular/core
-
Declare a required input property, e.g.
@Input({ required: true }) name!: string;
-
In tasks.component.html simply interpolate the name, e.g.
<h2>{{ name }}</h2>
-
-
Hook it up in AppComponent
-
Add
<app-tasks [name]="selectedUser.name"></app-tasks>
to app.component.html -
Ensure TasksComponent is declared or imported in AppComponent’s decorator
-
-
Manage selected user in AppComponent
-
Store
selectedUserId
(initialized to the first user’s ID) -
Update it in
onSelectUser(id: string)
instead of logging -
Expose a
get selectedUser()
getter that does
return this.users.find(u => u.id === this.selectedUserId)!;
(the!
tells TypeScript it will never be undefined)
-
Result: clicking a user updates selectedUserId
, the getter finds the
matching user, and TasksComponent displays that user’s name.
|