mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
docs(docs-infra): remove unused examples
(cherry picked from commit 899df39c7c)
This commit is contained in:
committed by
Andrew Scott
parent
c133a2c1eb
commit
06952b4f0f
@@ -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 {}
|
||||
-15
@@ -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;
|
||||
}
|
||||
-24
@@ -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
|
||||
-17
@@ -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 {}
|
||||
-12
@@ -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 = '🌸';
|
||||
}
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
.section {
|
||||
border: 2px solid #369;
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
<div class="section">
|
||||
<h2>Child of @Host() Component</h2>
|
||||
<p>Flower emoji: {{ flower.emoji }}</p>
|
||||
</div>
|
||||
-11
@@ -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);
|
||||
}
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
.section {
|
||||
border: 2px solid #369;
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
<div class="section">
|
||||
<h2>Parent of @Host() Component</h2>
|
||||
<p>Flower emoji: {{ flower.emoji }}</p>
|
||||
<app-host></app-host>
|
||||
</div>
|
||||
-14
@@ -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>@Host() Component</h2>
|
||||
<p>Flower emoji: {{ flower?.emoji }}</p>
|
||||
<p><i>(@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;
|
||||
}
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
<div class="section">
|
||||
<h2>@Optional() Component</h2>
|
||||
<p>
|
||||
This component still works even though the OptionalService (notice @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.
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
.section {
|
||||
border: 2px solid #369;
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
<div class="section">
|
||||
<h2>@Self() Component (without a provider)</h2>
|
||||
<p>Leaf emoji: {{ leaf?.emoji }}</p>
|
||||
</div>
|
||||
-17
@@ -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>@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
Reference in New Issue
Block a user