docs(docs-infra): remove unused examples

(cherry picked from commit 899df39c7c)
This commit is contained in:
Matthieu Riegler
2026-01-26 12:13:26 +01:00
committed by Andrew Scott
parent c133a2c1eb
commit 06952b4f0f
214 changed files with 3 additions and 7694 deletions
@@ -1,185 +0,0 @@
import {browser, element, by, ElementFinder} from 'protractor';
describe('Dependency Injection Tests', () => {
let expectedMsg: string;
let expectedMsgRx: RegExp;
beforeAll(() => browser.get(''));
describe('Cars:', () => {
it('DI car displays as expected', async () => {
expectedMsg = 'DI car with 4 cylinders and Flintstone tires.';
expect(await element(by.css('#di')).getText()).toEqual(expectedMsg);
});
it('No DI car displays as expected', async () => {
expectedMsg = 'No DI car with 4 cylinders and Flintstone tires.';
expect(await element(by.css('#nodi')).getText()).toEqual(expectedMsg);
});
it('Injector car displays as expected', async () => {
expectedMsg = 'Injector car with 4 cylinders and Flintstone tires.';
expect(await element(by.css('#injector')).getText()).toEqual(expectedMsg);
});
it('Factory car displays as expected', async () => {
expectedMsg = 'Factory car with 4 cylinders and Flintstone tires.';
expect(await element(by.css('#factory')).getText()).toEqual(expectedMsg);
});
it('Simple car displays as expected', async () => {
expectedMsg = 'Simple car with 4 cylinders and Flintstone tires.';
expect(await element(by.css('#simple')).getText()).toEqual(expectedMsg);
});
it('Super car displays as expected', async () => {
expectedMsg = 'Super car with 12 cylinders and Flintstone tires.';
expect(await element(by.css('#super')).getText()).toEqual(expectedMsg);
});
it('Test car displays as expected', async () => {
expectedMsg = 'Test car with 8 cylinders and YokoGoodStone tires.';
expect(await element(by.css('#test')).getText()).toEqual(expectedMsg);
});
});
describe('Other Injections:', () => {
it('DI car displays as expected', async () => {
expectedMsg = 'DI car with 4 cylinders and Flintstone tires.';
expect(await element(by.css('#car')).getText()).toEqual(expectedMsg);
});
it('Hero displays as expected', async () => {
expectedMsg = 'Dr. Nice';
expect(await element(by.css('#hero')).getText()).toEqual(expectedMsg);
});
it('Optional injection displays as expected', async () => {
expectedMsg = "R.O.U.S.'s? I don't think they exist!";
expect(await element(by.css('#rodent')).getText()).toEqual(expectedMsg);
});
});
describe('Tests:', () => {
it('Tests display as expected', async () => {
expectedMsgRx = /Tests passed/;
expect(await element(by.css('#tests')).getText()).toMatch(expectedMsgRx);
});
});
describe('Provider variations:', () => {
it('P1 (class) displays as expected', async () => {
expectedMsg = 'Hello from logger provided with Logger class';
expect(await element(by.css('#p1')).getText()).toEqual(expectedMsg);
});
it('P3 (provide) displays as expected', async () => {
expectedMsg = 'Hello from logger provided with useClass:Logger';
expect(await element(by.css('#p3')).getText()).toEqual(expectedMsg);
});
it('P4 (useClass:BetterLogger) displays as expected', async () => {
expectedMsg = 'Hello from logger provided with useClass:BetterLogger';
expect(await element(by.css('#p4')).getText()).toEqual(expectedMsg);
});
it('P5 (useClass:EvenBetterLogger - dependency) displays as expected', async () => {
expectedMsg = 'Message to Bob: Hello from EvenBetterlogger';
expect(await element(by.css('#p5')).getText()).toEqual(expectedMsg);
});
it('P6a (no alias) displays as expected', async () => {
expectedMsg = 'Hello OldLogger (but we want NewLogger)';
expect(await element(by.css('#p6a')).getText()).toEqual(expectedMsg);
});
it('P6b (alias) displays as expected', async () => {
expectedMsg = 'Hello from NewLogger (via aliased OldLogger)';
expect(await element(by.css('#p6b')).getText()).toEqual(expectedMsg);
});
it('P7 (useValue) displays as expected', async () => {
expectedMsg = 'Silent logger says "Shhhhh!". Provided via "useValue"';
expect(await element(by.css('#p7')).getText()).toEqual(expectedMsg);
});
it('P8 (useFactory) displays as expected', async () => {
expectedMsg = 'Hero service injected successfully via heroServiceProvider';
expect(await element(by.css('#p8')).getText()).toEqual(expectedMsg);
});
it('P9 (InjectionToken) displays as expected', async () => {
expectedMsg = 'APP_CONFIG Application title is Dependency Injection';
expect(await element(by.css('#p9')).getText()).toEqual(expectedMsg);
});
it('P10 (optional dependency) displays as expected', async () => {
expectedMsg = 'Optional logger was not available';
expect(await element(by.css('#p10')).getText()).toEqual(expectedMsg);
});
});
describe('User/Heroes:', () => {
it('User is Bob - unauthorized', async () => {
expectedMsgRx = /Bob, is not authorized/;
expect(await element(by.css('#user')).getText()).toMatch(expectedMsgRx);
});
it('should have button', async () => {
expect(
await element.all(by.cssContainingText('button', 'Next User')).get(0).isDisplayed(),
).toBe(true, "'Next User' button should be displayed");
});
it('unauthorized user should have multiple unauthorized heroes', async () => {
const heroes = element.all(by.css('#unauthorized app-hero-list div'));
expect(await heroes.count()).toBeGreaterThan(0);
});
it('unauthorized user should have no secret heroes', async () => {
const heroes = element.all(by.css('#unauthorized app-hero-list div'));
expect(await heroes.count()).toBeGreaterThan(0);
const filteredHeroes = heroes.filter(async (elem) => /secret/.test(await elem.getText()));
expect(await filteredHeroes.count()).toEqual(0);
});
it('unauthorized user should have no authorized heroes listed', async () => {
expect(await element.all(by.css('#authorized app-hero-list div')).count()).toEqual(0);
});
describe('after button click', () => {
beforeAll(async () => {
const buttonEle = element.all(by.cssContainingText('button', 'Next User')).get(0);
await buttonEle.click();
});
it('User is Alice - authorized', async () => {
expectedMsgRx = /Alice, is authorized/;
expect(await element(by.css('#user')).getText()).toMatch(expectedMsgRx);
});
it('authorized user should have multiple authorized heroes ', async () => {
const heroes = element.all(by.css('#authorized app-hero-list div'));
expect(await heroes.count()).toBeGreaterThan(0);
});
it('authorized user should have multiple authorized heroes with tree-shakeable HeroesService', async () => {
const heroes = element.all(by.css('#tspAuthorized app-hero-list div'));
expect(await heroes.count()).toBeGreaterThan(0);
});
it('authorized user should have secret heroes', async () => {
const heroes = element.all(by.css('#authorized app-hero-list div'));
expect(await heroes.count()).toBeGreaterThan(0);
const filteredHeroes = heroes.filter(async (elem) => /secret/.test(await elem.getText()));
expect(await filteredHeroes.count()).toBeGreaterThan(0);
});
it('authorized user should have no unauthorized heroes listed', async () => {
expect(await element.all(by.css('#unauthorized app-hero-list div')).count()).toEqual(0);
});
});
});
});
@@ -1,10 +0,0 @@
/*
Must put this interface in its own file instead of app.config.ts
or else TypeScript gives a (bogus) warning:
WARNING in ./src/app/... .ts
"export 'AppConfig' was not found in './app.config'
*/
export interface AppConfig {
apiEndpoint: string;
title: string;
}
@@ -1,17 +0,0 @@
// #docregion
import {Component} from '@angular/core';
import {CarComponent} from './car/car.component';
import {HeroesComponent} from './heroes/heroes.component';
@Component({
selector: 'app-root',
template: `
<h1>{{ title }}</h1>
<app-car />
<app-heroes />
`,
imports: [CarComponent, HeroesComponent],
})
export class AppComponent {
title = 'Dependency Injection';
}
@@ -1,24 +0,0 @@
import {Component, Inject} from '@angular/core';
import {APP_CONFIG, AppConfig} from './injection.config';
import {CarComponent} from './car/car.component';
import {HeroesComponent} from './heroes/heroes.component';
@Component({
selector: 'app-root',
template: `
<h1>{{ title }}</h1>
<app-car />
<app-heroes />
`,
imports: [CarComponent, HeroesComponent],
})
export class AppComponent {
title: string;
// #docregion ctor
constructor(@Inject(APP_CONFIG) config: AppConfig) {
this.title = config.title;
}
// #enddocregion ctor
}
@@ -1,72 +0,0 @@
import {Component, Inject} from '@angular/core';
import {APP_CONFIG, AppConfig} from './injection.config';
import {UserService} from './user.service';
import {HeroesComponent} from './heroes/heroes.component';
import {HeroesTspComponent} from './heroes/heroes-tsp.component';
import {ProvidersComponent} from './providers.component';
import {CarComponent} from './car/car.component';
import {InjectorComponent} from './injector.component';
import {TestComponent} from './test.component';
@Component({
selector: 'app-root',
template: `
<h1>{{ title }}</h1>
<app-car />
<app-injectors />
<app-tests />
<h2>User</h2>
<p id="user">
{{ userInfo }}
<button type="button" (click)="nextUser()">Next User</button>
</p>
<p>
@if (isAuthorized) {
<app-heroes id="authorized" />
}
@if (!isAuthorized) {
<app-heroes id="unauthorized" />
}
@if (isAuthorized) {
<app-heroes-tsp id="tspAuthorized" />
}
<app-providers />
</p>
`,
imports: [
HeroesComponent,
HeroesTspComponent,
ProvidersComponent,
CarComponent,
InjectorComponent,
TestComponent,
],
})
export class AppComponent {
title: string;
constructor(
@Inject(APP_CONFIG) config: AppConfig,
private userService: UserService,
) {
this.title = config.title;
}
get isAuthorized() {
return this.user.isAuthorized;
}
nextUser() {
this.userService.getNewUser();
}
get user() {
return this.userService.user;
}
get userInfo() {
return (
`Current user, ${this.user.name}, is ` + `${this.isAuthorized ? '' : 'not'} authorized. `
);
}
}
@@ -1,16 +0,0 @@
import {ApplicationConfig} from '@angular/core';
import {Logger} from './logger.service';
import {UserService} from './user.service';
import {APP_CONFIG, HERO_DI_CONFIG} from './injection.config';
import {provideProtractorTestingSupport} from '@angular/platform-browser';
const appConfig: ApplicationConfig = {
providers: [
provideProtractorTestingSupport(),
Logger,
UserService,
{provide: APP_CONFIG, useValue: HERO_DI_CONFIG},
],
};
export default appConfig;
@@ -1,39 +0,0 @@
// Examples with car and engine variations
import {Car, Engine, Tires} from './car';
///////// example 1 ////////////
export function simpleCar() {
// Simple car with 4 cylinders and Flintstone tires.
const car = new Car(new Engine(), new Tires());
car.description = 'Simple';
return car;
}
///////// example 2 ////////////
class Engine2 {
constructor(public cylinders: number) {}
}
export function superCar() {
// Super car with 12 cylinders and Flintstone tires.
const bigCylinders = 12;
const car = new Car(new Engine2(bigCylinders), new Tires());
car.description = 'Super';
return car;
}
/////////// example 3 //////////
class MockEngine extends Engine {
override cylinders = 8;
}
class MockTires extends Tires {
override make = 'YokoGoodStone';
}
export function testCar() {
// Test car with 8 cylinders and YokoGoodStone tires.
const car = new Car(new MockEngine(), new MockTires());
car.description = 'Test';
return car;
}
@@ -1,19 +0,0 @@
// #docregion
import {Engine, Tires, Car} from './car';
// BAD pattern!
export class CarFactory {
createCar() {
const car = new Car(this.createEngine(), this.createTires());
car.description = 'Factory';
return car;
}
createEngine() {
return new Engine();
}
createTires() {
return new Tires();
}
}
@@ -1,32 +0,0 @@
import {Injector} from '@angular/core';
import {Car, Engine, Tires} from './car';
import {Logger} from '../logger.service';
export function useInjector() {
let injector: Injector;
/*
// Cannot instantiate an Injector like this!
let injector = new Injector([
{ provide: Car, deps: [Engine, Tires] },
{ provide: Engine, deps: [] },
{ provide: Tires, deps: [] }
]);
*/
injector = Injector.create({
providers: [
{provide: Car, deps: [Engine, Tires]},
{provide: Engine, deps: []},
{provide: Tires, deps: []},
],
});
const car = injector.get(Car);
car.description = 'Injector';
injector = Injector.create({
providers: [{provide: Logger, deps: []}],
});
const logger = injector.get(Logger);
logger.log('Injector car.drive() said: ' + car.drive());
return car;
}
@@ -1,21 +0,0 @@
// Car without DI
import {Engine, Tires} from './car';
export class Car {
public engine: Engine;
public tires: Tires;
public description = 'No DI';
constructor() {
this.engine = new Engine();
this.tires = new Tires();
}
// Method using the engine and tires
drive() {
return (
`${this.description} car with ` +
`${this.engine.cylinders} cylinders and ${this.tires.make} tires.`
);
}
}
@@ -1,35 +0,0 @@
// #docregion
import {Component, inject} from '@angular/core';
import {Car, Engine, Tires} from './car';
import {Car as CarNoDi} from './car-no-di';
import {CarFactory} from './car-factory';
import {testCar, simpleCar, superCar} from './car-creations';
import {useInjector} from './car-injector';
@Component({
selector: 'app-car',
template: `
<h2>Cars</h2>
<div id="di">{{ car.drive() }}</div>
<div id="nodi">{{ noDiCar.drive() }}</div>
<div id="injector">{{ injectorCar.drive() }}</div>
<div id="factory">{{ factoryCar.drive() }}</div>
<div id="simple">{{ simpleCar.drive() }}</div>
<div id="super">{{ superCar.drive() }}</div>
<div id="test">{{ testCar.drive() }}</div>
`,
providers: [Car, Engine, Tires],
})
export class CarComponent {
factoryCar = new CarFactory().createCar();
injectorCar = useInjector();
noDiCar = new CarNoDi();
simpleCar = simpleCar();
superCar = superCar();
testCar = testCar();
public car = inject(Car);
}
@@ -1,28 +0,0 @@
import {Injectable} from '@angular/core';
export class Engine {
public cylinders = 4;
}
export class Tires {
public make = 'Flintstone';
public model = 'Square';
}
@Injectable()
export class Car {
public description = 'DI';
constructor(
public engine: Engine,
public tires: Tires,
) {}
// Method using the engine and tires
drive() {
return (
`${this.description} car with ` +
`${this.engine.cylinders} cylinders and ${this.tires.make} tires.`
);
}
}
@@ -1,34 +0,0 @@
/// Dummy modules to satisfy Angular Language Service
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
////////
import {HeroListComponent as HeroListComponent1} from './hero-list.component.1';
@NgModule({
imports: [CommonModule],
declarations: [HeroListComponent1],
exports: [HeroListComponent1],
})
export class DummyModule1 {}
/////////
import {HeroListComponent as HeroListComponent2} from './hero-list.component.2';
@NgModule({
imports: [CommonModule],
declarations: [HeroListComponent2],
})
export class DummyModule2 {}
/////////
import {HeroesComponent as HeroesComponent1} from './heroes.component.1';
@NgModule({
imports: [CommonModule, DummyModule1],
declarations: [HeroesComponent1],
})
export class DummyModule3 {}
@@ -1,15 +0,0 @@
import {NgFor} from '@angular/common';
import {Component} from '@angular/core';
import {HEROES} from './mock-heroes';
@Component({
selector: 'app-hero-list',
template: `
@for (hero of heroes; track hero) {
<div>{{ hero.id }} - {{ hero.name }}</div>
}
`,
})
export class HeroListComponent {
heroes = HEROES;
}
@@ -1,24 +0,0 @@
// #docplaster
// #docregion
import {Component, inject} from '@angular/core';
import {Hero} from './hero';
// #enddocregion
import {HeroService} from './hero.service.1';
/*
// #docregion
import { HeroService } from './hero.service';
// #enddocregion
*/
// #docregion
@Component({
selector: 'app-hero-list',
template: `
@for (hero of heroes; track hero) {
<div>{{ hero.id }} - {{ hero.name }}</div>
}
`,
})
export class HeroListComponent {
heroes: Hero[] = inject(HeroService).getHeroes();
}
@@ -1,24 +0,0 @@
// #docregion
import {Component} from '@angular/core';
import {Hero} from './hero';
import {HeroService} from './hero.service';
import {NgFor} from '@angular/common';
@Component({
selector: 'app-hero-list',
template: `
@for (hero of heroes; track hero) {
<div>{{ hero.id }} - {{ hero.name }} ({{ hero.isSecret ? 'secret' : 'public' }})</div>
}
`,
})
export class HeroListComponent {
heroes: Hero[];
// #docregion ctor-signature
constructor(
heroService: HeroService, // #enddocregion ctor-signature
) {
this.heroes = heroService.getHeroes();
}
}
@@ -1,6 +0,0 @@
import {Injectable} from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class HeroService {}
@@ -1,12 +0,0 @@
// #docregion
import {Injectable} from '@angular/core';
import {HEROES} from './mock-heroes';
@Injectable({
providedIn: 'root',
})
export class HeroService {
getHeroes() {
return HEROES;
}
}
@@ -1,15 +0,0 @@
import {inject, Injectable} from '@angular/core';
import {HEROES} from './mock-heroes';
import {Logger} from '../logger.service';
@Injectable({
providedIn: 'root',
})
export class HeroService {
private logger = inject(Logger);
getHeroes() {
this.logger.log('Getting heroes ...');
return HEROES;
}
}
@@ -1,14 +0,0 @@
// #docregion
import {Injectable} from '@angular/core';
import {HEROES} from './mock-heroes';
@Injectable({
// declares that this service should be created
// by the root application injector.
providedIn: 'root',
})
export class HeroService {
getHeroes() {
return HEROES;
}
}
@@ -1,20 +0,0 @@
// #docregion
import {EnvironmentInjector, inject, Injectable, runInInjectionContext} from '@angular/core';
@Injectable({providedIn: 'root'})
export class SomeService {}
// #docregion run-in-context
@Injectable({
providedIn: 'root',
})
export class HeroService {
private environmentInjector = inject(EnvironmentInjector);
someMethod() {
runInInjectionContext(this.environmentInjector, () => {
inject(SomeService); // Do what you need with the injected service
});
}
}
// #enddocregion run-in-context
@@ -1,17 +0,0 @@
// #docregion
import {HeroService} from './hero.service';
import {Logger} from '../logger.service';
import {UserService} from '../user.service';
// #docregion factory
const heroServiceFactory = (logger: Logger, userService: UserService) =>
new HeroService(logger, userService.user.isAuthorized);
// #enddocregion factory
// #docregion provider
export const heroServiceProvider = {
provide: HeroService,
useFactory: heroServiceFactory,
deps: [Logger, UserService],
};
// #enddocregion provider
@@ -1,26 +0,0 @@
// #docregion
import {Injectable} from '@angular/core';
import {HEROES} from './mock-heroes';
import {Logger} from '../logger.service';
import {UserService} from '../user.service';
@Injectable({
providedIn: 'root',
useFactory: (logger: Logger, userService: UserService) =>
new HeroService(logger, userService.user.isAuthorized),
deps: [Logger, UserService],
})
export class HeroService {
// #docregion internals
constructor(
private logger: Logger,
private isAuthorized: boolean,
) {}
getHeroes() {
const auth = this.isAuthorized ? 'authorized' : 'unauthorized';
this.logger.log(`Getting heroes for ${auth} user.`);
return HEROES.filter((hero) => this.isAuthorized || !hero.isSecret);
}
// #enddocregion internals
}
@@ -1,6 +0,0 @@
// #docregion
export interface Hero {
id: number;
name: string;
isSecret: boolean;
}
@@ -1,18 +0,0 @@
import {Component} from '@angular/core';
import {HeroListComponent} from './hero-list.component';
/**
* A version of `HeroesComponent` that does not provide the `HeroService` (and thus relies on its
* `Injectable`-declared provider) in order to function.
*
* TSP stands for Tree-Shakeable Provider.
*/
@Component({
selector: 'app-heroes-tsp',
template: `
<h2>Heroes</h2>
<app-hero-list />
`,
imports: [HeroListComponent],
})
export class HeroesTspComponent {}
@@ -1,15 +0,0 @@
import {Component} from '@angular/core';
import {HeroService} from './hero.service';
import {HeroListComponent} from './hero-list.component';
@Component({
selector: 'app-heroes',
providers: [HeroService],
template: `
<h2>Heroes</h2>
<app-hero-list />
`,
imports: [HeroListComponent],
})
export class HeroesComponent {}
@@ -1,15 +0,0 @@
// #docregion
import {Component} from '@angular/core';
import {heroServiceProvider} from './hero.service.provider';
import {HeroListComponent} from './hero-list.component';
@Component({
selector: 'app-heroes',
providers: [heroServiceProvider],
template: `
<h2>Heroes</h2>
<app-hero-list />
`,
imports: [HeroListComponent],
})
export class HeroesComponent {}
@@ -1,14 +0,0 @@
// #docregion
import {Hero} from './hero';
export const HEROES: Hero[] = [
{id: 12, isSecret: false, name: 'Dr. Nice'},
{id: 13, isSecret: false, name: 'Bombasto'},
{id: 14, isSecret: false, name: 'Celeritas'},
{id: 15, isSecret: false, name: 'Magneta'},
{id: 16, isSecret: false, name: 'RubberMan'},
{id: 17, isSecret: false, name: 'Dynama'},
{id: 18, isSecret: true, name: 'Dr. IQ'},
{id: 19, isSecret: true, name: 'Magma'},
{id: 20, isSecret: true, name: 'Tornado'},
];
@@ -1,13 +0,0 @@
import {AppConfig} from './app-config';
export {AppConfig} from './app-config';
// #docregion token
import {InjectionToken} from '@angular/core';
export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');
// #enddocregion token
export const HERO_DI_CONFIG: AppConfig = {
apiEndpoint: 'api.heroes.com',
title: 'Dependency Injection',
};
@@ -1,45 +0,0 @@
// #docplaster
// #docregion
import {Component, inject, Injector} from '@angular/core';
import {Car, Engine, Tires} from './car/car';
import {Hero} from './heroes/hero';
import {HeroService} from './heroes/hero.service';
import {heroServiceProvider} from './heroes/hero.service.provider';
import {Logger} from './logger.service';
@Component({
selector: 'app-injectors',
template: `
<h2>Other Injections</h2>
<div id="car">{{ car.drive() }}</div>
<div id="hero">{{ hero.name }}</div>
<div id="rodent">{{ rodent }}</div>
`,
providers: [Car, Engine, Tires, heroServiceProvider, Logger],
})
export class InjectorComponent {
car: Car;
heroService: HeroService;
hero: Hero;
private injector = inject(Injector);
constructor() {
this.car = this.injector.get(Car);
this.heroService = this.injector.get(HeroService);
this.hero = this.heroService.getHeroes()[0];
}
get rodent() {
const rousDontExist = "R.O.U.S.'s? I don't think they exist!";
return this.injector.get(ROUS, rousDontExist);
}
}
/**
* R.O.U.S. - Rodents Of Unusual Size
* // https://www.youtube.com/watch?v=BOv5ZjAOpC8
*/
class ROUS {}
@@ -1,14 +0,0 @@
// #docregion
import {Injectable} from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class Logger {
logs: string[] = []; // capture logs for testing
log(message: string) {
this.logs.push(message);
console.log(message);
}
}
@@ -1,287 +0,0 @@
/*
* A collection of demo components showing different ways to provide services
* in @Component metadata
*/
import {Component, inject, Injectable, OnInit} from '@angular/core';
import {APP_CONFIG, HERO_DI_CONFIG} from './injection.config';
import {HeroService} from './heroes/hero.service';
import {heroServiceProvider} from './heroes/hero.service.provider';
import {Logger} from './logger.service';
import {UserService} from './user.service';
const template = '{{log}}';
@Component({
selector: 'provider-1',
template,
// #docregion providers-logger
providers: [Logger],
// #enddocregion providers-logger
})
export class Provider1Component {
log: string;
constructor() {
const logger = inject(Logger);
logger.log('Hello from logger provided with Logger class');
this.log = logger.logs[0];
}
}
//////////////////////////////////////////
@Component({
selector: 'provider-3',
template,
providers:
// #docregion providers-3
[{provide: Logger, useClass: Logger}],
// #enddocregion providers-3
})
export class Provider3Component {
log: string;
constructor() {
const logger = inject(Logger);
logger.log('Hello from logger provided with useClass:Logger');
this.log = logger.logs[0];
}
}
//////////////////////////////////////////
export class BetterLogger extends Logger {}
@Component({
selector: 'provider-4',
template,
providers:
// #docregion providers-4
[{provide: Logger, useClass: BetterLogger}],
// #enddocregion providers-4
})
export class Provider4Component {
log: string;
constructor() {
const logger = inject(Logger);
logger.log('Hello from logger provided with useClass:BetterLogger');
this.log = logger.logs[0];
}
}
//////////////////////////////////////////
// #docregion EvenBetterLogger
@Injectable()
export class EvenBetterLogger extends Logger {
private userService = inject(UserService);
override log(message: string) {
const name = this.userService.user.name;
super.log(`Message to ${name}: ${message}`);
}
}
// #enddocregion EvenBetterLogger
@Component({
selector: 'provider-5',
template,
providers:
// #docregion providers-5
[UserService, {provide: Logger, useClass: EvenBetterLogger}],
// #enddocregion providers-5
})
export class Provider5Component {
log: string;
constructor() {
const logger = inject(Logger);
logger.log('Hello from EvenBetterlogger');
this.log = logger.logs[0];
}
}
//////////////////////////////////////////
export class NewLogger extends Logger {}
export class OldLogger {
logs: string[] = [];
log(message: string) {
throw new Error('Should not call the old logger!');
}
}
@Component({
selector: 'provider-6a',
template,
providers: [
NewLogger,
// Not aliased! Creates two instances of `NewLogger`
{provide: OldLogger, useClass: NewLogger},
],
})
export class Provider6aComponent {
log: string;
constructor() {
const newLogger = inject(NewLogger);
const oldLogger = inject(OldLogger);
if (newLogger === oldLogger) {
throw new Error('expected the two loggers to be different instances');
}
oldLogger.log('Hello OldLogger (but we want NewLogger)');
// The newLogger wasn't called so no logs[]
// display the logs of the oldLogger.
this.log = newLogger.logs[0] || oldLogger.logs[0];
}
}
@Component({
selector: 'provider-6b',
template,
providers:
// #docregion providers-6b
[
NewLogger,
// Alias OldLogger w/ reference to NewLogger
{provide: OldLogger, useExisting: NewLogger},
],
// #enddocregion providers-6b
})
export class Provider6bComponent {
log: string;
constructor() {
const newLogger = inject(NewLogger);
const oldLogger = inject(OldLogger);
if (newLogger !== oldLogger) {
throw new Error('expected the two loggers to be the same instance');
}
oldLogger.log('Hello from NewLogger (via aliased OldLogger)');
this.log = newLogger.logs[0];
}
}
//////////////////////////////////////////
// An object in the shape of the logger service
function silentLoggerFn() {}
export const SilentLogger = {
logs: ['Silent logger says "Shhhhh!". Provided via "useValue"'],
log: silentLoggerFn,
};
@Component({
selector: 'provider-7',
template,
providers: [{provide: Logger, useValue: SilentLogger}],
})
export class Provider7Component {
log: string;
constructor() {
const logger = inject(Logger);
logger.log('Hello from logger provided with useValue');
this.log = logger.logs[0];
}
}
/////////////////
@Component({
selector: 'provider-8',
template,
providers: [heroServiceProvider, Logger, UserService],
})
export class Provider8Component {
// must be true else this component would have blown up at runtime
log = 'Hero service injected successfully via heroServiceProvider';
heroService = inject(HeroService);
}
/////////////////
@Component({
selector: 'provider-9',
template,
/*
// #docregion providers-9-interface
// Can't use interface as provider token
[{ provide: AppConfig, useValue: HERO_DI_CONFIG })]
// #enddocregion providers-9-interface
*/
// #docregion providers-9
providers: [{provide: APP_CONFIG, useValue: HERO_DI_CONFIG}],
// #enddocregion providers-9
})
export class Provider9Component {
log = '';
/*
// #docregion provider-9-ctor-interface
// Can't inject using the interface as the parameter type
constructor(private config: AppConfig){ }
// #enddocregion provider-9-ctor-interface
*/
private config = inject(APP_CONFIG);
constructor() {
this.log = 'APP_CONFIG Application title is ' + this.config.title;
}
}
//////////////////////////////////////////
// Sample providers 1 to 7 illustrate a required logger dependency.
// Optional logger, can be null
import {Optional} from '@angular/core';
const someMessage = 'Hello from the injected logger';
@Component({
selector: 'provider-10',
template,
providers: [{provide: Logger, useValue: null}],
})
export class Provider10Component implements OnInit {
log = '';
private logger = inject(Logger, {optional: true});
constructor() {
if (this.logger) {
this.logger.log(someMessage);
}
}
ngOnInit() {
this.log = this.logger ? this.logger.logs[0] : 'Optional logger was not available';
}
}
/////////////////
@Component({
selector: 'app-providers',
template: `
<h2>Provider variations</h2>
<div id="p1"><provider-1></provider-1></div>
<div id="p3"><provider-3></provider-3></div>
<div id="p4"><provider-4></provider-4></div>
<div id="p5"><provider-5></provider-5></div>
<div id="p6a"><provider-6a></provider-6a></div>
<div id="p6b"><provider-6b></provider-6b></div>
<div id="p7"><provider-7></provider-7></div>
<div id="p8"><provider-8></provider-8></div>
<div id="p9"><provider-9></provider-9></div>
<div id="p10"><provider-10></provider-10></div>
`,
imports: [
Provider1Component,
Provider3Component,
Provider4Component,
Provider5Component,
Provider6aComponent,
Provider6bComponent,
Provider7Component,
Provider8Component,
Provider9Component,
Provider10Component,
],
})
export class ProvidersComponent {}
@@ -1,33 +0,0 @@
import {NgModule} from '@angular/core';
import {
Provider1Component,
Provider3Component,
Provider4Component,
Provider5Component,
Provider6aComponent,
Provider6bComponent,
Provider7Component,
Provider8Component,
Provider9Component,
Provider10Component,
ProvidersComponent,
} from './providers.component';
@NgModule({
declarations: [
Provider1Component,
Provider3Component,
Provider4Component,
Provider5Component,
Provider6aComponent,
Provider6bComponent,
Provider7Component,
Provider8Component,
Provider9Component,
Provider10Component,
ProvidersComponent,
],
exports: [ProvidersComponent],
})
export class ProvidersModule {}
@@ -1,54 +0,0 @@
// Simulate a simple test
// Reader should look to the testing chapter for the real thing
import {Component} from '@angular/core';
import {Hero} from './heroes/hero';
import {HeroService} from './heroes/hero.service';
import {HeroListComponent} from './heroes/hero-list.component';
@Component({
selector: 'app-tests',
template: `
<h2>Tests</h2>
<p id="tests">Tests {{ results.pass }}: {{ results.message }}</p>
`,
})
export class TestComponent {
results = runTests();
}
/////////////////////////////////////
function runTests() {
const expectedHeroes = [{name: 'A'}, {name: 'B'}];
const mockService = {getHeroes: () => expectedHeroes} as HeroService;
it('should have heroes when HeroListComponent created', () => {
// Pass the mock to the constructor as the Angular injector would
const component = new HeroListComponent(mockService);
expect(component.heroes.length).toEqual(expectedHeroes.length);
});
return testResults;
}
//////////////////////////////////
// Fake Jasmine infrastructure
let testName: string;
let testResults: {pass: string; message: string};
function expect(actual: any) {
return {
toEqual: (expected: any) => {
testResults =
actual === expected
? {pass: 'passed', message: testName}
: {pass: 'failed', message: `${testName}; expected ${actual} to equal ${expected}.`};
},
};
}
function it(label: string, test: () => void) {
testName = label;
test();
}
@@ -1,10 +0,0 @@
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {RouterModule} from '@angular/router';
import {ServiceModule} from './service-and-module';
// #docregion
@NgModule({
imports: [BrowserModule, RouterModule.forRoot([]), ServiceModule],
})
export class AppModule {}
@@ -1,12 +0,0 @@
// #docregion
import {Injectable, NgModule} from '@angular/core';
@Injectable()
export class Service {
doSomething(): void {}
}
@NgModule({
providers: [Service],
})
export class ServiceModule {}
@@ -1,10 +0,0 @@
import {Injectable} from '@angular/core';
// #docregion
@Injectable({
providedIn: 'root',
useFactory: () => new Service('dependency'),
})
export class Service {
constructor(private dep: string) {}
}
@@ -1,7 +0,0 @@
import {Injectable} from '@angular/core';
// #docregion
@Injectable({
providedIn: 'root',
})
export class Service {}
@@ -1,25 +0,0 @@
// #docregion
import {Injectable} from '@angular/core';
export class User {
constructor(
public name: string,
public isAuthorized = false,
) {}
}
// TODO: get the user; don't 'new' it.
const alice = new User('Alice', true);
const bob = new User('Bob', false);
@Injectable({
providedIn: 'root',
})
export class UserService {
user = bob; // initial user is Bob
// swap users
getNewUser() {
return (this.user = this.user === bob ? alice : bob);
}
}
@@ -1,14 +0,0 @@
<!-- #docregion -->
<!doctype html>
<html lang="en">
<head>
<title>Dependency Injection</title>
<base href="/" />
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<app-root></app-root>
</body>
</html>
@@ -1,5 +0,0 @@
import {bootstrapApplication} from '@angular/platform-browser';
import {AppComponent} from './app/app.component';
import appConfig from './app/app.config';
bootstrapApplication(AppComponent, appConfig);
@@ -1,11 +0,0 @@
{
"description": "Dependency Injection",
"files":[
"!**/*.d.ts",
"!**/*.js",
"!**/*.[0,1,2,3,4].*",
"!**/dummy.module.ts"
],
"file": "src/app/app.component.ts",
"tags": ["dependency", "di"]
}
@@ -1,8 +0,0 @@
/*
* This example project is special in that it is not a cli app.
*
* This is an empty placeholder file to ensure that example tests correctly runs
* tests for this project.
*
* TODO: Fix our infrastructure/tooling, so that this hack is not necessary.
*/
@@ -1,8 +0,0 @@
{
"tests": [
{
"cmd": "yarn",
"args": [ "tsc", "--project", "./tsconfig.app.json" ]
}
]
}
@@ -1,26 +0,0 @@
// TODO: Add unit tests for this file.
/* eslint-disable @angular-eslint/no-output-native */
// #docregion
import {Injector, InjectionToken} from '@angular/core';
interface MyInterface {
someProperty: string;
}
// #docregion InjectionToken
const TOKEN = new InjectionToken<MyInterface>('SomeToken');
// Setting up the provider using the same token instance
const providers = [
{provide: TOKEN, useValue: {someProperty: 'exampleValue'}}, // Mock value for MyInterface
];
// Creating the injector with the provider
const injector = Injector.create({providers});
// Retrieving the value using the same token instance
const myInterface = injector.get(TOKEN);
// myInterface is inferred to be MyInterface.
// #enddocregion InjectionToken
@@ -1,121 +0,0 @@
import {browser, element, by} from 'protractor';
describe('Pipes', () => {
beforeAll(() => browser.get(''));
it('should open correctly', async () => {
expect(await element.all(by.tagName('h1')).get(0).getText()).toEqual('Pipes');
expect(await element(by.css('app-birthday p')).getText()).toEqual(
`The hero's birthday is Apr 15, 1988`,
);
});
it('should show 4 heroes', async () => {
expect(await element.all(by.css('app-hero-list div')).count()).toEqual(4);
});
it('should show a familiar hero in json', async () => {
expect(
await element(by.cssContainingText('app-hero-list p', 'Heroes as JSON')).getText(),
).toContain('Bombasto');
});
it('should show alternate birthday formats', async () => {
expect(
await element(
by.cssContainingText('app-birthday-formatting > p', `The hero's birthday is 4/15/88`),
).isDisplayed(),
).toBe(true);
expect(
await element(
by.cssContainingText('app-birthday-formatting > p', `The hero's birthday is Apr 15, 1988`),
).isDisplayed(),
).toBe(true);
});
it('should be able to chain and compose pipes', async () => {
const chainedPipeEles = element.all(
by.cssContainingText('app-birthday-pipe-chaining p', `The chained hero's`),
);
expect(await chainedPipeEles.count()).toBe(2, 'should have 2 chained pipe examples');
expect(await chainedPipeEles.get(0).getText()).toContain('APR 15, 1988');
expect(await chainedPipeEles.get(1).getText()).toContain('FRIDAY, APRIL 15, 1988');
});
it('should be able to use ExponentialStrengthPipe pipe', async () => {
const ele = element(by.css('app-power-booster p'));
expect(await ele.getText()).toContain('Super power boost: 1024');
});
it('should be able to use the exponential calculator', async () => {
const eles = element.all(by.css('app-power-boost-calculator input'));
const baseInputEle = eles.get(0);
const factorInputEle = eles.get(1);
const outputEle = element(by.css('app-power-boost-calculator p'));
await baseInputEle.clear();
await baseInputEle.sendKeys('7');
await factorInputEle.clear();
await factorInputEle.sendKeys('3');
expect(await outputEle.getText()).toContain('343');
});
it('should support flying heroes (pure) ', async () => {
const nameEle = element(by.css('app-flying-heroes input[type="text"]'));
const canFlyCheckEle = element(by.css('app-flying-heroes #can-fly'));
const mutateCheckEle = element(by.css('app-flying-heroes #mutate'));
const resetEle = element(by.css('app-flying-heroes button'));
const flyingHeroesEle = element.all(by.css('app-flying-heroes #flyers div'));
expect(await canFlyCheckEle.getAttribute('checked')).toEqual(
'true',
'should default to "can fly"',
);
expect(await mutateCheckEle.getAttribute('checked')).toEqual(
'true',
'should default to mutating array',
);
expect(await flyingHeroesEle.count()).toEqual(2, 'only two of the original heroes can fly');
await nameEle.sendKeys('test1\n');
expect(await flyingHeroesEle.count()).toEqual(2, 'no change while mutating array');
await mutateCheckEle.click();
await nameEle.sendKeys('test2\n');
expect(await flyingHeroesEle.count()).toEqual(4, 'not mutating; should see both adds');
expect(await flyingHeroesEle.get(2).getText()).toContain('test1');
expect(await flyingHeroesEle.get(3).getText()).toContain('test2');
await resetEle.click();
expect(await flyingHeroesEle.count()).toEqual(2, 'reset should restore original flying heroes');
});
it('should support flying heroes (impure) ', async () => {
const nameEle = element(by.css('app-flying-heroes-impure input[type="text"]'));
const canFlyCheckEle = element(by.css('app-flying-heroes-impure #can-fly'));
const mutateCheckEle = element(by.css('app-flying-heroes-impure #mutate'));
const flyingHeroesEle = element.all(by.css('app-flying-heroes-impure #flyers div'));
expect(await canFlyCheckEle.getAttribute('checked')).toEqual(
'true',
'should default to "can fly"',
);
expect(await mutateCheckEle.getAttribute('checked')).toEqual(
'true',
'should default to mutating array',
);
expect(await flyingHeroesEle.count()).toEqual(2, 'only two of the original heroes can fly');
await nameEle.sendKeys('test1\n');
expect(await flyingHeroesEle.count()).toEqual(
3,
'new flying hero should show in mutating array',
);
});
it('should show an async hero message', async () => {
expect(await element.all(by.tagName('app-hero-async-message')).get(0).getText()).toContain(
'hero',
);
});
});
@@ -1,45 +0,0 @@
<h1 id="toc">Pipes</h1>
<a href="#date-pipe">Date Pipe</a>
<a href="#date-pipe-formatting">Date Pipe Formatting</a>
<a href="#pipe-chaining">Pipe Chaining</a>
<a href="#power-booster">Power Booster custom pipe</a>
<a href="#hero-async-message">Async Messages and AsyncPipe</a>
<a href="#json-pipe">Json Pipe for Debugging</a>
<a href="#pipe-precedence">Pipes and Precedence</a>
<a href="#flying-heroes">Flying Heroes filter pipe (pure)</a>
<a href="#flying-heroes-impure">Flying Heroes filter pipe (impure)</a>
<hr />
<h2 id="date-pipe">Date Pipe</h2>
<app-birthday />
<hr />
<h2 id="date-pipe-formatting">Date Pipe Formatting</h2>
<app-birthday-formatting />
<hr />
<h2 id="pipe-chaining">Pipe Chaining</h2>
<app-birthday-pipe-chaining />
<hr />
<app-power-booster id="power-booster" />
<h2 id="pipe-precedence">Pipes and Precedence</h2>
<app-pipe-precedence />
<hr />
<app-hero-async-message id="hero-async-message" />
<hr />
<h2 id="json-pipe">Json Pipe for Debugging</h2>
<p>Use the JsonPipe to display component properties for debugging.</p>
<code>data | json</code>
<p>
<app-json-pipe />
</p>
<hr />
<app-flying-heroes id="flying-heroes" />
<hr />
<app-flying-heroes-impure id="flying-heroes-impure" />
@@ -1,36 +0,0 @@
import {Component} from '@angular/core';
import {BirthdayComponent} from './birthday.component';
import {BirthdayFormattingComponent} from './birthday-formatting.component';
import {BirthdayPipeChainingComponent} from './birthday-pipe-chaining.component';
import {FlyingHeroesComponent, FlyingHeroesImpureComponent} from './flying-heroes.component';
import {HeroAsyncMessageComponent} from './hero-async-message.component';
import {PrecedenceComponent} from './precedence.component';
import {JsonPipeComponent} from './json-pipe.component';
import {PowerBoosterComponent} from './power-booster.component';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
imports: [
// Example components
BirthdayComponent,
BirthdayFormattingComponent,
BirthdayPipeChainingComponent,
FlyingHeroesComponent,
FlyingHeroesImpureComponent,
HeroAsyncMessageComponent,
PrecedenceComponent,
JsonPipeComponent,
PowerBoosterComponent,
],
styles: [
'a[href] {display: block; padding: 10px 0;}',
'a:hover {text-decoration: none;}',
'h2 {margin: 0;}',
'code {font-family: monospace; background-color: #eee; padding: 0.5em;}',
],
})
export class AppComponent {
birthday = new Date(1988, 3, 15); // April 15, 1988 -- since month parameter is zero-based
}
@@ -1,8 +0,0 @@
<p>The hero's birthday is {{ birthday | date: 'shortDate' }} in the "shortDate" format.</p>
<p>
The hero's birthday is {{ birthday | date: format }} in "{{ format }}" format. Click the toggle
button to change formats.
</p>
<button type="button" (click)="toggleFormat()">Toggle Format</button>
@@ -1,20 +0,0 @@
import {Component} from '@angular/core';
import {DatePipe} from '@angular/common';
@Component({
selector: 'app-birthday-formatting',
templateUrl: './birthday-formatting.component.html',
imports: [DatePipe],
})
export class BirthdayFormattingComponent {
birthday = new Date(1988, 3, 15); // April 15, 1988 -- since month parameter is zero-based
toggle = true;
get format() {
return this.toggle ? 'mediumDate' : 'fullDate';
}
toggleFormat() {
this.toggle = !this.toggle;
}
}
@@ -1,9 +0,0 @@
<p>
The chained hero's uppercase birthday is
{{ birthday | date | uppercase }}
</p>
<p>
The chained hero's uppercase birthday in "fullDate" format is
{{ birthday | date: 'fullDate' | uppercase }}
</p>
@@ -1,11 +0,0 @@
import {Component} from '@angular/core';
import {DatePipe, UpperCasePipe} from '@angular/common';
@Component({
selector: 'app-birthday-pipe-chaining',
templateUrl: './birthday-pipe-chaining.component.html',
imports: [DatePipe, UpperCasePipe],
})
export class BirthdayPipeChainingComponent {
birthday = new Date(1988, 3, 15); // April 15, 1988 -- since month parameter is zero-based
}
@@ -1 +0,0 @@
<p>The hero's birthday is {{ birthday | date }}</p>
@@ -1,11 +0,0 @@
import {Component} from '@angular/core';
import {DatePipe} from '@angular/common';
@Component({
selector: 'app-birthday',
templateUrl: './birthday.component.html',
imports: [DatePipe],
})
export class BirthdayComponent {
birthday = new Date(1988, 3, 15); // April 15, 1988 -- since month parameter is zero-based
}
@@ -1,20 +0,0 @@
import {Pipe, PipeTransform} from '@angular/core';
/*
* Raise the value exponentially
* Takes an exponent argument that defaults to 1.
* Usage:
* value | exponentialStrength:exponent
* Example:
* {{ 2 | exponentialStrength:10 }}
* formats to: 1024
*/
// #docregion pipe-class
@Pipe({
name: 'exponentialStrength',
})
export class ExponentialStrengthPipe implements PipeTransform {
transform(value: number, exponent = 1): number {
return Math.pow(value, exponent);
}
}
// #enddocregion pipe-class
@@ -1,37 +0,0 @@
<!-- #docplaster-->
<!-- #docregion -->
<h2>{{ title }}</h2>
<label for="hero-name">New hero: </label>
<input
type="text"
id="hero-name"
#box
(keyup.enter)="addHero(box.value); box.value = ''"
placeholder="hero name"
/>
<div>
<input id="can-fly" type="checkbox" [(ngModel)]="canFly" />
<label for="can-fly">can fly</label>
</div>
<div>
<input id="mutate" type="checkbox" [(ngModel)]="mutate" />Mutate array
<button type="button" (click)="reset()">Reset</button>
</div>
<h3>Heroes who fly (piped)</h3>
<div id="flyers">
<!-- #docregion template-flying-heroes -->
@for (hero of heroes | flyingHeroesImpure; track hero) {
<div>{{ hero.name }}</div>
}
<!-- #enddocregion template-flying-heroes -->
</div>
<h3>All Heroes (no pipe)</h3>
<div id="all">
@for (hero of heroes; track hero) {
<div>{{ hero.name }}</div>
}
</div>
@@ -1,43 +0,0 @@
<!-- #docplaster-->
<!-- #docregion -->
<h2>{{ title }}</h2>
<p>Create a new hero and press enter to add it to the list.</p>
<!-- #docregion template-1 -->
<label for="hero-name">New hero name: </label>
<input
type="text"
#box
id="hero-name"
(keyup.enter)="addHero(box.value); box.value = ''"
placeholder="hero name"
/>
<!-- #enddocregion template-1 -->
<div>
<input id="can-fly" type="checkbox" [(ngModel)]="canFly" />
<label for="can-fly">Hero can fly</label>
</div>
<div>
<input id="mutate" type="checkbox" [(ngModel)]="mutate" />
<label for="mutate">Mutate array</label>
<!-- #docregion template-1 -->
<button type="button" (click)="reset()">Reset list of heroes</button>
<!-- #enddocregion template-1 -->
</div>
<h3>Heroes who fly (piped)</h3>
<div id="flyers">
<!-- #docregion template-flying-heroes -->
@for (hero of heroes | flyingHeroes; track hero) {
<div>{{ hero.name }}</div>
}
<!-- #enddocregion template-flying-heroes -->
</div>
<h3>All Heroes (no pipe)</h3>
<div id="all">
<!-- #docregion template-1 -->
@for (hero of heroes; track hero) {
<div>{{ hero.name }}</div>
}
<!-- #enddocregion template-1 -->
</div>
@@ -1,83 +0,0 @@
// #docplaster
// #docregion
import {Component} from '@angular/core';
import {CommonModule} from '@angular/common';
import {FormsModule} from '@angular/forms';
import {FlyingHeroesPipe, FlyingHeroesImpurePipe} from './flying-heroes.pipe';
import {HEROES} from './heroes';
@Component({
selector: 'app-flying-heroes',
templateUrl: './flying-heroes.component.html',
imports: [CommonModule, FormsModule, FlyingHeroesPipe],
styles: [
`
#flyers,
#all {
font-style: italic;
}
button {
display: block;
}
input {
margin: 0.25rem 0.25rem 0.5rem 0;
}
`,
],
})
// #docregion v1
export class FlyingHeroesComponent {
heroes: any[] = [];
canFly = true;
// #enddocregion v1
mutate = true;
title = 'Flying Heroes (pure pipe)';
// #docregion v1
constructor() {
this.reset();
}
addHero(name: string) {
name = name.trim();
if (!name) {
return;
}
const hero = {name, canFly: this.canFly};
// #enddocregion v1
if (this.mutate) {
// Pure pipe won't update display because heroes array reference is unchanged
// Impure pipe will display
// #docregion v1
// #docregion push
this.heroes.push(hero);
// #enddocregion push
// #enddocregion v1
} else {
// Pipe updates display because heroes array is a new object
this.heroes = this.heroes.concat(hero);
}
// #docregion v1
}
reset() {
this.heroes = HEROES.slice();
}
}
// #enddocregion v1
////// Identical except for impure pipe //////
@Component({
selector: 'app-flying-heroes-impure',
templateUrl: './flying-heroes-impure.component.html',
imports: [CommonModule, FormsModule, FlyingHeroesImpurePipe],
styles: [
'#flyers, #all {font-style: italic}',
'button {display: block}',
'input {margin: .25rem .25rem .5rem 0;}',
],
})
export class FlyingHeroesImpureComponent extends FlyingHeroesComponent {
override title = 'Flying Heroes (impure pipe)';
}
@@ -1,28 +0,0 @@
// #docregion
// #docregion pure
import {Pipe, PipeTransform} from '@angular/core';
import {Hero} from './heroes';
@Pipe({
name: 'flyingHeroes',
})
export class FlyingHeroesPipe implements PipeTransform {
transform(allHeroes: Hero[]) {
// #docregion filter
return allHeroes.filter((hero) => hero.canFly);
// #enddocregion filter
}
}
// #enddocregion pure
/////// Identical except for the pure flag
// #docregion impure
// #docregion pipe-decorator
@Pipe({
name: 'flyingHeroesImpure',
pure: false,
})
// #enddocregion pipe-decorator
export class FlyingHeroesImpurePipe extends FlyingHeroesPipe {}
// #enddocregion impure
@@ -1,34 +0,0 @@
import {Component} from '@angular/core';
import {AsyncPipe} from '@angular/common';
import {Observable, interval} from 'rxjs';
import {map, startWith, take} from 'rxjs/operators';
@Component({
selector: 'app-hero-async-message',
template: ` <h2>Async Messages and AsyncPipe</h2>
<p>{{ message$ | async }}</p>
<button type="button" (click)="resend()">Resend Messages</button>`,
imports: [AsyncPipe],
})
export class HeroAsyncMessageComponent {
message$: Observable<string>;
private messages = ['You are my hero!', 'You are the best hero!', 'Will you be my hero?'];
constructor() {
this.message$ = this.getResendObservable();
}
resend() {
this.message$ = this.getResendObservable();
}
private getResendObservable() {
return interval(1000).pipe(
map((i) => `Message #${i + 1}: ${this.messages[i]}`),
take(this.messages.length),
startWith('Waiting for messages...'),
);
}
}
@@ -1,6 +0,0 @@
[
{"name": "Windstorm", "canFly": true},
{"name": "Bombasto", "canFly": false},
{"name": "Magneto", "canFly": false},
{"name": "Tornado", "canFly": true}
]
@@ -1,10 +0,0 @@
export interface Hero {
name: string;
canFly: boolean;
}
export const HEROES: Hero[] = [
{name: 'Windstorm', canFly: true},
{name: 'Bombasto', canFly: false},
{name: 'Magneto', canFly: false},
{name: 'Tornado', canFly: true},
];
@@ -1,18 +0,0 @@
import {Component} from '@angular/core';
import {JsonPipe} from '@angular/common';
@Component({
selector: 'app-json-pipe',
template: `{{ data | json }}`,
imports: [JsonPipe],
})
export class JsonPipeComponent {
data = {
name: 'John Doe',
age: 30,
address: {
street: '123 Main St',
city: 'Anytown',
},
};
}
@@ -1,12 +0,0 @@
import {Component} from '@angular/core';
import {ExponentialStrengthPipe} from './exponential-strength.pipe';
@Component({
selector: 'app-power-booster',
template: `
<h2>Power Booster</h2>
<p>Super power boost: {{ 2 | exponentialStrength: 10 }}</p>
`,
imports: [ExponentialStrengthPipe],
})
export class PowerBoosterComponent {}
@@ -1,45 +0,0 @@
<!-- #docregion -->
<!-- #docregion precedence -->
<p>
In most cases, you'll wrap the entire ternary expression in parentheses before passing the result
to a pipe.
</p>
<p>
Example: <code>(isLeft ? 'left' : 'right') | uppercase</code>
<b>
{{ (isLeft ? 'left' : 'right') | uppercase }}
</b>
</p>
<button type="button" (click)="toggleDirection()">Toggle 'isLeft'</button>
<p>Without parentheses, only the second value is uppercased.</p>
<p>
Example: <code>isGood ? 'good' : 'bad' | uppercase</code>
<b>
{{ isGood ? 'good' : ('bad' | uppercase) }}
</b>
</p>
<p>
Same as: <code>isGood ? 'good' : ('bad' | uppercase)</code>
<b>
{{ isGood ? 'good' : ('bad' | uppercase) }}
</b>
</p>
<button type="button" (click)="toggleGood()">Toggle 'isGood'</button>
<p>
If only one of the values should be passed to a pipe, be explicit and surround that value with
parentheses.
</p>
<p>
Example: <code>isUpper ? ('upper' | uppercase) : 'lower'</code>
<b>
{{ isUpper ? ('upper' | uppercase) : 'lower' }}
</b>
</p>
<button type="button" (click)="toggleCase()">Toggle 'isUpper'</button>
<!-- #enddocregion precedence -->
@@ -1,25 +0,0 @@
import {Component} from '@angular/core';
import {UpperCasePipe} from '@angular/common';
@Component({
selector: 'app-pipe-precedence',
templateUrl: './precedence.component.html',
imports: [UpperCasePipe],
styles: ['code {font-family: monospace; background-color: #eee; padding: 0.5em;}'],
})
export class PrecedenceComponent {
isLeft = true;
toggleDirection() {
this.isLeft = !this.isLeft;
}
isGood = true;
toggleGood() {
this.isGood = !this.isGood;
}
isUpper = true;
toggleCase() {
this.isUpper = !this.isUpper;
}
}
@@ -1,6 +0,0 @@
[
{"name": "Windstorm", "canFly": true},
{"name": "Bombasto", "canFly": false},
{"name": "Magneto", "canFly": false},
{"name": "Tornado", "canFly": true}
]
@@ -1,13 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<title>Pipes</title>
<base href="/" />
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<app-root></app-root>
</body>
</html>
@@ -1,12 +0,0 @@
import {bootstrapApplication, provideProtractorTestingSupport} from '@angular/platform-browser';
import {provideHttpClient} from '@angular/common/http';
import {AppComponent} from './app/app.component';
bootstrapApplication(AppComponent, {
// HttpClientModule is only used in deprecated HeroListComponent
providers: [
provideHttpClient(),
provideProtractorTestingSupport(), // essential for e2e testing
],
});
@@ -1,7 +0,0 @@
{
"description": "Pipes",
"files":[
"!**/*.d.ts",
"!**/*.js"],
"tags": ["pipe"]
}
@@ -1,17 +0,0 @@
import {browser, element, by} from 'protractor';
describe('Resolution-modifiers-example', () => {
beforeAll(() => browser.get(''));
it('shows basic flower emoji', async () => {
expect(await element.all(by.css('p')).get(0).getText()).toContain('🌸');
});
it('shows basic leaf emoji', async () => {
expect(await element.all(by.css('p')).get(1).getText()).toContain('🌿');
});
it('shows tulip in host child', async () => {
expect(await element.all(by.css('p')).get(9).getText()).toContain('🌷');
});
});
@@ -1,14 +0,0 @@
<h1>DI resolution modifiers</h1>
<p>Basic flower service: {{ flower.emoji }}</p>
<p>Basic leaf service: {{ leaf.emoji }}</p>
<app-optional />
<app-self />
<app-self-no-data />
<app-skipself />
<app-host-parent />
@@ -1,32 +0,0 @@
import {Component} from '@angular/core';
import {LeafService} from './leaf.service';
import {FlowerService} from './flower.service';
import {HostComponent} from './host/host.component';
import {OptionalComponent} from './optional/optional.component';
import {SelfComponent} from './self/self.component';
import {HostParentComponent} from './host-parent/host-parent.component';
import {HostChildComponent} from './host-child/host-child.component';
import {SelfNoDataComponent} from './self-no-data/self-no-data.component';
import {SkipselfComponent} from './skipself/skipself.component';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
imports: [
HostComponent,
HostChildComponent,
HostParentComponent,
OptionalComponent,
SelfComponent,
SelfNoDataComponent,
SkipselfComponent,
],
})
export class AppComponent {
name = 'Angular';
constructor(
public flower: FlowerService,
public leaf: LeafService,
) {}
}
@@ -1,10 +0,0 @@
import {ApplicationConfig} from '@angular/core';
import {provideProtractorTestingSupport} from '@angular/platform-browser';
const appConfig: ApplicationConfig = {
providers: [
provideProtractorTestingSupport(), //only needed for docs e2e testing
],
};
export default appConfig;
@@ -1,8 +0,0 @@
import {Injectable} from '@angular/core';
@Injectable({
providedIn: 'root', // provide this service in the root ModuleInjector
})
export class FlowerService {
emoji = '🌸';
}
@@ -1,5 +0,0 @@
.section {
border: 2px solid #369;
padding: 1rem;
margin: 1rem 0;
}
@@ -1,4 +0,0 @@
<div class="section">
<h2>Child of &commat;Host() Component</h2>
<p>Flower emoji: {{ flower.emoji }}</p>
</div>
@@ -1,11 +0,0 @@
import {Component, inject} from '@angular/core';
import {FlowerService} from '../flower.service';
@Component({
selector: 'app-host-child',
templateUrl: './host-child.component.html',
styleUrls: ['./host-child.component.css'],
})
export class HostChildComponent {
public flower = inject(FlowerService);
}
@@ -1,5 +0,0 @@
.section {
border: 2px solid #369;
padding: 1rem;
margin: 1rem 0;
}
@@ -1,5 +0,0 @@
<div class="section">
<h2>Parent of &commat;Host() Component</h2>
<p>Flower emoji: {{ flower.emoji }}</p>
<app-host></app-host>
</div>
@@ -1,14 +0,0 @@
import {Component, inject} from '@angular/core';
import {FlowerService} from '../flower.service';
import {HostComponent} from '../host/host.component';
@Component({
selector: 'app-host-parent',
templateUrl: './host-parent.component.html',
styleUrls: ['./host-parent.component.css'],
providers: [{provide: FlowerService, useValue: {emoji: '🌺'}}],
imports: [HostComponent],
})
export class HostParentComponent {
public flower = inject(FlowerService);
}
@@ -1,5 +0,0 @@
.section {
border: 2px solid #369;
padding: 1rem;
margin: 1rem 0;
}
@@ -1,6 +0,0 @@
<div class="section">
<h2>&commat;Host() Component</h2>
<p>Flower emoji: {{ flower?.emoji }}</p>
<p><i>(&commat;Host() stops it here)</i></p>
<app-host-child />
</div>
@@ -1,20 +0,0 @@
import {Component, Host, Optional} from '@angular/core';
import {FlowerService} from '../flower.service';
import {HostChildComponent} from '../host-child/host-child.component';
// #docregion host-component
@Component({
selector: 'app-host',
templateUrl: './host.component.html',
styleUrls: ['./host.component.css'],
// provide the service
providers: [{provide: FlowerService, useValue: {emoji: '🌷'}}],
imports: [HostChildComponent],
})
export class HostComponent {
// use @Host() in the constructor when injecting the service
constructor(@Host() @Optional() public flower?: FlowerService) {}
}
// #enddocregion host-component
// if you take out @Host() and the providers array, flower will be red hibiscus
@@ -1,10 +0,0 @@
import {Injectable} from '@angular/core';
@Injectable({
providedIn: 'root',
})
// #docregion leafservice
export class LeafService {
emoji = '🌿';
}
// #enddocregion leafservice
@@ -1,6 +0,0 @@
import {Injectable} from '@angular/core';
@Injectable()
export class OptionalService {}
// This service isn't provided anywhere.
@@ -1,5 +0,0 @@
.section {
border: 2px solid #369;
padding: 1rem;
margin: 1rem 0;
}
@@ -1,8 +0,0 @@
<div class="section">
<h2>&commat;Optional() Component</h2>
<p>
This component still works even though the OptionalService (notice &commat;Optional() in the
constructor isn't provided or configured anywhere. Angular goes through tree and visibility
rules, and if it doesn't find the requested service, returns null.
</p>
</div>
@@ -1,18 +0,0 @@
import {Component, Optional} from '@angular/core';
import {OptionalService} from '../optional.service';
@Component({
selector: 'app-optional',
templateUrl: './optional.component.html',
styleUrls: ['./optional.component.css'],
})
// #docregion optional-component
export class OptionalComponent {
constructor(@Optional() public optional?: OptionalService) {}
}
// #enddocregion optional-component
// The OptionalService isn't provided here, in the @Injectable()
// providers array, or in the NgModule. If you remove @Optional()
// from the constructor, you'll get an error.
@@ -1,5 +0,0 @@
.section {
border: 2px solid #369;
padding: 1rem;
margin: 1rem 0;
}
@@ -1,4 +0,0 @@
<div class="section">
<h2>&commat;Self() Component (without a provider)</h2>
<p>Leaf emoji: {{ leaf?.emoji }}</p>
</div>
@@ -1,17 +0,0 @@
import {Component, Self, Optional} from '@angular/core';
import {LeafService} from '../leaf.service';
// #docregion self-no-data-component
@Component({
selector: 'app-self-no-data',
templateUrl: './self-no-data.component.html',
styleUrls: ['./self-no-data.component.css'],
})
export class SelfNoDataComponent {
constructor(@Self() @Optional() public leaf?: LeafService) {}
}
// #enddocregion self-no-data-component
// The app doesn't break because the value being available at self is optional.
// If you remove @Optional(), the app breaks.
@@ -1,5 +0,0 @@
.section {
border: 2px solid #369;
padding: 1rem;
margin: 1rem 0;
}
@@ -1,4 +0,0 @@
<div class="section">
<h2>&commat;Self() Component</h2>
<p>Flower emoji: {{ flower.emoji }}</p>
</div>
@@ -1,17 +0,0 @@
import {Component, Self} from '@angular/core';
import {FlowerService} from '../flower.service';
// #docregion self-component
@Component({
selector: 'app-self',
templateUrl: './self.component.html',
styleUrls: ['./self.component.css'],
providers: [{provide: FlowerService, useValue: {emoji: '🌷'}}],
})
export class SelfComponent {
constructor(@Self() public flower: FlowerService) {}
}
// #enddocregion self-component
// This component provides the FlowerService so the injector
// doesn't have to look further up the injector tree
@@ -1,5 +0,0 @@
.section {
border: 2px solid #369;
padding: 1rem;
margin: 1rem 0;
}

Some files were not shown because too many files have changed in this diff Show More