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: ` +
+ Exterior photo of {{housingLocation.name}} +

{{ 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" + ] + ] +}