diff --git a/aio/content/examples/examples.bzl b/aio/content/examples/examples.bzl
index 8bdc67d3bb0..52609862948 100644
--- a/aio/content/examples/examples.bzl
+++ b/aio/content/examples/examples.bzl
@@ -51,6 +51,7 @@ EXAMPLES = {
"first-app-lesson-06": {"stackblitz": True, "zip": True},
"first-app-lesson-07": {"stackblitz": True, "zip": True},
"first-app-lesson-08": {"stackblitz": True, "zip": True},
+ "first-app-lesson-09": {"stackblitz": True, "zip": True},
"form-validation": {"stackblitz": True, "zip": True},
"forms": {"stackblitz": True, "zip": True},
"forms-overview": {"stackblitz": True, "zip": True},
diff --git a/aio/content/examples/first-app-lesson-09/BUILD.bazel b/aio/content/examples/first-app-lesson-09/BUILD.bazel
new file mode 100644
index 00000000000..1108db20e21
--- /dev/null
+++ b/aio/content/examples/first-app-lesson-09/BUILD.bazel
@@ -0,0 +1,11 @@
+load("//aio/content/examples:examples.bzl", "docs_example")
+load("@aio_npm//@angular/build-tooling/bazel/remote-execution:index.bzl", "ENABLE_NETWORK")
+
+package(default_visibility = ["//visibility:public"])
+
+docs_example(
+ name = "first-app-lesson-09",
+ # This example downloads fonts and images from googleapis.com
+ test_exec_properties = ENABLE_NETWORK,
+ test_tags = ["requires-network"],
+)
diff --git a/aio/content/examples/first-app-lesson-09/e2e/src/app.e2e-spec.ts b/aio/content/examples/first-app-lesson-09/e2e/src/app.e2e-spec.ts
new file mode 100644
index 00000000000..9d46ebdd336
--- /dev/null
+++ b/aio/content/examples/first-app-lesson-09/e2e/src/app.e2e-spec.ts
@@ -0,0 +1,39 @@
+import { browser, element, by, logging } from 'protractor';
+
+describe('first-app-lesson-09 app', () => {
+
+ beforeEach(() => browser.get(''));
+
+ it('should display correct title', async () => {
+ expect(await element.all(by.css('.brand-logo')).get(0).getAttribute('src')).toContain('/assets/logo.svg');
+ });
+
+ it('should have a filter string input', async () => {
+ expect(await element.all(by.css('input')).get(0).getAttribute('placeholder')).toEqual('Filter by city');
+ expect(await element.all(by.css('input')).get(0).getAttribute('type')).toEqual('text');
+ });
+
+ it('should have a search button', async () => {
+ expect(await element.all(by.css('button')).get(0).getText()).toEqual('Search');
+ });
+
+ it('should have a housing-location component', async () => {
+ expect(await element.all(by.css('.listing > .listing-location')).get(0).getText()).toEqual('Chicago, IL');
+ });
+
+ it('should have a title in the housing-location component', async () => {
+ expect(await element.all(by.css('.listing > h2')).get(0).getText()).toEqual('Acme Fresh Start Housing');
+ });
+
+ it('should have 10 housing-location components', async () => {
+ expect(await element.all(by.css('app-housing-location'))['length'] === 10);
+ });
+
+ afterEach(async () => {
+ // Assert that there are no errors emitted from the browser
+ const logs = await browser.manage().logs().get(logging.Type.BROWSER);
+ expect(logs).not.toContain(jasmine.objectContaining({
+ level: logging.Level.SEVERE,
+ } as logging.Entry));
+ });
+});
diff --git a/aio/content/examples/first-app-lesson-09/example-config.json b/aio/content/examples/first-app-lesson-09/example-config.json
new file mode 100644
index 00000000000..6b43b07a8db
--- /dev/null
+++ b/aio/content/examples/first-app-lesson-09/example-config.json
@@ -0,0 +1,6 @@
+{
+ "useCommonBoilerplate": false,
+ "overrideBoilerplate": [
+ "src/styles.css"
+ ]
+}
diff --git a/aio/content/examples/first-app-lesson-09/src/app/app.component.css b/aio/content/examples/first-app-lesson-09/src/app/app.component.css
new file mode 100644
index 00000000000..ca76bf56f4e
--- /dev/null
+++ b/aio/content/examples/first-app-lesson-09/src/app/app.component.css
@@ -0,0 +1,12 @@
+:host {
+ --content-padding: 10px;
+}
+header {
+ display: block;
+ height: 60px;
+ padding: var(--content-padding);
+ box-shadow: 0px 5px 25px var(--shadow-color);
+}
+.content {
+ padding: var(--content-padding);
+}
diff --git a/aio/content/examples/first-app-lesson-09/src/app/app.component.ts b/aio/content/examples/first-app-lesson-09/src/app/app.component.ts
new file mode 100644
index 00000000000..f53b0fe39c9
--- /dev/null
+++ b/aio/content/examples/first-app-lesson-09/src/app/app.component.ts
@@ -0,0 +1,24 @@
+import { Component } from '@angular/core';
+import { HomeComponent } from './home/home.component';
+
+@Component({
+ selector: 'app-root',
+ standalone: true,
+ imports: [
+ HomeComponent,
+ ],
+ template: `
+
+
+
+
+
+
+ `,
+ styleUrls: ['./app.component.css'],
+})
+export class AppComponent {
+ title = 'homes';
+}
diff --git a/aio/content/examples/first-app-lesson-09/src/app/home/home.component.css b/aio/content/examples/first-app-lesson-09/src/app/home/home.component.css
new file mode 100644
index 00000000000..b9b528d636f
--- /dev/null
+++ b/aio/content/examples/first-app-lesson-09/src/app/home/home.component.css
@@ -0,0 +1,39 @@
+.results {
+ display: grid;
+ column-gap: 14px;
+ row-gap: 14px;
+ grid-template-columns: repeat(3, 1fr);
+ margin-top: 50px;
+}
+
+input[type='text'] {
+ border: solid 1px var(--primary-color);
+ padding: 10px;
+ border-radius: 8px;
+ margin-right: 4px;
+ display: inline-block;
+ width: 30%;
+}
+
+button {
+ padding: 10px;
+ border: solid 1px var(--primary-color);
+ background: var(--primary-color);
+ color: white;
+ border-radius: 8px;
+}
+
+@media (min-width: 500px) and (max-width: 768px) {
+ .results {
+ grid-template-columns: repeat(2, 1fr);
+ }
+ input[type='text'] {
+ width: 70%;
+ }
+}
+
+@media (max-width: 499px) {
+ .results {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/aio/content/examples/first-app-lesson-09/src/app/home/home.component.ts b/aio/content/examples/first-app-lesson-09/src/app/home/home.component.ts
new file mode 100644
index 00000000000..5ba8e77c954
--- /dev/null
+++ b/aio/content/examples/first-app-lesson-09/src/app/home/home.component.ts
@@ -0,0 +1,39 @@
+import { Component, inject } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { HousingLocationComponent } from '../housing-location/housing-location.component';
+import { HousingLocation } from '../housinglocation';
+import { HousingService } from '../housing.service';
+
+@Component({
+ selector: 'app-home',
+ standalone: true,
+ imports: [
+ CommonModule,
+ HousingLocationComponent
+ ],
+ template: `
+
+
+ `,
+ styleUrls: ['./home.component.css'],
+})
+
+export class HomeComponent {
+ housingLocationList: HousingLocation[] = [];
+ housingService: HousingService = inject(HousingService);
+
+ constructor() {
+ this.housingLocationList = this.housingService.getAllHousingLocations();
+ }
+
+}
diff --git a/aio/content/examples/first-app-lesson-09/src/app/housing-location/housing-location.component.css b/aio/content/examples/first-app-lesson-09/src/app/housing-location/housing-location.component.css
new file mode 100644
index 00000000000..2a970e0a6b8
--- /dev/null
+++ b/aio/content/examples/first-app-lesson-09/src/app/housing-location/housing-location.component.css
@@ -0,0 +1,31 @@
+.listing {
+ background: var(--accent-color);
+ border-radius: 30px;
+ padding-bottom: 30px;
+}
+.listing-heading {
+ color: var(--primary-color);
+ padding: 10px 10px 0 10px;
+}
+.listing-photo {
+ height: 250px;
+ width: 100%;
+ object-fit: cover;
+ border-radius: 30px 30px 0 0;
+}
+.listing-location {
+ padding: 10px 10px 20px 10px;
+}
+.listing-location::before {
+ content: url('/assets/location-pin.svg') / '';
+}
+
+section.listing a {
+ padding-left: 10px;
+ text-decoration: none;
+ color: var(--primary-color);
+}
+section.listing a::after {
+ content: '\203A';
+ margin-left: 5px;
+}
diff --git a/aio/content/examples/first-app-lesson-09/src/app/housing-location/housing-location.component.ts b/aio/content/examples/first-app-lesson-09/src/app/housing-location/housing-location.component.ts
new file mode 100644
index 00000000000..24e930ce9f2
--- /dev/null
+++ b/aio/content/examples/first-app-lesson-09/src/app/housing-location/housing-location.component.ts
@@ -0,0 +1,23 @@
+import { Component, Input } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { HousingLocation } from '../housinglocation';
+
+@Component({
+ selector: 'app-housing-location',
+ standalone: true,
+ imports: [CommonModule],
+ template: `
+
+
+ {{ housingLocation.name }}
+ {{ housingLocation.city}}, {{housingLocation.state }}
+
+ `,
+ styleUrls: ['./housing-location.component.css'],
+})
+
+export class HousingLocationComponent {
+
+ @Input() housingLocation!: HousingLocation;
+
+}
diff --git a/aio/content/examples/first-app-lesson-09/src/app/housing.service.ts b/aio/content/examples/first-app-lesson-09/src/app/housing.service.ts
new file mode 100644
index 00000000000..b2bb86ea1e7
--- /dev/null
+++ b/aio/content/examples/first-app-lesson-09/src/app/housing.service.ts
@@ -0,0 +1,119 @@
+import { Injectable } from '@angular/core';
+import { HousingLocation } from './housinglocation';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class HousingService {
+ private img_server = "https://storage.googleapis.com/angular-tutorial-assets/first-app/";
+ protected housingLocationList: HousingLocation[] = [
+ {
+ id: 0,
+ name: 'Acme Fresh Start Housing',
+ city: 'Chicago',
+ state: 'IL',
+ photo: this.img_server + 'house_0.png',
+ availableUnits: 4,
+ wifi: true,
+ laundry: true
+ },
+ {
+ id: 1,
+ name: 'A113 Transitional Housing',
+ city: 'Santa Monica',
+ state: 'CA',
+ photo: this.img_server + 'house_1.png',
+ availableUnits: 0,
+ wifi: false,
+ laundry: true
+ },
+ {
+ id: 2,
+ name: 'Warm Beds Support',
+ city: 'Juneau',
+ state: 'AK',
+ photo: this.img_server + 'house_2.png',
+ availableUnits: 1,
+ wifi: false,
+ laundry: false
+ },
+ {
+ id: 3,
+ name: 'Homesteady Housing',
+ city: 'Chicago',
+ state: 'IL',
+ photo: this.img_server + 'house_3.png',
+ availableUnits: 1,
+ wifi: true,
+ laundry: false
+ },
+ {
+ id: 4,
+ name: 'Happy Homes Group',
+ city: 'Gary',
+ state: 'IN',
+ photo: this.img_server + 'house_4.png',
+ availableUnits: 1,
+ wifi: true,
+ laundry: false
+ },
+ {
+ id: 5,
+ name: 'Hopeful Apartment Group',
+ city: 'Oakland',
+ state: 'CA',
+ photo: this.img_server + 'house_5.png',
+ availableUnits: 2,
+ wifi: true,
+ laundry: true
+ },
+ {
+ id: 6,
+ name: 'Seriously Safe Towns',
+ city: 'Oakland',
+ state: 'CA',
+ photo: this.img_server + 'house_6.png',
+ availableUnits: 5,
+ wifi: true,
+ laundry: true
+ },
+ {
+ id: 7,
+ name: 'Hopeful Housing Solutions',
+ city: 'Oakland',
+ state: 'CA',
+ photo: this.img_server + 'house_7.png',
+ availableUnits: 2,
+ wifi: true,
+ laundry: true
+ },
+ {
+ id: 8,
+ name: 'Seriously Safe Towns',
+ city: 'Oakland',
+ state: 'CA',
+ photo: this.img_server + 'house_8.png',
+ availableUnits: 10,
+ wifi: false,
+ laundry: false
+ },
+ {
+ id: 9,
+ name: 'Capital Safe Towns',
+ city: 'Portland',
+ state: 'OR',
+ photo: this.img_server + 'house_9.png',
+ availableUnits: 6,
+ wifi: true,
+ laundry: true
+ }
+ ];
+
+ getAllHousingLocations(): HousingLocation[] {
+ return this.housingLocationList;
+ }
+
+ getHousingLocationById(id: number): HousingLocation | undefined {
+ return this.housingLocationList.find(housingLocation => housingLocation.id === id);
+ }
+}
diff --git a/aio/content/examples/first-app-lesson-09/src/app/housinglocation.ts b/aio/content/examples/first-app-lesson-09/src/app/housinglocation.ts
new file mode 100644
index 00000000000..8303b6754eb
--- /dev/null
+++ b/aio/content/examples/first-app-lesson-09/src/app/housinglocation.ts
@@ -0,0 +1,10 @@
+export interface HousingLocation {
+ id: number;
+ name: string;
+ city: string;
+ state: string;
+ photo: string;
+ availableUnits: number;
+ wifi: boolean;
+ laundry: boolean;
+}
diff --git a/aio/content/examples/first-app-lesson-09/src/assets/location-pin.svg b/aio/content/examples/first-app-lesson-09/src/assets/location-pin.svg
new file mode 100644
index 00000000000..8a709d4b39c
--- /dev/null
+++ b/aio/content/examples/first-app-lesson-09/src/assets/location-pin.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/aio/content/examples/first-app-lesson-09/src/assets/logo.svg b/aio/content/examples/first-app-lesson-09/src/assets/logo.svg
new file mode 100644
index 00000000000..6a4c13d5bac
--- /dev/null
+++ b/aio/content/examples/first-app-lesson-09/src/assets/logo.svg
@@ -0,0 +1,7 @@
+
diff --git a/aio/content/examples/first-app-lesson-09/src/favicon.ico b/aio/content/examples/first-app-lesson-09/src/favicon.ico
new file mode 100644
index 00000000000..997406ad22c
Binary files /dev/null and b/aio/content/examples/first-app-lesson-09/src/favicon.ico differ
diff --git a/aio/content/examples/first-app-lesson-09/src/index.html b/aio/content/examples/first-app-lesson-09/src/index.html
new file mode 100644
index 00000000000..a3d07306714
--- /dev/null
+++ b/aio/content/examples/first-app-lesson-09/src/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+ Homes
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aio/content/examples/first-app-lesson-09/src/main.ts b/aio/content/examples/first-app-lesson-09/src/main.ts
new file mode 100644
index 00000000000..6aa14fb91c4
--- /dev/null
+++ b/aio/content/examples/first-app-lesson-09/src/main.ts
@@ -0,0 +1,10 @@
+/*
+* Protractor support is deprecated in Angular.
+* Protractor is used in this example for compatibility with Angular documentation tools.
+*/
+import { bootstrapApplication,provideProtractorTestingSupport } from '@angular/platform-browser';
+import { AppComponent } from './app/app.component';
+
+bootstrapApplication(AppComponent,
+ {providers: [provideProtractorTestingSupport()]})
+ .catch(err => console.error(err));
diff --git a/aio/content/examples/first-app-lesson-09/src/styles.css b/aio/content/examples/first-app-lesson-09/src/styles.css
new file mode 100644
index 00000000000..8ff9d490e35
--- /dev/null
+++ b/aio/content/examples/first-app-lesson-09/src/styles.css
@@ -0,0 +1,23 @@
+/* You can add global styles to this file, and also import other style files */
+* {
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ font-family: 'Be Vietnam Pro', sans-serif;
+}
+:root {
+ --primary-color: #605DC8;
+ --secondary-color: #8B89E6;
+ --accent-color: #e8e7fa;
+ --shadow-color: #E8E8E8;
+}
+
+button.primary {
+ padding: 10px;
+ border: solid 1px var(--primary-color);
+ background: var(--primary-color);
+ color: white;
+ border-radius: 8px;
+}
diff --git a/aio/content/examples/first-app-lesson-09/stackblitz.json b/aio/content/examples/first-app-lesson-09/stackblitz.json
new file mode 100644
index 00000000000..bf7fae0feb5
--- /dev/null
+++ b/aio/content/examples/first-app-lesson-09/stackblitz.json
@@ -0,0 +1,17 @@
+{
+ "description": "Angular - First App - Lesson 09",
+ "files": [
+ "!**/*.d.ts",
+ "!**/*.js",
+ "!**/*.[1,2].*"
+ ],
+ "tags": [
+ [
+ "first",
+ "angular",
+ "app",
+ "lesson",
+ "09"
+ ]
+ ]
+}