(Chromium) JavaScript engine. While mostly used for web servers, it is also used for JavaScript development tools, including the Angular CLI as well as the TypeScript and Angular compilers. Download Node.js from https://nodejs.org. Make sure to download and install a major version supported by the major version of Angular you are using. Refer to https://angular.dev/reference/versions. Use the node -v command to verify the version installed.
to install versioned JavaScript packages from a private or public JavaScript package registry • The JavaScript package registry at https://registry.npmjs.com • The GitHub-owned npm, Inc. company that maintains the npm products • The JavaScript package explorer at https://npmjs.com The npm CLI is distributed with Node.js. NPM was originally an abbreviation of Node Package Manager. Use the npm -v command to verify the version installed.
package registries. Yarn Classic refers to versions 1.x. It has a lax module resolution algorithm but is significantly slower than modern alternatives. Install the Yarn CLI using the npm CLI: npm install -g yarn or for Yarn Classic: npm install -g yarn@1 Use the yarn -v command to verify the version installed.
for Angular projects. Its main features include: • Code generation • Codemods and code migrations • Development web server • Task runner • Angular compilation Install the Angular CLI using the npm CLI: npm install -g @angular/cli or using the Yarn Classic CLI: yarn global add @angular/cli Use the ng version command to verify the version installed.
free cross-platform code editor by Microsoft. Angular CLI generates a workspace with recommended VS Code extensions. Download VS Code from https://code.visualstudio.com. Install the following VS Code extensions: • Angular Language Service • EditorConfig for VS Code • Error Lens • ESLint • GitLens (optional) • Prettier (optional)
F12) will do. • Arc • Brave • Google Chrome • Microsoft Edge • Mozilla Firefox • Opera • Safari* • Vivaldi* *No Angular DevTools[1][2] web extension support.
new introduction-to-angular 2. Run the development server ng serve Or npm start Or yarn start 3. Open the Angular application http://localhost:4200 Or press O then Enter in the terminal
the following setting to display generated components as block HTML elements { "schematics": { "@schematics/angular:component": { "displayBlock": true } } }
"*.html", "options": { "parser": "html" } }, { "files": "*.component.html", "options": { "parser": "angular" } } ] } 1. Create a .prettierrc JSON file in the root of the workspace 2. Add this configuration to support formatting Angular component templates with Prettier 3. Enable the Editor: Format On Save setting in VS Code Or use the Format Document command in VS Code
'@angular/core'; @Component({ selector: 'app-hello-world', imports: [], templateUrl: './hello-world.component.html', styleUrl: './hello-world.component.css' }) export class HelloWorldComponent {} <- hello-world.component.html --> <p>hello-world works!<-p> /- hello-world.component.css *- :host { display: block; } An Angular component consist of: • A TypeScript class that represents the code-behind component model connecting user input to the application and application state to the user • A component template which represents the graphical user interface as HTML-/JavaScript-inspired Angular template syntax and is bound to the component model • A component stylesheet containing component-specific styles using CSS These parts can be inline in a single file or spread between two or more separate files.
'@angular/core'; import { HelloWorldComponent } from './hello-world/hello-world.component'; @Component({ selector: 'app-root', imports: [HelloWorldComponent], templateUrl: './app.component.html', styleUrl: './app.component.css', }) export class AppComponent {} <- app.component.html --> <h1>Welcome<-h1> <app-hello-world /- To use a component as a child component: • Add the child component class to the Component.imports array of the parent component • Add an HTML tag to the parent component template that uses the element name specified by the child component’s selector
'@angular/core'; @Component({ selector: 'app-hello-world', imports: [], templateUrl: './hello-world.component.html', styleUrl: './hello-world.component.css', }) export class HelloWorldComponent { name = 'World'; } <- hello-world.component.html --> <p>Hello, {{ name }}!<-p> Component state is represented by properties on the component model. Component state is displayed to the user using template expressions, for example {{ name }}
'@angular/core'; @Component({ selector: 'app-hello-world', imports: [], templateUrl: './hello-world.component.html', styleUrl: './hello-world.component.css', }) export class HelloWorldComponent { @Input() name = 'World'; } A component can accept data from other components via properties that are marked with an @Input() decorator.
} from '@angular/core'; @Component({ selector: 'app-hello-world', imports: [], templateUrl: './hello-world.component.html', styleUrl: './hello-world.component.css', }) export class HelloWorldComponent { @Input() name = 'World'; @Output() messageSent = new EventEmitter<string>(); } <- hello-world.component.html --> <p>Hello, {{ name }}!<-p> <button (click)="messageSent.emit('Hi there')">Send message<-button> A component can output data to other components via properties that are marked with an @Output() decorator.
$event" /- @if (message) { <p>Received: {{ message }}<-p> } Angular has built-in template blocks for control flow. Conditionals use @if, @else-if, and @else blocks.
import { Component } from '@angular/core'; @Component({ selector: 'app-today', imports: [DatePipe], templateUrl: './today.component.html', styleUrl: './today.component.css', }) export class TodayComponent { now = new Date(); refreshTime() { this.now = new Date(); } } Component templates can use Angular pipes to process values, for example to format a date-time. 1. In the component model, add the pipe class to Component.imports
{ @case (0) { Sunday } @case (1) { Monday } @case (2) { Tuesday } @case (3) { Wednesday } @case (4) { Thursday } @case (5) { Friday } @case (6) { Saturday } @default { a new day } } and the time is {{ now | date }} <button (click)="refreshTime()">Refresh time<-button> <-p> Component templates can use Angular pipes to process values, for example to format a date-time. 1. In the component model, add the pipe class to Component.imports 2. In the template, add a pipe (|) operator followed by the pipe’s name
{ @case (0) { Sunday } @case (1) { Monday } @case (2) { Tuesday } @case (3) { Wednesday } @case (4) { Thursday } @case (5) { Friday } @case (6) { Saturday } @default { a new day } } and the time is {{ now | date: "mediumTime" }} <button (click)="refreshTime()">Refresh time<-button> <-p> Component templates can use Angular pipes to process values, for example to format a date-time. 1. In the component model, add the pipe class to Component.imports 2. In the template, add a pipe (|) operator followed by the pipe’s name 3. Optionally, add pipe parameters separated by colon (:)
from '@angular/core'; @Pipe({ name: 'weekday', }) export class WeekdayPipe implements PipeTransform { transform(value: unknown, .--args: unknown[]): unknown { return null; } } Generate your first Angular pipe using the command ng generate pipe weekday
from '@angular/core'; @Pipe({ name: 'weekday', }) export class WeekdayPipe implements PipeTransform { transform(value: Date) { let weekday = ''; switch (value.getDay()) { case 0: weekday = 'Sunday'; break; case 1: weekday = 'Monday'; break; case 2: weekday = 'Tuesday'; break; case 3: weekday = 'Wednesday'; break; case 4: weekday = 'Thursday'; break; case 5: weekday = 'Friday'; break; case 6: weekday = 'Saturday'; break; default: weekday = 'a new day'; break; } return weekday; } } Generate your first Angular pipe using the command ng generate pipe weekday For this example, we move the weekday switch statement from today.component.html to WeekdayPipe.transform and convert it to JavaScript.
'@angular/common'; import { Component } from '@angular/core'; import { WeekdayPipe } from '.-/weekday.pipe'; @Component({ selector: 'app-today', imports: [DatePipe, WeekdayPipe], templateUrl: './today.component.html', styleUrl: './today.component.css', }) export class TodayComponent { now = new Date(); refreshTime() { this.now = new Date(); } } To use the custom pipe: 1. In today.component.ts, add WeekdayPipe to Component.imports
'@angular/common'; import { Component } from '@angular/core'; import { WeekdayPipe } from '.-/weekday.pipe'; @Component({ selector: 'app-today', imports: [DatePipe, WeekdayPipe], templateUrl: './today.component.html', styleUrl: './today.component.css', }) export class TodayComponent { now = new Date(); refreshTime() { this.now = new Date(); } } <- today.component.html --> <p> Today is {{ now | weekday }} and the time is {{ now | date: "mediumTime" }} <button (click)="refreshTime()">Refresh time<-button> <-p> To use the custom pipe: 1. In today.component.ts, add WeekdayPipe to Component.imports 2. In today.component.html, add {{ now | weekday }}
and templates. • They have input properties • They have output properties • They can access their host DOM element • They have host listeners and host bindings • They have lifecycle hooks • They usually have HTML attribute selectors instead of HTML element selectors An Angular directive is commonly used to manipulate the Document Object Model (DOM), that is HTML elements in the browser.
'@angular/core'; @Directive({ selector: '[appFontSize]', }) export class FontSizeDirective { constructor() {} } Generate your first Angular directive using the command ng generate directive font- size
} from '@angular/core'; @Directive({ selector: '[appFontSize]', }) export class FontSizeDirective { @Input() appFontSize; } Add an input property named the same as the selector, in this case appFontSize, to accept an input value.
} from '@angular/core'; @Directive({ selector: '[appFontSize]', }) export class FontSizeDirective { @Input() appFontSize: number; } Annotate the expected type, in this case number.
} from '@angular/core'; @Directive({ selector: '[appFontSize]', }) export class FontSizeDirective { @Input({ required: true }) appFontSize! number; } Add the required: true setting to the @Input decorator and use the non-null assertion operator (!) to prevent errors.
} from '@angular/core'; @Directive({ selector: '[appFontSize]', }) export class FontSizeDirective { @Input({ required: true }) @HostBinding('style.fontSize.px') appFontSize!- number; } Use a @HostBinding() decorator to change properties of the host DOM element, for example style.fontSize.
} from '@angular/core'; @Directive({ selector: '[appFontSize]', }) export class FontSizeDirective { @Input({ required: true }) @HostBinding('style.fontSize.px') appFontSize!- number; } .px is a special unit-specifying suffix for style property bindings.
Output } from '@angular/core'; import { FontSizeDirective } from '.-/font-size.directive'; @Component({ selector: 'app-hello-world', imports: [FontSizeDirective], templateUrl: './hello-world.component.html', styleUrl: './hello-world.component.css', }) export class HelloWorldComponent { @Input() name = 'World'; @Output() messageSent = new EventEmitter<string>(); } To use a directive: 1. Add the directive class to Component.imports.
Output } from '@angular/core'; import { FontSizeDirective } from '.-/font-size.directive'; @Component({ selector: 'app-hello-world', imports: [FontSizeDirective], templateUrl: './hello-world.component.html', styleUrl: './hello-world.component.css', }) export class HelloWorldComponent { @Input() name = 'World'; @Output() messageSent = new EventEmitter<string>(); } <- hello-world.component.html --> <p appFontSize>Hello, {{ name }}!<-p> <button (click)="messageSent.emit('Hi there')">Send message<-button> To use a directive: 1. Add the directive class to Component.imports. 2. Use the attribute selector on a DOM element in the component template.
Output } from '@angular/core'; import { FontSizeDirective } from '.-/font-size.directive'; @Component({ selector: 'app-hello-world', imports: [FontSizeDirective], templateUrl: './hello-world.component.html', styleUrl: './hello-world.component.css', }) export class HelloWorldComponent { @Input() name = 'World'; @Output() messageSent = new EventEmitter<string>(); } <- hello-world.component.html --> <p [appFontSize]="24">Hello, {{ name }}!<-p> <button (click)="messageSent.emit('Hi there')">Send message<-button> To use a directive: 1. Add the directive class to Component.imports. 2. Use the attribute selector on a DOM element in the component template. 3. Optionally bind input properties and/or output properties.
} button { background-color: purple; color: white; border: none; &:hover { background-color: rebeccapurple; } &:active { background-color: blueviolet; } } <- hello-world.component.html --> <p [appFontSize]="24">Hello, {{ name }}!<-p> <button (click)="messageSent.emit('Hi there')">Send message<-button> Component styles only apply to DOM elements mentioned in the component template. A simple selector like button is often enough to target a DOM element. In this case, there is no need to add HTML/CSS classes.
@Injectable({ providedIn: 'root', }) export class MessageService { constructor() {} } Angular services are usually class-based. Generate your first Angular service using the command ng generate service message
@Injectable({ providedIn: 'root', }) export class MessageService { constructor() {} } Singleton Angular services are provided by passing the providedIn: 'root' option to the @Injectable decorator.
'@angular/core'; import { HelloWorldComponent } from './hello-world/hello-world.component'; import { MessageListComponent } from './message-list/message-list.component'; import { MessageService } from './message.service'; import { TodayComponent } from './today/today.component'; @Component({ selector: 'app-root', imports: [HelloWorldComponent, TodayComponent, MessageListComponent], templateUrl: './app.component.html', styleUrl: './app.component.css', }) export class AppComponent { messageService = inject(MessageService); messages: readonly string[] = []; receiveMessage(message: string) { this.messages = [.--this.messages, message]; } } A class-based Angular service is injected into a component, directive, pipe, or service by passing the service class to the inject function.
<app-today /- <h2>Received messages<-h2> <app-message-list [messages]="messageService.messages" /- Use the injected MessageService instance in app.component.html.
'@angular/common/http'; import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; export const appConfig: ApplicationConfig = { providers: [ provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideHttpClient(), ], }; To use the HttpClient service, first provide it in your AppConfig by using the provideHttpClient provider function.
from '@angular/common/http'; import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; export const appConfig: ApplicationConfig = { providers: [ provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideHttpClient(withFetch()), ], }; It’s recommended to use the modern fetch API instead of the classic XMLHttpRequest API as the HTTP engine for the HttpClient service. To do this, pass the withFetch function to provideHttpClient.
'@angular/common/http'; import { Component, inject } from '@angular/core'; import { JokeResponse } from '.-/joke-response'; @Component({ selector: 'app-joke', imports: [], templateUrl: './joke.component.html', styleUrl: './joke.component.css', }) export class JokeComponent { http = inject(HttpClient); jokeRequest$ = this.http.get<JokeResponse>( 'https:--v2.jokeapi.dev/joke/Programming?safe-mode', ); } Add JokeResponse as the type parameter for the HttpClient.get method. This specifies the type of the HTTP response body when parsed as JSON.
different URL. Generate page components using the following commands ng generate component hello- page ng generate component today- page ng generate component joke- page ng generate component message- page
@Component({ selector: 'app-navigation', imports: [], templateUrl: './navigation.component.html', styleUrl: './navigation.component.css', }) export class NavigationComponent {} Generate a navigation component using the command ng generate component navigation
import { routes } from '.-/app.routes'; @Component({ selector: 'app-navigation', imports: [], templateUrl: './navigation.component.html', styleUrl: './navigation.component.css', }) export class NavigationComponent { routes = routes.filter((route) => route.path !-- ''); } Filter out the default route from the routes constant and assign the rest to a NavigationComponent.routes property.
data from a web API, for example • JokeAPI (https://sv443.net/jokeapi/v2/) • PokeAPI (https://pokeapi.co/) • TODO API (https://api.nstack.in/) • JSONPlaceholder (https://jsonplaceholder.typicode.com/) Or follow these slides to continue the sample application. Available at https://speakerdeck.com/layzee.
asynchronous programming. RxJS extends The Observer Pattern and includes Observable, operators, schedulers, as well as Subject and friends. Similar libraries are available for other programming languages, platforms, and frameworks, for example: • RxJava • Rx.NET • RxPHP
Most observables are lazy. They do nothing until they are observed. import { from, Observable } from 'rxjs'; const name$: Observable<string> = from(['Ella', 'Noah', 'Lily']);
notifications. • Zero to many next notifications with a data value • Zero to one error notification with an error value • Zero to one complete notification with no value
is passed the value of every next notification. Its return value is the replacement value for this next notification as observed by subscribers. import { from, map, Observable } from 'rxjs'; import { Name } from './name'; const name$: Observable<Name> = from(['Ella', 'Noah', 'Lily']).pipe( map((name) => ({ name, firstLetter: name[0], })), ); name$.subscribe({ next: (name) => console.log(name), error: (error) => console.error(error), complete: () => console.log('End of transmission'), }); /- -> { name: "Ella", firstLetter: "E" } /- -> { name: "Noah", firstLetter: "N" } /- -> { name: "Lily", firstLetter: "L" } /- -> "End of transmission” /- name.ts export interface Name { readonly name: string; readonly firstLetter: string; }
is passed the value of every next notification. When it returns true, the next notification is forwarded to subscribers. When it returns false, the next notification is ignored and never observed by subscribers. import { filter, from, map, Observable } from 'rxjs’; import { Name } from './name'; const name$: Observable<Name> = from(['Ella', 'Noah', 'Lily']).pipe( map((name) => ({ name, firstLetter: name[0], })), filter((name) => name.firstLetter !-- 'L'), ); name$.subscribe({ next: (name) => console.log(name), error: (error) => console.error(error), complete: () => console.log('End of transmission'), }); /- -> { name: "Ella", firstLetter: "E" } /- -> { name: "Noah", firstLetter: "N" } /- -> "End of transmission"
by observable notifications. We could and should place our browser console side effects in a tap operation. Similar to the Observable.subscribe method, the tap operator accepts either: • A single callback for next notifications • An observer object with one or more notification handlers import { filter, from, map, Observable, tap } from 'rxjs'; import { Name } from './name'; const name$: Observable<Name> = from(['Ella', 'Noah', 'Lily']).pipe( map((name) => ({ name, firstLetter: name[0], })), filter((name) => name.firstLetter !-- 'L'), tap({ next: (name) => console.log(name), error: (error) => console.error(error), complete: () => console.log('End of transmission'), }) ); name$.subscribe(); /- -> { name: "Ella", firstLetter: "E" } /- -> { name: "Noah", firstLetter: "N" } /- -> "End of transmission"
calling its Subscription.unsubscribe method. In this example, it has no effect since the observable emits its next notifications synchronously and we observe their values synchronously. import { filter, from, map, Observable, Subscription, tap } from 'rxjs'; import { Name } from './name'; const name$: Observable<Name> = from(['Ella', 'Noah', 'Lily']).pipe( map((name) => ({ name, firstLetter: name[0], })), filter((name) => name.firstLetter !-- 'L'), tap((value) => {}) ); const subscription: Subscription = name$.subscribe(); subscription.unsubscribe(); /- -> { name: "Ella", firstLetter: "E" } /- -> { name: "Noah", firstLetter: "N" } /- -> "End of transmission"
we can use for the distinctUntilChanged operator. In this case, we still receive the duplicate value because the operator compares by reference by default. The two 'Ella' strings are mapped to Name data structures by the map operator, each a new object reference so they both pass the distinctUntilChanged operator. import { delay, distinctUntilChanged, filter, from, map, Observable, Subscription, tap } from 'rxjs'; import { Name } from './name'; const name$: Observable<Name> = from(['Ella', 'Ella', 'Noah', 'Lily']).pipe( delay(100), map((name) => ({ name, firstLetter: name[0], })), filter((name) => name.firstLetter !-- 'L'), distinctUntilChanged(), tap({ next: (name) => console.log(name), error: (error) => console.error(error), complete: () => console.log('End of transmission'), }), ); const subscription: Subscription = name$.subscribe(); setTimeout(() => { subscription.unsubscribe(); }, 200); /- -> { name: "Ella", firstLetter: "E" } /- -> { name: "Ella", firstLetter: "E" } /- -> { name: "Noah", firstLetter: "N" } /- -> "End of transmission"
service causes the HTTP request to be aborted. In our example it happens when a user navigates away from the component before an HTTP response is received.
hook is a common pattern in Angular applications, we can use the takeUntilDestroyed operator from the @angular/core/rxjs-interop package. Now we do not need to manage the subscription manually. /- joke.component.ts import { HttpClient } from '@angular/common/http'; import { Component, inject } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { tap } from 'rxjs'; import { JokeResponse } from '.-/joke-response'; @Component({ /- (.--) }) export class JokeComponent { http = inject(HttpClient); jokeRequest$ = this.http.get<JokeResponse>( 'https:--v2.jokeapi.dev/joke/Programming?safe-mode&type=single', ); joke?- string; constructor() { this.jokeRequest$ .pipe( tap((response) => { this.joke = response.joke; }), takeUntilDestroyed(), ) .subscribe(); } }
from a Subject instance, we miss that notification. import { map, Subject, Subscription } from 'rxjs'; const name = new Subject<string>(); name.next('Ella'); const subscription: Subscription = name .pipe(map((n) => n.toUpperCase())) .subscribe((n) => console.log(n)); name.next('Noah'); name.next('Lily'); name.complete(); /- -> "NOAH" /- -> "LILY"
receive a value on subscription. Either the initial value or the value of the latest next notification. import { BehaviorSubject } from 'rxjs'; const isAuthenticated = new BehaviorSubject<boolean>(false); isAuthenticated.subscribe((b) => console.log(b)); /- -> false
be accesed using its value property. This requires no subscription. import { BehaviorSubject } from 'rxjs'; const isAuthenticated = new BehaviorSubject<boolean>(false); console.log(isAuthenticated.value); /- -> false
is passed through the AsyncPipe and assigned to a local messages template variable in message-page.component.html. <- message-page.component.html --> <h2>Received messages<-h2> @let messages = messageService.messages$ | async; <app-message-list [messages]="messageService.messages" /-
your previous code lab applications. Or make the time in the Today page update every second automatically using RxJS state management. Or follow these slides to continue the sample application. Available at https://speakerdeck.com/layzee.
Update the test description 2. Use auto change detection in the component test by calling the ComponentFixture.autoDetectCh anges method 3. Click the first link (<a> HTML element) 4. Wait for the Router and Angular’s change detection to stabilize in the component test by calling the ComponentFixture.whenStable method and awaiting its returned Promise 5. Assert that the first paragraph (<p> HTML element) contains the text Hello, TimePlan! /- app.component.spec.ts import { provideLocationMocks } from '@angular/common/testing'; import { TestBed } from '@angular/core/testing'; import { provideRouter } from '@angular/router'; import { AppComponent } from './app.component'; import { routes } from './app.routes'; describe('AppComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [AppComponent], providers: [provideRouter(routes), provideLocationMocks()], }).compileComponents(); }); it('should create the app', () => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.componentInstance; expect(app).toBeTruthy(); }); it('should render a greeting', async () => { /- [1] const fixture = TestBed.createComponent(AppComponent); fixture.autoDetectChanges(); /- [2] const compiled = fixture.nativeElement as HTMLElement; compiled.querySelector('a')?-click(); /- [3] await fixture.whenStable(); /- [4] expect(compiled.querySelector('p')?-textContent).toContain( /- [5] 'Hello, TimePlan!', /- [5] ); }); });
example.spec.ts in headless mode in your terminal using the command ng e2e Running 3 tests using 3 workers 1) [webkit] › example.spec.ts:3:5 › has title ─────────────────────────────────────────────────── Error: Timed out 5000ms waiting for expect(locator).toHaveTitle(expected) Locator: locator(':root') Expected pattern: /MyApp/ Received string: "Hello" Call log: - expect.toHaveTitle with timeout 5000ms - waiting for locator(':root') 8 × locator resolved to <html lang="en">…</html> - unexpected value "Hello" 5 | 6 | // Expect a title "to contain" a substring. > 7 | await expect(page).toHaveTitle(/MyApp/); | ^ 8 | }); 9 | at D:\projects\sandbox\introduction-to-angular\e2e\example.spec.ts:7:22
pass, open example.spec.ts and make the following changes. 1. Change the test description 2. Change the comment 3. Add a Playwright Locator for the greeting 4. Assert that the greeting contains the text Hello, TimePlan! /- example.spec.ts import { expect, test } from '@playwright/test'; test('has greeting', async ({ page }) => { /- [1] await page.goto('/'); /- Expect a greeting "to contain" a substring. /- [2] const greeting = page.getByText('hello,'); /- [3] await expect(greeting).toContainText('Hello, TimePlan!'); /- [3][4] });
browsers in Playwright UI mode, follow these steps: 1. Expand the Filter accordion below the Playwright logo 2. Check the box next to each browser you want to test in 3. Double click a test or test file to run it in the specified browsers Or expand a test accordian to list each browser individually then double click a browser name