docs(dependency-injection): revised Dart and TS code and prose (#1573)

docs(dependency-injection): revise Dart and TS code and prose
This commit is contained in:
Patrice Chalin 2016-06-03 11:16:46 -07:00 committed by Kathy Walrath
parent 1324085c0c
commit 05864c2584
49 changed files with 1041 additions and 1299 deletions

View File

@ -1,6 +1,3 @@
// #docplaster
// #docregion
// #docregion imports
import 'package:angular2/core.dart'; import 'package:angular2/core.dart';
import 'app_config.dart'; import 'app_config.dart';
@ -8,9 +5,8 @@ import 'car/car_component.dart';
import 'heroes/heroes_component.dart'; import 'heroes/heroes_component.dart';
import 'logger_service.dart'; import 'logger_service.dart';
import 'user_service.dart'; import 'user_service.dart';
//PENDING: check whether we intend to hide injector_component.dart & providers_component.dart; if so, change docregion name?
// #enddocregion imports
import 'injector_component.dart'; import 'injector_component.dart';
import 'test_component.dart';
import 'providers_component.dart'; import 'providers_component.dart';
@Component( @Component(
@ -31,21 +27,21 @@ import 'providers_component.dart';
CarComponent, CarComponent,
HeroesComponent, HeroesComponent,
InjectorComponent, InjectorComponent,
TestComponent,
ProvidersComponent ProvidersComponent
], ],
// #docregion providers // #docregion providers
providers: const [ providers: const [
Logger, Logger, UserService,
UserService, const Provider(APP_CONFIG, useFactory: heroDiConfigFactory)]
const Provider(AppConfig, useValue: config1)] // #enddocregion providers
// #enddocregion providers
) )
class AppComponent { class AppComponent {
final UserService _userService; final UserService _userService;
final String title; final String title;
//#docregion ctor // #docregion ctor
AppComponent(AppConfig config, this._userService) AppComponent(@Inject(APP_CONFIG) AppConfig config, this._userService)
: title = config.title; : title = config.title;
// #enddocregion ctor // #enddocregion ctor
@ -61,7 +57,6 @@ class AppComponent {
return _userService.user; return _userService.user;
} }
String get userInfo => 'Current user, ${user.name}, is' String get userInfo => 'Current user, ${user.name}, is' +
'${isAuthorized ? "" : " not"} authorized. '; (isAuthorized ? '' : ' not') + ' authorized. ';
} }
// #enddocregion

View File

@ -16,13 +16,3 @@ import 'heroes/heroes_component_1.dart';
class AppComponent { class AppComponent {
final String title = 'Dependency Injection'; final String title = 'Dependency Injection';
} }
// #enddocregion
/*
//#docregion ctor-di-fail
// FAIL! Injectable `config` is not a class!
AppComponent(HeroService heroService, Map config) {
title = config['title'];
}
//#enddocregion ctor-di-fail
*/

View File

@ -21,13 +21,16 @@ import 'logger_service.dart';
], ],
providers: const [ providers: const [
Logger, Logger,
const Provider(AppConfig, useValue: config1) // #docregion providers
]) const Provider(APP_CONFIG, useValue: heroDiConfig)
// #enddocregion providers
]
)
class AppComponent { class AppComponent {
final String title; final String title;
// #docregion ctor // #docregion ctor
AppComponent(AppConfig config) AppComponent(@Inject(APP_CONFIG) Map config)
: title = config.title; : title = config['title'];
// #enddocregion // #enddocregion ctor
} }

View File

@ -1,17 +1,27 @@
// #docregion
// #docregion token // #docregion token
import 'package:angular2/core.dart'; import 'package:angular2/core.dart';
//#docregion const-class const APP_CONFIG = const OpaqueToken('app.config');
@Injectable() // #enddocregion token
// #docregion config
const Map heroDiConfig = const <String,String>{
'apiEndpoint' : 'api.heroes.com',
'title' : 'Dependency Injection'
};
// #enddocregion config
// #docregion config-alt
class AppConfig { class AppConfig {
final apiEndpoint; String apiEndpoint;
final String title; String title;
const AppConfig(this.apiEndpoint, this.title);
} }
//#enddocregion const-class
//#docregion const-object AppConfig heroDiConfigFactory() => new AppConfig()
const config1 = const AppConfig('api.heroes.com', 'Dependency Injection'); ..apiEndpoint = 'api.heroes.com'
//#enddocregion const-object ..title = 'Dependency Injection';
// #enddocregion config-alt
const appConfigProvider = const Provider(APP_CONFIG,
useFactory: heroDiConfigFactory,
deps: const []);

View File

@ -1,21 +1,19 @@
// #docregion
import 'package:angular2/core.dart'; import 'package:angular2/core.dart';
@Injectable() @Injectable()
// #docregion engine
class Engine { class Engine {
final int cylinders = 4; final int cylinders;
Engine() : cylinders = 4;
Engine.withCylinders(this.cylinders);
} }
// #enddocregion engine
@Injectable() @Injectable()
// #docregion tires
class Tires { class Tires {
String make = 'Flintstone'; String make = 'Flintstone';
String model = 'Square'; String model = 'Square';
} }
// #enddocregion tires
@Injectable() @Injectable()
class Car { class Car {
//#docregion car-ctor //#docregion car-ctor
@ -24,11 +22,10 @@ class Car {
String description = 'DI'; String description = 'DI';
Car(this.engine, this.tires); Car(this.engine, this.tires);
// #enddocregion car-ctor // #enddocregion car-ctor
// Method using the engine and tires // Method using the engine and tires
String drive() => '$description car with ${engine.cylinders} cylinders' String drive() => '$description car with '
' and ${tires.make} tires.'; '${engine.cylinders} cylinders and '
'${tires.make} tires.';
} }
// #enddocregion car

View File

@ -3,50 +3,39 @@
import 'car.dart'; import 'car.dart';
///////// example 1 //////////// ///////// example 1 ////////////
Car simpleCar() { Car simpleCar() =>
//#docregion car-ctor-instantiation // #docregion car-ctor-instantiation
// Simple car with 4 cylinders and Flintstone tires. // Simple car with 4 cylinders and Flintstone tires.
var car = new Car(new Engine(), new Tires()); new Car(new Engine(), new Tires())
//#enddocregion car-ctor-instantiation // #enddocregion car-ctor-instantiation
car.description = 'Simple'; ..description = 'Simple';
return car;
}
///////// example 2 //////////// ///////// example 2 ////////////
//#docregion car-ctor-instantiation-with-param // #docregion car-ctor-instantiation-with-param
class Engine2 implements Engine { class Engine2 extends Engine {
final int cylinders; Engine2(cylinders) : super.withCylinders(cylinders);
Engine2(this.cylinders);
} }
//#enddocregion car-ctor-instantiation-with-param
Car superCar() { Car superCar() =>
//#docregion car-ctor-instantiation-with-param // Super car with 12 cylinders and Flintstone tires.
// Super car with 12 cylinders and Flintstone tires. new Car(new Engine2(12), new Tires())
var bigCylinders = 12; ..description = 'Super';
var car = new Car(new Engine2(bigCylinders), new Tires()); // #enddocregion car-ctor-instantiation-with-param
//#enddocregion car-ctor-instantiation-with-param
car.description = 'Super';
return car;
}
/////////// example 3 ////////// /////////// example 3 //////////
//#docregion car-ctor-instantiation-with-mocks // #docregion car-ctor-instantiation-with-mocks
class MockEngine extends Engine { class MockEngine extends Engine {
final int cylinders = 8; MockEngine() : super.withCylinders(8);
} }
class MockTires extends Tires { class MockTires extends Tires {
String make = 'YokoGoodStone'; MockTires() { make = 'YokoGoodStone'; }
} }
//#enddocregion car-ctor-instantiation-with-mocks Car testCar() =>
Car testCar() { // Test car with 8 cylinders and YokoGoodStone tires.
//#docregion car-ctor-instantiation-with-mocks new Car(new MockEngine(), new MockTires())
// Test car with 8 cylinders and YokoGoodStone tires. ..description = 'Test';
var car = new Car(new MockEngine(), new MockTires()); // #enddocregion car-ctor-instantiation-with-mocks
//#enddocregion car-ctor-instantiation-with-mocks
car.description = 'Test';
return car;
}

View File

@ -3,10 +3,9 @@ import 'car.dart';
// BAD pattern! // BAD pattern!
class CarFactory { class CarFactory {
Car createCar() { Car createCar() =>
return new Car(createEngine(), createTires()) new Car(createEngine(), createTires())
..description = 'Factory'; ..description = 'Factory';
}
Engine createEngine() => new Engine(); Engine createEngine() => new Engine();
Tires createTires() => new Tires(); Tires createTires() => new Tires();

View File

@ -1,36 +1,27 @@
// #docplaster
//#docregion
import 'package:angular2/core.dart'; import 'package:angular2/core.dart';
import '../logger_service.dart'; import '../logger_service.dart';
import 'car.dart'; import 'car.dart';
//#docregion injector // #docregion injector
Car useInjector() { Car useInjector() {
ReflectiveInjector injector; ReflectiveInjector injector;
//#enddocregion injector // #enddocregion injector
/* /*
//#docregion injector-no-new // #docregion injector-no-new
// Cannot 'new' an ReflectiveInjector like this! // Cannot instantiate an ReflectiveInjector like this!
var injector = new ReflectiveInjector([Car, Engine, Tires, Logger]); var injector = new ReflectiveInjector([Car, Engine, Tires]);
//#enddocregion injector-no-new // #enddocregion injector-no-new
*/ */
// #docregion injector, injector-create-and-call
//#docregion injector injector = ReflectiveInjector.resolveAndCreate([Car, Engine, Tires]);
// #docregion injector-call
//#docregion injector-create-and-call
injector = ReflectiveInjector.resolveAndCreate([Car, Engine, Tires, Logger]);
//#docregion injector-call
var car = injector.get(Car); var car = injector.get(Car);
//#enddocregion injector-call // #enddocregion injector-call, injector-create-and-call
//#enddocregion injector-create-and-call
car.description = 'Injector'; car.description = 'Injector';
injector = ReflectiveInjector.resolveAndCreate([Logger]);
var logger = injector.get(Logger); var logger = injector.get(Logger);
logger.log('Injector car.drive() said: ' + car.drive()); logger.log('Injector car.drive() said: ' + car.drive());
return car; return car;
} }
//#enddocregion injector
//#enddocregion

View File

@ -17,6 +17,7 @@ class Car {
// Method using the engine and tires // Method using the engine and tires
String drive() => '$description car with ' String drive() => '$description car with '
'${engine.cylinders} cylinders and ${tires.make} tires.'; '${engine.cylinders} cylinders and '
'${tires.make} tires.';
} }
//#enddocregion car //#enddocregion car

View File

@ -1,6 +1,8 @@
// #docregion // #docregion
class Hero { class Hero {
num id; final int id;
String name; final String name;
bool isSecret = false; final bool isSecret;
Hero(this.id, this.name, [this.isSecret = false]);
} }

View File

@ -14,7 +14,8 @@ import 'hero_service.dart';
class HeroListComponent { class HeroListComponent {
final List<Hero> heroes; final List<Hero> heroes;
//#docregion ctor-signature // #docregion ctor-signature
HeroListComponent(HeroService heroService) : heroes = heroService.getHeroes(); HeroListComponent(HeroService heroService)
//#enddocregion ctor-signature // #enddocregion ctor-signature
: heroes = heroService.getHeroes();
} }

View File

@ -1,8 +1,16 @@
// #docplaster
// #docregion // #docregion
import 'package:angular2/core.dart'; import 'package:angular2/core.dart';
import 'hero.dart'; import 'hero.dart';
// #enddocregion
import 'hero_service_1.dart';
/*
// #docregion
import 'hero_service.dart'; import 'hero_service.dart';
// #enddocregion
*/
// #docregion
@Component( @Component(
selector: 'hero-list', selector: 'hero-list',
@ -13,8 +21,8 @@ import 'hero_service.dart';
class HeroListComponent { class HeroListComponent {
final List<Hero> heroes; final List<Hero> heroes;
//#docregion ctor // #docregion ctor
HeroListComponent(HeroService heroService) HeroListComponent(HeroService heroService)
: heroes = heroService.getHeroes(); : heroes = heroService.getHeroes();
//#enddocregion ctor // #enddocregion ctor
} }

View File

@ -20,5 +20,5 @@ class HeroService {
.where((hero) => _isAuthorized || !hero.isSecret) .where((hero) => _isAuthorized || !hero.isSecret)
.toList(); .toList();
} }
// #enddocregion internals // #enddocregion internals
} }

View File

@ -1,7 +1,10 @@
// #docregion // #docregion
import 'package:angular2/core.dart';
import 'hero.dart'; import 'hero.dart';
import 'mock_heroes.dart'; import 'mock_heroes.dart';
@Injectable()
class HeroService { class HeroService {
List<Hero> getHeroes() => HEROES; List<Hero> getHeroes() => HEROES;
} }

View File

@ -11,7 +11,6 @@ class HeroService {
//#docregion ctor //#docregion ctor
HeroService(this._logger); HeroService(this._logger);
//#enddocregion ctor //#enddocregion ctor
List<Hero> getHeroes() { List<Hero> getHeroes() {
_logger.log('Getting heroes ...'); _logger.log('Getting heroes ...');

View File

@ -6,8 +6,7 @@ import '../user_service.dart';
import 'hero_service.dart'; import 'hero_service.dart';
// #docregion factory // #docregion factory
@Injectable() HeroService heroServiceFactory(Logger logger, UserService userService) =>
heroServiceFactory(Logger logger, UserService userService) =>
new HeroService(logger, userService.user.isAuthorized); new HeroService(logger, userService.user.isAuthorized);
// #enddocregion factory // #enddocregion factory

View File

@ -1,23 +1,27 @@
// #docplaster // #docplaster
// #docregion // #docregion
// #docregion v1 // #docregion full, v1
import 'package:angular2/core.dart'; import 'package:angular2/core.dart';
// #enddocregion full, v1
import 'hero_list_component_2.dart';
import 'hero_service_1.dart';
/*
// #docregion full
import 'hero_list_component.dart'; import 'hero_list_component.dart';
// #enddocregion v1
import 'hero_service.dart';
// #docregion v1 // #docregion v1
import 'hero_service.dart';
// #enddocregion full, v1
*/
// #docregion full, v1
@Component( @Component(
selector: 'my-heroes', selector: 'my-heroes',
template: ''' template: '''
<h2>Heroes</h2> <h2>Heroes</h2>
<hero-list></hero-list>''', <hero-list></hero-list>''',
// #enddocregion v1 // #enddocregion v1
// #docregion providers
providers: const [HeroService], providers: const [HeroService],
// #enddocregion providers // #docregion v1
// #docregion v1
directives: const [HeroListComponent]) directives: const [HeroListComponent])
class HeroesComponent {} class HeroesComponent {}
// #enddocregion v1

View File

@ -1,45 +1,18 @@
// #docregion // #docregion
import 'hero.dart'; import 'hero.dart';
List<Hero> HEROES = [ List<Hero> HEROES = <Map>[
new Hero() {'id': 11, 'isSecret': false, 'name': 'Mr. Nice'},
..id = 11 {'id': 12, 'isSecret': false, 'name': 'Narco'},
..isSecret = false {'id': 13, 'isSecret': false, 'name': 'Bombasto'},
..name = 'Mr. Nice', {'id': 14, 'isSecret': false, 'name': 'Celeritas'},
new Hero() {'id': 15, 'isSecret': false, 'name': 'Magneta'},
..id = 12 {'id': 16, 'isSecret': false, 'name': 'RubberMan'},
..isSecret = false {'id': 17, 'isSecret': false, 'name': 'Dynama'},
..name = 'Narco', {'id': 18, 'isSecret': true, 'name': 'Dr IQ'},
new Hero() {'id': 19, 'isSecret': true, 'name': 'Magma'},
..id = 13 {'id': 20, 'isSecret': true, 'name': 'Tornado'}
..isSecret = false ].map(_initHero).toList();
..name = 'Bombasto',
new Hero() Hero _initHero(Map heroProperties) => new Hero(
..id = 14 heroProperties['id'], heroProperties['name'], heroProperties['isSecret']);
..isSecret = false
..name = 'Celeritas',
new Hero()
..id = 15
..isSecret = false
..name = 'Magneta',
new Hero()
..id = 16
..isSecret = false
..name = 'RubberMan',
new Hero()
..id = 17
..isSecret = false
..name = 'Dynama',
new Hero()
..id = 18
..isSecret = true
..name = 'Dr IQ',
new Hero()
..id = 19
..isSecret = true
..name = 'Magma',
new Hero()
..id = 20
..isSecret = true
..name = 'Tornado'
];

View File

@ -1,6 +1,6 @@
// #docplaster // #docplaster
//#docregion // #docregion
import 'package:angular2/core.dart'; import 'package:angular2/core.dart';
import 'car/car.dart'; import 'car/car.dart';
@ -9,7 +9,7 @@ import 'heroes/hero_service.dart';
import 'heroes/hero_service_provider.dart'; import 'heroes/hero_service_provider.dart';
import 'logger_service.dart'; import 'logger_service.dart';
//#docregion injector // #docregion injector
@Component( @Component(
selector: 'my-injectors', selector: 'my-injectors',
template: ''' template: '''
@ -18,39 +18,26 @@ import 'logger_service.dart';
<div id="hero">{{hero.name}}</div> <div id="hero">{{hero.name}}</div>
<div id="rodent">{{rodent}}</div>''', <div id="rodent">{{rodent}}</div>''',
providers: const [ providers: const [
Car, Car, Engine, Tires, heroServiceProvider, Logger])
Engine,
Tires,
const Provider(HeroService, useFactory: heroServiceFactory),
Logger
])
class InjectorComponent { class InjectorComponent {
final Injector _injector; final Injector _injector;
Car car; Car car;
HeroService heroService; HeroService heroService;
Hero hero; Hero hero;
String get rodent {
try {
_injector.get(ROUS);
} on NoProviderError {
return "R.O.U.S.'s? I don't think they exist!";
}
throw new Exception('Aaaargh!');
}
InjectorComponent(this._injector) { InjectorComponent(this._injector) {
car = _injector.get(Car); car = _injector.get(Car);
//#docregion get-hero-service // #docregion get-hero-service
heroService = _injector.get(HeroService); heroService = _injector.get(HeroService);
//#enddocregion get-hero-service // #enddocregion get-hero-service
hero = heroService.getHeroes()[0]; hero = heroService.getHeroes()[0];
} }
}
//#enddocregion injector
/** String get rodent =>
* R.O.U.S. - Rodents Of Unusual Size _injector.get(ROUS, "R.O.U.S.'s? I don't think they exist!");
* // https://www.youtube.com/watch?v=BOv5ZjAOpC8 }
*/ // #enddocregion injector
/// R.O.U.S. - Rodents Of Unusual Size
/// https://www.youtube.com/watch?v=BOv5ZjAOpC8
class ROUS {} class ROUS {}

View File

@ -3,10 +3,11 @@ import 'package:angular2/core.dart';
@Injectable() @Injectable()
class Logger { class Logger {
List<String> logs = []; List<String> _logs = [];
List<String> get logs => _logs;
void log(String message) { void log(String message) {
logs.add(message); _logs.add(message);
print(message); print(message);
} }
} }

View File

@ -1,6 +1,5 @@
// Examples of provider arrays // Examples of provider arrays
// #docplaster
//#docplaster
import 'package:angular2/core.dart'; import 'package:angular2/core.dart';
import 'app_config.dart'; import 'app_config.dart';
@ -9,20 +8,22 @@ import 'heroes/hero_service.dart';
import 'logger_service.dart'; import 'logger_service.dart';
import 'user_service.dart'; import 'user_service.dart';
// TODO file an issue: cannot use the following const in metadata.
const template = '{{log}}';
@Component( @Component(
selector: 'provider-1', selector: 'provider-1',
template: '{{log}}', template: '{{log}}',
providers: // #docregion providers-1, providers-logger
//#docregion providers-1 providers: const [Logger]
const [Logger] // #enddocregion providers-1, providers-logger
//#enddocregion providers-1
) )
class ProviderComponent1 { class ProviderComponent1 {
String log; String log;
ProviderComponent1(Logger logger) { ProviderComponent1(Logger logger) {
logger.log('Hello from logger provided with Logger class'); logger.log('Hello from logger provided with Logger class');
log = logger.logs.last; log = logger.logs[0];
} }
} }
@ -30,29 +31,46 @@ class ProviderComponent1 {
selector: 'provider-2', selector: 'provider-2',
template: '{{log}}', template: '{{log}}',
providers: providers:
//#docregion providers-2 // #docregion providers-2
const [const Provider(Logger, useClass: Logger)] const [const Provider(Logger, useClass: Logger)]
//#enddocregion providers-2 // #enddocregion providers-2
) )
class ProviderComponent2 { class ProviderComponent2 {
String log; String log;
ProviderComponent2(Logger logger) { ProviderComponent2(Logger logger) {
logger.log('Hello from logger provided with Provider class and useClass'); logger.log('Hello from logger provided with Provider class and useClass');
log = logger.logs.last; log = logger.logs[0];
} }
} }
/// Component just used to ensure that shared E2E tests pass.
@Component( @Component(
selector: 'provider-3', selector: 'provider-3',
template: '{{log}}', template: '{{log}}',
providers: const [const Provider(Logger, useClass: Logger)]) providers: const [const Provider(Logger, useClass: Logger)]
)
class ProviderComponent3 { class ProviderComponent3 {
String log; String log;
ProviderComponent3(Logger logger) { ProviderComponent3(Logger logger) {
logger.log('Hello from logger provided with useClass'); logger.log('Hello from logger provided with useClass');
log = logger.logs.last; log = logger.logs[0];
}
}
/// Component just used to ensure that shared E2E tests pass.
@Component(
selector: 'provider-3a',
template: '{{log}}',
providers: const [const Provider(Logger, useClass: Logger)]
)
class ProviderComponent3a {
String log;
ProviderComponent3a(Logger logger) {
logger.log('Hello from logger provided with {provide: Logger, useClass: Logger}');
log = logger.logs[0];
} }
} }
@ -63,58 +81,55 @@ class BetterLogger extends Logger {}
selector: 'provider-4', selector: 'provider-4',
template: '{{log}}', template: '{{log}}',
providers: providers:
//#docregion providers-4 // #docregion providers-4
const [const Provider(Logger, useClass: BetterLogger)] const [const Provider(Logger, useClass: BetterLogger)]
//#enddocregion providers-4 // #enddocregion providers-4
) )
class ProviderComponent4 { class ProviderComponent4 {
String log; String log;
ProviderComponent4(Logger logger) { ProviderComponent4(Logger logger) {
logger.log('Hello from logger provided with useClass:BetterLogger'); logger.log('Hello from logger provided with useClass:BetterLogger');
log = logger.logs.last; log = logger.logs[0];
} }
} }
// #docregion EvenBetterLogger // #docregion EvenBetterLogger
@Injectable() @Injectable()
class EvenBetterLogger implements Logger { class EvenBetterLogger extends Logger {
final UserService _userService; final UserService _userService;
@override List<String> logs = [];
EvenBetterLogger(this._userService); EvenBetterLogger(this._userService);
@override void log(String message) { @override void log(String message) {
var msg = 'Message to ${_userService.user.name}: $message.'; var name = _userService.user.name;
print(msg); super.log('Message to $name: $message');
logs.add(msg);
} }
} }
// #enddocregion EvenBetterLogger // #enddocregion EvenBetterLogger
@Component( @Component(
selector: 'provider-5', selector: 'provider-5',
template: '{{log}}', template: '{{log}}',
providers: providers:
//#docregion providers-5 // #docregion providers-5
const [UserService, const Provider(Logger, useClass: EvenBetterLogger)] const [UserService, const Provider(Logger, useClass: EvenBetterLogger)]
//#enddocregion providers-5 // #enddocregion providers-5
) )
class ProviderComponent5 { class ProviderComponent5 {
String log; String log;
ProviderComponent5(Logger logger) { ProviderComponent5(Logger logger) {
logger.log('Hello from EvenBetterlogger'); logger.log('Hello from EvenBetterlogger');
log = logger.logs.last; log = logger.logs[0];
} }
} }
@Injectable() @Injectable()
class NewLogger extends Logger implements OldLogger {} class NewLogger extends Logger implements OldLogger {}
class OldLogger { class OldLogger extends Logger {
List<String> logs = []; @override
void log(String message) { void log(String message) {
throw new Exception('Should not call the old logger!'); throw new Exception('Should not call the old logger!');
} }
@ -124,26 +139,23 @@ class OldLogger {
selector: 'provider-6a', selector: 'provider-6a',
template: '{{log}}', template: '{{log}}',
providers: providers:
//#docregion providers-6a // #docregion providers-6a
const [NewLogger, const [NewLogger,
// Not aliased! Creates two instances of `NewLogger` // Not aliased! Creates two instances of `NewLogger`
const Provider(OldLogger, useClass: NewLogger)] const Provider(OldLogger, useClass: NewLogger)]
//#enddocregion providers-6a // #enddocregion providers-6a
) )
class ProviderComponent6a { class ProviderComponent6a {
String log; String log;
ProviderComponent6a(NewLogger newLogger, OldLogger oldLogger) { ProviderComponent6a(NewLogger newLogger, OldLogger oldLogger) {
if (identical(newLogger, oldLogger)) { if (newLogger == oldLogger) {
throw new Exception('expected the two loggers to be different instances'); throw new Exception('expected the two loggers to be different instances');
} }
oldLogger.log('Hello OldLogger (but we want NewLogger)'); oldLogger.log('Hello OldLogger (but we want NewLogger)');
// The newLogger wasn't called so no logs[] // The newLogger wasn't called so no logs[]
// display the logs of the oldLogger. // display the logs of the oldLogger.
log = newLogger.logs == null || newLogger.logs.isEmpty log = newLogger.logs.isEmpty ? oldLogger.logs[0] : newLogger.logs[0];
? oldLogger.logs[0]
: newLogger.logs[0];
} }
} }
@ -151,17 +163,17 @@ class ProviderComponent6a {
selector: 'provider-6b', selector: 'provider-6b',
template: '{{log}}', template: '{{log}}',
providers: providers:
//#docregion providers-6b // #docregion providers-6b
const [NewLogger, const [NewLogger,
// Alias OldLogger with reference to NewLogger // Alias OldLogger with reference to NewLogger
const Provider(OldLogger, useExisting: NewLogger)] const Provider(OldLogger, useExisting: NewLogger)]
//#enddocregion providers-6b // #enddocregion providers-6b
) )
class ProviderComponent6b { class ProviderComponent6b {
String log; String log;
ProviderComponent6b(NewLogger newLogger, OldLogger oldLogger) { ProviderComponent6b(NewLogger newLogger, OldLogger oldLogger) {
if (!identical(newLogger, oldLogger)) { if (newLogger != oldLogger) {
throw new Exception('expected the two loggers to be the same instance'); throw new Exception('expected the two loggers to be the same instance');
} }
oldLogger.log('Hello from NewLogger (via aliased OldLogger)'); oldLogger.log('Hello from NewLogger (via aliased OldLogger)');
@ -169,140 +181,102 @@ class ProviderComponent6b {
} }
} }
// #docregion opaque-token // #docregion silent-logger
const loggerPrefix = const OpaqueToken('Logger prefix'); // #docregion const-class
// #enddocregion opaque-token class SilentLogger implements Logger {
@override
final List<String> logs = const ['Silent logger says "Shhhhh!". Provided via "useValue"'];
// #docregion configurable-logger const SilentLogger();
@Injectable()
class ConfigurableLogger extends Logger {
final String _prefix;
// #docregion use-opaque-token
ConfigurableLogger(@Inject(loggerPrefix) this._prefix);
// #enddocregion use-opaque-token
@override @override
void log(String msg) { void log(String message) { }
super.log('$_prefix: $msg');
}
} }
// #enddocregion configurable-logger // #enddocregion const-class
// #docregion const-object
@Component(selector: 'provider-7', template: '{{message}}', const silentLogger = const SilentLogger();
//#docregion providers-7 // #enddocregion const-object
providers: const [ // #enddocregion silent-logger
const Provider(Logger, useClass: ConfigurableLogger),
//#docregion providers-usevalue @Component(
const Provider(loggerPrefix, useValue: 'Testing') selector: 'provider-7',
//#enddocregion providers-usevalue template: '{{log}}',
] providers:
//#enddocregion providers-7 // #docregion providers-7
const [const Provider(Logger, useValue: silentLogger)]
// #enddocregion providers-7
) )
class ProviderComponent7 { class ProviderComponent7 {
String message; String log;
ProviderComponent7(Logger logger) { ProviderComponent7(Logger logger) {
logger.log('Hello from configurable logger.'); logger.log('Hello from logger provided with useValue');
message = logger.logs.last; log = logger.logs[0];
} }
} }
@Component(selector: 'provider-8', template: '{{log}}', providers: const [
const Provider(HeroService, useFactory: heroServiceFactory),
Logger,
UserService
])
class ProviderComponent8 {
// #docregion provider-8-ctor
ProviderComponent8(HeroService heroService);
// #enddocregion provider-8-ctor
// must be true else this component would have blown up at runtime
var log = 'Hero service injected successfully';
}
@Component( @Component(
selector: 'provider-9', selector: 'provider-8',
template: '{{log}}', template: '{{log}}',
providers: providers: const [heroServiceProvider, Logger, UserService])
// #docregion providers-9 class ProviderComponent8 {
const [const Provider(AppConfig, useValue: config1)] // #docregion provider-8-ctor
// #enddocregion providers-9 ProviderComponent8(HeroService heroService);
// #enddocregion provider-8-ctor
// must be true else this component would have blown up at runtime
var log = 'Hero service injected successfully via heroServiceProvider';
}
@Component(
selector: 'provider-9',
template: '{{log}}',
// #docregion providers-9
providers: const [
const Provider(APP_CONFIG, useValue: heroDiConfig)]
// #enddocregion providers-9
) )
class ProviderComponent9 implements OnInit { class ProviderComponent9 implements OnInit {
AppConfig _config; Map _config;
String log; String log;
ProviderComponent9(AppConfig this._config); // #docregion provider-9-ctor
ProviderComponent9(@Inject(APP_CONFIG) this._config);
// #enddocregion provider-9-ctor
@override @override
void ngOnInit() { void ngOnInit() {
log = 'appConfigToken Application title is ${_config.title}'; log = 'APP_CONFIG Application title is ${_config['title']}';
} }
} }
// Normal required logger // Sample providers 1 to 7 illustrate a required logger dependency.
@Component(selector: 'provider-10a', template: '{{log}}', // Optional logger, can be null.
//#docregion providers-logger @Component(selector: 'provider-10', template: '{{log}}')
providers: const [Logger] class ProviderComponent10 implements OnInit {
//#enddocregion providers-logger
)
class ProviderComponent10a {
String log;
ProviderComponent10a(Logger logger) {
logger.log('Hello from the required logger.');
log = logger.logs.last;
}
}
// Optional logger, can be null
@Component(selector: 'provider-10b', template: '{{log}}')
class ProviderComponent10b {
// #docregion provider-10-ctor
final Logger _logger; final Logger _logger;
String log; String log;
ProviderComponent10b(@Optional() Logger this._logger) { /*
// . . .
// #enddocregion provider-10-ctor
_logger == null ? log = 'No logger exists' : log = _logger.logs.last;
// #docregion provider-10-ctor // #docregion provider-10-ctor
HeroService(@Optional() this._logger) {
// #enddocregion provider-10-ctor
*/
ProviderComponent10(@Optional() this._logger) {
const someMessage = 'Hello from the injected logger';
// #docregion provider-10-ctor
if (_logger != null)
_logger.log(someMessage);
} }
// #enddocregion provider-10-ctor // #enddocregion provider-10-ctor
}
// Optional logger, non null @override
@Component(selector: 'provider-10c', template: '{{log}}') void ngOnInit() {
class ProviderComponent10c { log = _logger == null ? 'Optional logger was not available' : _logger.logs[0];
// #docregion provider-10-logger
final Logger _logger;
String log;
ProviderComponent10c(@Optional() Logger logger) :
_logger = logger ?? new DoNothingLogger() {
// . . .
// #enddocregion provider-10-logger
logger == null
? _logger.log('Optional logger was not available.')
: _logger.log('Hello from the injected logger.');
log = _logger.logs.last;
// #docregion provider-10-logger
} }
// #enddocregion provider-10-logger
} }
// #docregion provider-10-logger
// . . .
@Injectable()
class DoNothingLogger extends Logger {
@override List<String> logs = [];
@override void log(String msg) => logs.add(msg);
}
// #enddocregion provider-10-logger
@Component( @Component(
selector: 'my-providers', selector: 'my-providers',
template: ''' template: '''
@ -310,20 +284,20 @@ class DoNothingLogger extends Logger {
<div id="p1"><provider-1></provider-1></div> <div id="p1"><provider-1></provider-1></div>
<div id="p2"><provider-2></provider-2></div> <div id="p2"><provider-2></provider-2></div>
<div id="p3"><provider-3></provider-3></div> <div id="p3"><provider-3></provider-3></div>
<div id="p3a"><provider-3a></provider-3a></div>
<div id="p4"><provider-4></provider-4></div> <div id="p4"><provider-4></provider-4></div>
<div id="p5"><provider-5></provider-5></div> <div id="p5"><provider-5></provider-5></div>
<div id="p6a"><provider-6a></provider-6a></div> <div id="p6a"><provider-6a></provider-6a></div>
<div id="p6b"><provider-6b></provider-6b></div> <div id="p6b"><provider-6b></provider-6b></div>
<div id="p7"><provider-7></provider-7></div> <div id="p7"><provider-7></provider-7></div>
<div id="p8"><provider-8></provider-8></div> <div id="p8"><provider-8></provider-8></div>
<div id="p8"><provider-9></provider-9></div> <div id="p9"><provider-9></provider-9></div>
<div id="p10a"><provider-10a></provider-10a></div> <div id="p10"><provider-10></provider-10></div>''',
<div id="p10b"><provider-10b></provider-10b></div>
<div id="p10c"><provider-10c></provider-10c></div>''',
directives: const [ directives: const [
ProviderComponent1, ProviderComponent1,
ProviderComponent2, ProviderComponent2,
ProviderComponent3, ProviderComponent3,
ProviderComponent3a,
ProviderComponent4, ProviderComponent4,
ProviderComponent5, ProviderComponent5,
ProviderComponent6a, ProviderComponent6a,
@ -331,8 +305,6 @@ class DoNothingLogger extends Logger {
ProviderComponent7, ProviderComponent7,
ProviderComponent8, ProviderComponent8,
ProviderComponent9, ProviderComponent9,
ProviderComponent10a, ProviderComponent10
ProviderComponent10b,
ProviderComponent10c
]) ])
class ProvidersComponent {} class ProvidersComponent {}

View File

@ -0,0 +1,65 @@
// Simulate a simple test
// Reader should look to the testing chapter for the real thing
import 'package:angular2/core.dart';
import 'heroes/hero.dart';
import 'heroes/hero_list_component.dart';
import 'heroes/hero_service.dart';
@Component(
selector: 'my-tests',
template: '''
<h2>Tests</h2>
<p id="tests">Tests {{results['pass']}}: {{results['message']}}</p>
''')
class TestComponent {
var results = runTests();
}
class MockHeroService implements HeroService {
final List<Hero> _heroes;
MockHeroService(this._heroes);
@override
List<Hero> getHeroes() => _heroes;
}
/////////////////////////////////////
dynamic runTests() {
//#docregion spec
var expectedHeroes = [new Hero(0, 'A'), new Hero(1, 'B')];
var mockService = new MockHeroService(expectedHeroes);
it('should have heroes when HeroListComponent created', () {
var hlc = new HeroListComponent(mockService);
expect(hlc.heroes.length).toEqual(expectedHeroes.length);
});
//#enddocregion spec
return testResults;
}
//////////////////////////////////
// Fake Jasmine infrastructure
String testName;
dynamic testResults;
dynamic expect(dynamic actual) => new ExpectResult(actual);
class ExpectResult {
final actual;
ExpectResult(this.actual);
void toEqual(dynamic expected) {
testResults = actual == expected
? {'pass': 'passed', 'message': testName}
: {
'pass': 'failed',
'message': '$testName; expected $actual to equal $expected.'
};
}
}
void it(String label, void test()) {
testName = label;
test();
}

View File

@ -1,26 +1,23 @@
// #docregion // #docregion
import 'package:angular2/core.dart'; import 'package:angular2/core.dart';
@Injectable()
class UserService {
UserService() {
user = _bob;
}
// Todo: get the user; don't 'new' it.
User _alice = new User('Alice', true);
User _bob = new User('Bob', false);
// initial user is Bob
User user;
// swaps users
User getNewUser() => user = user == _bob ? _alice : _bob;
}
class User { class User {
String name; final String name;
bool isAuthorized; final bool isAuthorized;
User(this.name, [this.isAuthorized = false]); User(this.name, [this.isAuthorized = false]);
} }
// Todo: get the user; don't 'new' it.
final User _alice = new User('Alice', true);
final User _bob = new User('Bob', false);
@Injectable()
class UserService {
User user;
UserService() : user = _bob;
// swap users
User getNewUser() => user = user == _bob ? _alice : _bob;
}

View File

@ -9,13 +9,8 @@ import 'package:test/test.dart';
/////////////////////////////////////// ///////////////////////////////////////
////#docregion spec ////#docregion spec
List<Hero> expectedHeroes = [ List<Hero> expectedHeroes = [
new Hero() new Hero(1, 'hero1'),
..id = 1 new Hero(2, 'hero2', true)
..name = 'hero1',
new Hero()
..id = 2
..name = 'hero2'
..isSecret = true
]; ];
class HeroServiceMock implements HeroService { class HeroServiceMock implements HeroService {

View File

@ -1,10 +1,9 @@
//#docregion
import 'package:angular2/platform/browser.dart'; import 'package:angular2/platform/browser.dart';
import 'package:dependency_injection/app_component.dart'; import 'package:dependency_injection/app_component.dart';
import 'package:dependency_injection/providers_component.dart'; import 'package:dependency_injection/providers_component.dart';
main() { void main() {
//#docregion bootstrap //#docregion bootstrap
bootstrap(AppComponent); bootstrap(AppComponent);
//#enddocregion bootstrap //#enddocregion bootstrap

View File

@ -1,10 +1,21 @@
import 'package:angular2/platform/browser.dart'; // **WARNING**
import 'package:dependency_injection/app_component.dart'; // To try out this version of the app, ensure that you update:
import 'package:dependency_injection/heroes/hero_service.dart'; // - web/index.html
// - pubspec.yaml
// to refer to this file instead of main.dart
main() { import 'package:angular2/platform/browser.dart';
//#docregion bootstrap
bootstrap(AppComponent, import 'package:dependency_injection/app_component_1.dart';
[HeroService]); // DISCOURAGED (but works) import 'package:dependency_injection/heroes/hero_service_1.dart';
//#enddocregion bootstrap
void main() {
bootstrap(AppComponent);
}
void main_alt() {
// #docregion bootstrap-discouraged
bootstrap(AppComponent,
[HeroService]); // DISCOURAGED (but works)
// #enddocregion bootstrap-discouraged
} }

View File

@ -101,7 +101,7 @@ describe('Dependency Injection Tests', function () {
}); });
it('P5 (useClass:EvenBetterLogger - dependency) displays as expected', function () { it('P5 (useClass:EvenBetterLogger - dependency) displays as expected', function () {
expectedMsg = 'Message to Bob: Hello from EvenBetterlogger.'; expectedMsg = 'Message to Bob: Hello from EvenBetterlogger';
expect(element(by.css('#p5')).getText()).toEqual(expectedMsg); expect(element(by.css('#p5')).getText()).toEqual(expectedMsg);
}); });
@ -121,28 +121,18 @@ describe('Dependency Injection Tests', function () {
}); });
it('P8 (useFactory) displays as expected', function () { it('P8 (useFactory) displays as expected', function () {
expectedMsg = 'Hero service injected successfully'; expectedMsg = 'Hero service injected successfully via heroServiceProvider';
expect(element(by.css('#p8')).getText()).toEqual(expectedMsg); expect(element(by.css('#p8')).getText()).toEqual(expectedMsg);
}); });
it('P9a (string token) displays as expected', function () { it('P9 (OpaqueToken) displays as expected', function () {
expectedMsg = '"app.config" Application title is Dependency Injection';
expect(element(by.css('#p9a')).getText()).toEqual(expectedMsg);
});
it('P9b (OpaqueToken) displays as expected', function () {
expectedMsg = 'APP_CONFIG Application title is Dependency Injection'; expectedMsg = 'APP_CONFIG Application title is Dependency Injection';
expect(element(by.css('#p9b')).getText()).toEqual(expectedMsg); expect(element(by.css('#p9')).getText()).toEqual(expectedMsg);
}); });
it('P10a (required dependency) displays as expected', function () { it('P10 (optional dependency) displays as expected', function () {
expectedMsg = 'Hello from the required logger.'; expectedMsg = 'Optional logger was not available';
expect(element(by.css('#p10a')).getText()).toEqual(expectedMsg); expect(element(by.css('#p10')).getText()).toEqual(expectedMsg);
});
it('P10b (optional dependency) displays as expected', function () {
expectedMsg = 'Optional logger was not available.';
expect(element(by.css('#p10b')).getText()).toEqual(expectedMsg);
}) })
}); });

View File

@ -19,14 +19,3 @@ import { HeroesComponent } from './heroes/heroes.component.1';
export class AppComponent { export class AppComponent {
title = 'Dependency Injection'; title = 'Dependency Injection';
} }
// #enddocregion
/*
//#docregion ctor-di-fail
// FAIL! Injectable `config` is not a class!
constructor(heroService: HeroService, config: config) {
this.title = config.title;
}
//#enddocregion ctor-di-fail
*/

View File

@ -5,7 +5,8 @@ import { CarComponent } from './car/car.component';
import { HeroesComponent } from './heroes/heroes.component.1'; import { HeroesComponent } from './heroes/heroes.component.1';
import { provide, Inject } from '@angular/core'; import { provide, Inject } from '@angular/core';
import { Config, CONFIG } from './app.config'; import { APP_CONFIG, AppConfig,
HERO_DI_CONFIG } from './app.config';
import { Logger } from './logger.service'; import { Logger } from './logger.service';
// #enddocregion imports // #enddocregion imports
@ -17,23 +18,19 @@ import { Logger } from './logger.service';
<my-heroes></my-heroes> <my-heroes></my-heroes>
`, `,
directives:[CarComponent, HeroesComponent], directives:[CarComponent, HeroesComponent],
// #docregion providers
providers: [ providers: [
Logger, Logger,
// #docregion provider-config // #docregion providers
provide('app.config', {useValue: CONFIG}) provide(APP_CONFIG, {useValue: HERO_DI_CONFIG})
// #enddocregion provider-config // #enddocregion providers
] ]
// #docregion providers
}) })
export class AppComponent { export class AppComponent {
title:string; title:string;
// #docregion ctor // #docregion ctor
constructor(@Inject('app.config') config:Config) { constructor(@Inject(APP_CONFIG) config:AppConfig) {
this.title = config.title; this.title = config.title;
} }
// #docregion ctor // #enddocregion ctor
} }
// #enddocregion

View File

@ -6,8 +6,8 @@ import { Component, Inject, provide } from '@angular/core';
import { CarComponent } from './car/car.component'; import { CarComponent } from './car/car.component';
import { HeroesComponent } from './heroes/heroes.component'; import { HeroesComponent } from './heroes/heroes.component';
import { APP_CONFIG, import { APP_CONFIG, AppConfig,
Config, CONFIG } from './app.config'; HERO_DI_CONFIG } from './app.config';
import { Logger } from './logger.service'; import { Logger } from './logger.service';
import { User, UserService } from './user.service'; import { User, UserService } from './user.service';
@ -33,22 +33,21 @@ import { ProvidersComponent } from './providers.component';
`, `,
directives:[CarComponent, HeroesComponent, directives:[CarComponent, HeroesComponent,
InjectorComponent, TestComponent, ProvidersComponent], InjectorComponent, TestComponent, ProvidersComponent],
// #docregion providers // #docregion providers
providers: [ providers: [
Logger, Logger,
UserService, UserService,
provide(APP_CONFIG, {useValue: CONFIG}) provide(APP_CONFIG, {useValue: HERO_DI_CONFIG})
] ]
// #enddocregion providers // #enddocregion providers
}) })
export class AppComponent { export class AppComponent {
title:string; title:string;
//#docregion ctor // #docregion ctor
constructor( constructor(
@Inject(APP_CONFIG) config:Config, @Inject(APP_CONFIG) config:AppConfig,
private userService: UserService) { private userService: UserService) {
this.title = config.title; this.title = config.title;
} }
// #enddocregion ctor // #enddocregion ctor
@ -62,4 +61,3 @@ export class AppComponent {
`${this.isAuthorized ? '' : 'not'} authorized. `; `${this.isAuthorized ? '' : 'not'} authorized. `;
} }
} }
// #enddocregion

View File

@ -1,4 +1,3 @@
//#docregion
// #docregion token // #docregion token
import { OpaqueToken } from '@angular/core'; import { OpaqueToken } from '@angular/core';
@ -6,13 +5,12 @@ export let APP_CONFIG = new OpaqueToken('app.config');
// #enddocregion token // #enddocregion token
//#docregion config //#docregion config
export interface Config { export interface AppConfig {
apiEndpoint: string, apiEndpoint: string,
title: string title: string
} }
export const CONFIG:Config = { export const HERO_DI_CONFIG: AppConfig = {
apiEndpoint: 'api.heroes.com', apiEndpoint: 'api.heroes.com',
title: 'Dependency Injection' title: 'Dependency Injection'
}; };
//#enddocregion config

View File

@ -1,37 +1,27 @@
// #docplaster import { ReflectiveInjector } from '@angular/core';
//#docregion
import { ReflectiveInjector } from '@angular/core';
import { Car, Engine, Tires } from './car'; import { Car, Engine, Tires } from './car';
import { Logger } from '../logger.service'; import { Logger } from '../logger.service';
//#docregion injector // #docregion injector
export function useInjector() { export function useInjector() {
var injector:ReflectiveInjector; var injector: ReflectiveInjector;
// #enddocregion injector
//#enddocregion injector /*
/* // #docregion injector-no-new
//#docregion injector-no-new // Cannot instantiate an ReflectiveInjector like this!
// Cannot 'new' an ReflectiveInjector like this! var injector = new ReflectiveInjector([Car, Engine, Tires]);
var injector = new ReflectiveInjector([Car, Engine, Tires, Logger]); // #enddocregion injector-no-new
//#enddocregion injector-no-new */
*/ // #docregion injector, injector-create-and-call
injector = ReflectiveInjector.resolveAndCreate([Car, Engine, Tires]);
//#docregion injector // #docregion injector-call
//#docregion injector-create-and-call
injector = ReflectiveInjector.resolveAndCreate([Car, Engine, Tires, Logger]);
//#docregion injector-call
var car = injector.get(Car); var car = injector.get(Car);
//#enddocregion injector-call // #enddocregion injector-call, injector-create-and-call
//#enddocregion injector-create-and-call
car.description = 'Injector'; car.description = 'Injector';
injector = ReflectiveInjector.resolveAndCreate([Logger]);
var logger = injector.get(Logger); var logger = injector.get(Logger);
logger.log('Injector car.drive() said: '+car.drive()); logger.log('Injector car.drive() said: '+car.drive());
return car; return car;
} }
//#enddocregion injector
//#enddocregion

View File

@ -1,21 +1,15 @@
// #docregion
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
// #docregion engine
export class Engine { export class Engine {
public cylinders = 4; // default public cylinders = 4;
} }
// #enddocregion engine
// #docregion tires
export class Tires { export class Tires {
public make = 'Flintstone'; public make = 'Flintstone';
public model = 'Square'; public model = 'Square';
} }
// #enddocregion tires
@Injectable() @Injectable()
// #docregion car
export class Car { export class Car {
//#docregion car-ctor //#docregion car-ctor
public description = 'DI'; public description = 'DI';
@ -29,4 +23,3 @@ export class Car {
`${this.engine.cylinders} cylinders and ${this.tires.make} tires.` `${this.engine.cylinders} cylinders and ${this.tires.make} tires.`
} }
} }
// #enddocregion car

View File

@ -9,7 +9,7 @@ import { HEROES } from './mock-heroes';
<div *ngFor="let hero of heroes"> <div *ngFor="let hero of heroes">
{{hero.id}} - {{hero.name}} {{hero.id}} - {{hero.name}}
</div> </div>
`, `
}) })
export class HeroListComponent { export class HeroListComponent {
heroes = HEROES; heroes = HEROES;

View File

@ -1,8 +1,16 @@
// #docplaster
// #docregion // #docregion
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { Hero } from './hero'; import { Hero } from './hero';
// #enddocregion
import { HeroService } from './hero.service.1';
/*
// #docregion
import { HeroService } from './hero.service'; import { HeroService } from './hero.service';
// #enddocregion
*/
// #docregion
@Component({ @Component({
selector: 'hero-list', selector: 'hero-list',

View File

@ -17,8 +17,9 @@ export class HeroListComponent {
heroes: Hero[]; heroes: Hero[];
// #docregion ctor-signature // #docregion ctor-signature
constructor(heroService: HeroService) { constructor(heroService: HeroService)
// #enddocregion ctor-signature // #enddocregion ctor-signature
{
this.heroes = heroService.getHeroes(); this.heroes = heroService.getHeroes();
} }
} }

View File

@ -1,7 +1,10 @@
// #docregion // #docregion
import { Hero } from './hero'; import { Injectable } from '@angular/core';
import { HEROES } from './mock-heroes';
import { Hero } from './hero';
import { HEROES } from './mock-heroes';
@Injectable()
export class HeroService { export class HeroService {
getHeroes() { return HEROES; } getHeroes() { return HEROES; }
} }

View File

@ -1,12 +1,18 @@
// #docplaster // #docplaster
// #docregion // #docregion full, v1
// #docregion v1
import { Component } from '@angular/core'; import { Component } from '@angular/core';
// #enddocregion full, v1
import { HeroListComponent } from './hero-list.component.2';
import { HeroService } from './hero.service.1';
/*
// #docregion full
import { HeroListComponent } from './hero-list.component'; import { HeroListComponent } from './hero-list.component';
// #enddocregion v1
import { HeroService } from './hero.service';
// #docregion v1 // #docregion v1
import { HeroService } from './hero.service';
// #enddocregion full, v1
*/
// #docregion full, v1
@Component({ @Component({
selector: 'my-heroes', selector: 'my-heroes',
@ -15,11 +21,8 @@ import { HeroService } from './hero.service';
<hero-list></hero-list> <hero-list></hero-list>
`, `,
// #enddocregion v1 // #enddocregion v1
// #docregion providers
providers:[HeroService], providers:[HeroService],
// #enddocregion providers // #docregion v1
// #docregion v1
directives:[HeroListComponent] directives:[HeroListComponent]
}) })
export class HeroesComponent { } export class HeroesComponent { }
// #enddocregion v1

View File

@ -2,14 +2,14 @@
import { Hero } from './hero'; import { Hero } from './hero';
export var HEROES: Hero[] = [ export var HEROES: Hero[] = [
{ "id": 11, isSecret: false, "name": "Mr. Nice" }, { id: 11, isSecret: false, name: "Mr. Nice" },
{ "id": 12, isSecret: false, "name": "Narco" }, { id: 12, isSecret: false, name: "Narco" },
{ "id": 13, isSecret: false, "name": "Bombasto" }, { id: 13, isSecret: false, name: "Bombasto" },
{ "id": 14, isSecret: false, "name": "Celeritas" }, { id: 14, isSecret: false, name: "Celeritas" },
{ "id": 15, isSecret: false, "name": "Magneta" }, { id: 15, isSecret: false, name: "Magneta" },
{ "id": 16, isSecret: false, "name": "RubberMan" }, { id: 16, isSecret: false, name: "RubberMan" },
{ "id": 17, isSecret: false, "name": "Dynama" }, { id: 17, isSecret: false, name: "Dynama" },
{ "id": 18, isSecret: true, "name": "Dr IQ" }, { id: 18, isSecret: true, name: "Dr IQ" },
{ "id": 19, isSecret: true, "name": "Magma" }, { id: 19, isSecret: true, name: "Magma" },
{ "id": 20, isSecret: true, "name": "Tornado" } { id: 20, isSecret: true, name: "Tornado" }
]; ];

View File

@ -1,13 +1,14 @@
// #docplaster // #docplaster
//#docregion // #docregion
import { Component, Injector } from '@angular/core'; import { Component, Injector } from '@angular/core';
import { Car, Engine, Tires } from './car/car'; import { Car, Engine, Tires } from './car/car';
import { Hero } from './heroes/hero';
import { HeroService } from './heroes/hero.service'; import { HeroService } from './heroes/hero.service';
import { heroServiceProvider } from './heroes/hero.service.provider'; import { heroServiceProvider } from './heroes/hero.service.provider';
import { Logger } from './logger.service'; import { Logger } from './logger.service';
//#docregion injector // #docregion injector
@Component({ @Component({
selector: 'my-injectors', selector: 'my-injectors',
template: ` template: `
@ -16,29 +17,24 @@ import { Logger } from './logger.service';
<div id="hero">{{hero.name}}</div> <div id="hero">{{hero.name}}</div>
<div id="rodent">{{rodent}}</div> <div id="rodent">{{rodent}}</div>
`, `,
providers: [Car, Engine, Tires, heroServiceProvider, Logger]
providers: [Car, Engine, Tires,
heroServiceProvider, Logger]
}) })
export class InjectorComponent { export class InjectorComponent {
constructor(private injector: Injector) { } constructor(private injector: Injector) { }
car:Car = this.injector.get(Car); car: Car = this.injector.get(Car);
//#docregion get-hero-service // #docregion get-hero-service
heroService:HeroService = this.injector.get(HeroService); heroService: HeroService = this.injector.get(HeroService);
//#enddocregion get-hero-service // #enddocregion get-hero-service
hero = this.heroService.getHeroes()[0]; hero: Hero = this.heroService.getHeroes()[0];
get rodent() { get rodent() {
let rous = this.injector.get(ROUS, null); let rousDontExist = "R.O.U.S.'s? I don't think they exist!";
if (rous) { return this.injector.get(ROUS, rousDontExist);
throw new Error('Aaaargh!')
}
return "R.O.U.S.'s? I don't think they exist!";
} }
} }
//#enddocregion injector // #enddocregion injector
/** /**
* R.O.U.S. - Rodents Of Unusual Size * R.O.U.S. - Rodents Of Unusual Size

View File

@ -4,7 +4,8 @@ import { Injectable } from '@angular/core';
@Injectable() @Injectable()
export class Logger { export class Logger {
logs:string[] = []; // capture logs for testing logs:string[] = []; // capture logs for testing
log(message: string){
log(message: string) {
this.logs.push(message); this.logs.push(message);
console.log(message); console.log(message);
} }

View File

@ -1,8 +1,12 @@
import { bootstrap } from '@angular/platform-browser-dynamic'; import { bootstrap } from '@angular/platform-browser-dynamic';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component.1';
import { HeroService } from './heroes/hero.service'; import { HeroService } from './heroes/hero.service.1';
//#docregion bootstrap bootstrap(AppComponent);
bootstrap(AppComponent,
[HeroService]); // DISCOURAGED (but works) function discouraged() {
//#enddocregion bootstrap //#docregion bootstrap-discouraged
bootstrap(AppComponent,
[HeroService]); // DISCOURAGED (but works)
//#enddocregion bootstrap-discouraged
}

View File

@ -1,4 +1,3 @@
//#docregion
import { bootstrap } from '@angular/platform-browser-dynamic'; import { bootstrap } from '@angular/platform-browser-dynamic';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { ProvidersComponent } from './providers.component'; import { ProvidersComponent } from './providers.component';

View File

@ -4,8 +4,8 @@
import { Component, Inject, Injectable, import { Component, Inject, Injectable,
provide, Provider } from '@angular/core'; provide, Provider } from '@angular/core';
import { APP_CONFIG, import { APP_CONFIG, AppConfig,
Config, CONFIG } from './app.config'; HERO_DI_CONFIG } from './app.config';
import { HeroService } from './heroes/hero.service'; import { HeroService } from './heroes/hero.service';
import { heroServiceProvider } from './heroes/hero.service.provider'; import { heroServiceProvider } from './heroes/hero.service.provider';
@ -18,10 +18,9 @@ let template = '{{log}}';
@Component({ @Component({
selector: 'provider-1', selector: 'provider-1',
template: template, template: template,
providers: // #docregion providers-1, providers-logger
// #docregion providers-1 providers: [Logger]
[Logger] // #enddocregion providers-1, providers-logger
// #enddocregion providers-1
}) })
export class ProviderComponent1 { export class ProviderComponent1 {
log: string; log: string;
@ -104,15 +103,12 @@ export class ProviderComponent4 {
////////////////////////////////////////// //////////////////////////////////////////
// #docregion EvenBetterLogger // #docregion EvenBetterLogger
@Injectable() @Injectable()
class EvenBetterLogger { class EvenBetterLogger extends Logger {
logs: string[] = []; constructor(private userService: UserService) { super(); }
constructor(private userService: UserService) { } log(message: string) {
let name = this.userService.user.name;
log(message: string){ super.log(`Message to ${name}: ${message}`);
message = `Message to ${this.userService.user.name}: ${message}.`;
console.log(message);
this.logs.push(message);
} }
} }
// #enddocregion EvenBetterLogger // #enddocregion EvenBetterLogger
@ -218,117 +214,69 @@ export class ProviderComponent7 {
template: template, template: template,
providers: [heroServiceProvider, Logger, UserService] providers: [heroServiceProvider, Logger, UserService]
}) })
export class ProviderComponent8{ export class ProviderComponent8 {
// #docregion provider-8-ctor // #docregion provider-8-ctor
constructor(heroService: HeroService){ } constructor(heroService: HeroService) { }
// #enddocregion provider-8-ctor // #enddocregion provider-8-ctor
// must be true else this component would have blown up at runtime // must be true else this component would have blown up at runtime
log = 'Hero service injected successfully'; log = 'Hero service injected successfully via heroServiceProvider';
} }
///////////////// /////////////////
@Component({ @Component({
selector: 'provider-9a', selector: 'provider-9',
template: template, template: template,
providers: /*
/* // #docregion providers-9-interface
// #docregion providers-9a-interface // FAIL! Can't use interface as provider token
// FAIL! Can't use interface as provider token [provide(AppConfig, {useValue: HERO_DI_CONFIG})]
[provide(Config, {useValue: CONFIG})] // #enddocregion providers-9-interface
// #enddocregion providers-9a-interface */
*/ // #docregion providers-9
// #docregion providers-9a providers: [provide(APP_CONFIG, {useValue: HERO_DI_CONFIG})]
// Use string as provider token // #enddocregion providers-9
[provide('app.config', {useValue: CONFIG})]
// #enddocregion providers-9a
}) })
export class ProviderComponent9a { export class ProviderComponent9 {
log: string; log: string;
/* /*
// #docregion provider-9a-ctor-interface // #docregion provider-9-ctor-interface
// FAIL! Can't inject using the interface as the parameter type // FAIL! Can't inject using the interface as the parameter type
constructor(private config: Config){ } constructor(private config: AppConfig){ }
// #enddocregion provider-9a-ctor-interface // #enddocregion provider-9-ctor-interface
*/ */
// #docregion provider-9-ctor
// #docregion provider-9a-ctor constructor(@Inject(APP_CONFIG) private config: AppConfig) { }
// @Inject(token) to inject the dependency // #enddocregion provider-9-ctor
constructor(@Inject('app.config') private config: Config){ }
// #enddocregion provider-9a-ctor
ngOnInit() {
this.log = '"app.config" Application title is ' + this.config.title;
}
}
@Component({
selector: 'provider-9b',
template: template,
// #docregion providers-9b
providers: [provide(APP_CONFIG, {useValue: CONFIG})]
// #enddocregion providers-9b
})
export class ProviderComponent9b {
log: string;
// #docregion provider-9b-ctor
constructor(@Inject(APP_CONFIG) private config: Config){ }
// #enddocregion provider-9b-ctor
ngOnInit() { ngOnInit() {
this.log = 'APP_CONFIG Application title is ' + this.config.title; this.log = 'APP_CONFIG Application title is ' + this.config.title;
} }
} }
////////////////////////////////////////// //////////////////////////////////////////
// Normal required logger // Sample providers 1 to 7 illustrate a required logger dependency.
@Component({ // Optional logger, can be null
selector: 'provider-10a',
template: template,
// #docregion providers-logger
providers: [Logger]
// #enddocregion providers-logger
})
export class ProviderComponent10a {
log: string;
constructor(logger: Logger) {
logger.log('Hello from the required logger.');
this.log = logger.logs[0];
}
}
// Optional logger
// #docregion import-optional // #docregion import-optional
import {Optional} from '@angular/core'; import {Optional} from '@angular/core';
// #enddocregion import-optional // #enddocregion import-optional
let some_message: string = 'Hello from the injected logger';
@Component({ @Component({
selector: 'provider-10b', selector: 'provider-10',
template: template template: template
}) })
export class ProviderComponent10b { export class ProviderComponent10 {
// #docregion provider-10-ctor
log: string; log: string;
constructor(@Optional() private logger: Logger) { } // #docregion provider-10-ctor
constructor(@Optional() private logger: Logger) {
if (this.logger)
this.logger.log(some_message);
}
// #enddocregion provider-10-ctor // #enddocregion provider-10-ctor
ngOnInit() { ngOnInit() {
// #docregion provider-10-logger this.log = this.logger ? this.logger.logs[0] : 'Optional logger was not available';
// No logger? Make one!
if (!this.logger) {
this.logger = {
log: (msg: string) => this.logger.logs.push(msg),
logs: []
};
// #enddocregion provider-10-logger
this.logger.log('Optional logger was not available.');
// #docregion provider-10-logger
}
// #enddocregion provider-10-logger
else {
this.logger.log('Hello from the injected logger.');
this.log = this.logger.logs[0];
}
this.log = this.logger.logs[0];
} }
} }
@ -347,10 +295,8 @@ export class ProviderComponent10b {
<div id="p6b"><provider-6b></provider-6b></div> <div id="p6b"><provider-6b></provider-6b></div>
<div id="p7"><provider-7></provider-7></div> <div id="p7"><provider-7></provider-7></div>
<div id="p8"><provider-8></provider-8></div> <div id="p8"><provider-8></provider-8></div>
<div id="p9a"><provider-9a></provider-9a></div> <div id="p9"><provider-9></provider-9></div>
<div id="p9b"><provider-9b></provider-9b></div> <div id="p10"><provider-10></provider-10></div>
<div id="p10a"><provider-10a></provider-10a></div>
<div id="p10b"><provider-10b></provider-10b></div>
`, `,
directives: [ directives: [
ProviderComponent1, ProviderComponent1,
@ -363,10 +309,8 @@ export class ProviderComponent10b {
ProviderComponent6b, ProviderComponent6b,
ProviderComponent7, ProviderComponent7,
ProviderComponent8, ProviderComponent8,
ProviderComponent9a, ProviderComponent9,
ProviderComponent9b, ProviderComponent10,
ProviderComponent10a,
ProviderComponent10b,
], ],
}) })
export class ProvidersComponent { } export class ProvidersComponent { }

View File

@ -35,14 +35,14 @@ function runTests() {
////////////////////////////////// //////////////////////////////////
// Fake Jasmine infrastructure // Fake Jasmine infrastructure
var testName:string; var testName: string;
var testResults: {pass:string; message:string}; var testResults: {pass:string; message:string};
function expect(actual:any) { function expect(actual:any) {
return { return {
toEqual: function(expected:any){ toEqual: function(expected:any){
testResults = actual === expected? testResults = actual === expected?
{pass:'passed', message: `${testName}`} : {pass:'passed', message: testName} :
{pass:'failed', message: `${testName}; expected ${actual} to equal ${expected}.`}; {pass:'failed', message: `${testName}; expected ${actual} to equal ${expected}.`};
} }
} }

View File

@ -1,23 +1,22 @@
// #docregion // #docregion
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
@Injectable()
export class UserService {
// Todo: get the user; don't 'new' it.
private _alice = new User('Alice', true);
private _bob = new User('Bob', false);
// initial user is Bob
user = this._bob;
// swaps users
getNewUser() {
return this.user = this.user === this._bob ? this._alice : this._bob;
}
}
export class User { export class User {
constructor( constructor(
public name:string, public name:string,
public isAuthorized:boolean = false) { } public isAuthorized:boolean = false) { }
} }
// Todo: get the user; don't 'new' it.
let alice = new User('Alice', true);
let bob = new User('Bob', false);
@Injectable()
export class UserService {
user = bob; // initial user is Bob
// swap users
getNewUser() {
return this.user = this.user === bob ? alice : bob;
}
}

View File

@ -1,279 +1,138 @@
include ../_util-fns extends ../../../ts/latest/guide/dependency-injection.jade
+includeShared('{ts}', 'intro') block includes
include ../_util-fns
- var _thisDot = '';
:marked block ctor-syntax
The complete source code for the example app in this chapter is .l-sub-section
[in GitHub](https://github.com/angular/angular.io/tree/master/public/docs/_examples/dependency-injection/dart). :marked
We also leveraged Dart's constructor syntax for declaring parameters and
initializing properties simultaneously.
+includeShared('{ts}', 'why-1') block service-in-its-own-file
+makeExample('dependency-injection/dart/lib/car/car_no_di.dart', 'car', 'lib/car/car.dart (without DI)') //- N/A
+includeShared('{ts}', 'why-2')
+makeTabs( block one-class-per-file-ts-tradeoffs
'dependency-injection/dart/lib/car/car.dart, dependency-injection/dart/lib/car/car_no_di.dart', //- N/A
'car-ctor, car-ctor',
'lib/car/car.dart (excerpt with DI), lib/car/car.dart (excerpt without DI)')(format=".") block injectable-not-always-needed-in-ts
+includeShared('{ts}', 'why-3-1') //- The [Angular 2 Dart Transformer](https://github.com/angular/angular/wiki/Angular-2-Dart-Transformer)
+includeShared('{ts}', 'why-3-2') //- generates static code to replace the use of dart:mirrors. It requires that types be
- var stylePattern = { otl: /(new Car.*$)/gm }; //- identified as targets for static code generation. Generally this is achieved
+makeExample('dependency-injection/dart/lib/car/car_creations.dart', 'car-ctor-instantiation', '', stylePattern)(format=".") //- by marking the class as @Injectable (though there are other mechanisms).
+includeShared('{ts}', 'why-4')
.l-sub-section block ts-any-decorator-will-do
//- N/A
block always-include-paren
:marked :marked
The _consumer_ of `Car` has the problem. The consumer must update the car creation code to Always write `@Injectable()`, not just `@Injectable`.
something like this:
- var stylePattern = { otl: /(new Car.*$)/gm };
+makeExample('dependency-injection/dart/lib/car/car_creations.dart', 'car-ctor-instantiation-with-param', '', stylePattern)(format=".")
:marked
The critical point is this: `Car` itself did not have to change.
We'll take care of the consumer's problem soon enough.
+includeShared('{ts}', 'why-6')
- var stylePattern = { otl: /(new Car.*$)/gm };
+makeExample('dependency-injection/dart/lib/car/car_creations.dart', 'car-ctor-instantiation-with-mocks', '', stylePattern)(format=".")
+includeShared('{ts}', 'why-7')
+makeExample('dependency-injection/dart/lib/car/car_factory.dart', null, 'lib/car/car_factory.dart')
+includeShared('{ts}', 'why-8')
+makeExample('dependency-injection/dart/lib/car/car_injector.dart','injector-call')(format=".")
+includeShared('{ts}', 'why-9')
+includeShared('{ts}', 'di-1')
+makeTabs(
`dependency-injection/dart/lib/heroes/heroes_component_1.dart,
dependency-injection/dart/lib/heroes/hero_list_component_1.dart,
dependency-injection/dart/lib/heroes/hero.dart,
dependency-injection/dart/lib/heroes/mock_heroes.dart`,
'v1,,,',
`lib/heroes/heroes_component.dart,
lib/heroes/hero_list_component.dart,
lib/heroes/hero.dart,
lib/heroes/mock_heroes.dart`)
+includeShared('{ts}', 'di-2')
+includeShared('{ts}', 'di-3')
+makeExample('dependency-injection/dart/lib/heroes/hero_service_1.dart',null, 'lib/heroes/hero_service.dart' )
+includeShared('{ts}', 'di-4')
.l-sub-section
:marked
We aren't even pretending this is a real service.
If we were actually getting data from a remote server, the API would have to be asynchronous,
returning a `Future`.
We'd also have to rewrite the way components consume our service.
This is important in general, but not to our current story.
+includeShared('{ts}', 'di-6')
+includeShared('{ts}', 'di-configure-injector-1')
+makeExample('dependency-injection/dart/web/main.dart', 'bootstrap', 'web/main.dart (excerpt)')(format='.')
+includeShared('{ts}', 'di-configure-injector-2')
+makeExample('dependency-injection/dart/web/main_1.dart', 'bootstrap')(format='.')
+includeShared('{ts}', 'di-configure-injector-3')
+includeShared('{ts}', 'di-register-providers-1')
+makeExample('dependency-injection/dart/lib/heroes/heroes_component_1.dart',null,'lib/heroes/heroes_component.dart')
+includeShared('{ts}', 'di-register-providers-2')
+makeExample('dependency-injection/dart/lib/heroes/heroes_component_1.dart','providers')(format='.')
+includeShared('{ts}', 'di-register-providers-3')
+includeShared('{ts}', 'di-prepare-for-injection-1')
+makeTabs(
`dependency-injection/dart/lib/heroes/hero_list_component_2.dart,
dependency-injection/dart/lib/heroes/hero_list_component_1.dart`,
null,
`lib/heroes/hero_list_component (with DI),
lib/heroes/hero_list_component (without DI)`)
.l-sub-section
:marked
### Focus on the constructor
Adding a parameter to the constructor isn't all that's happening here.
+makeExample('dependency-injection/dart/lib/heroes/hero_list_component_2.dart', 'ctor')(format=".")
:marked
The constructor parameter has a type: `HeroService`.
The `HeroListComponent` class is also annotated with `@Component`
(scroll up to confirm that fact).
Also recall that the parent component (`HeroesComponent`)
has `providers` information for `HeroService`.
The constructor parameter type, the `@Component` annotation,
and the parent's `providers` information combine to tell the
Angular injector to inject an instance of
`HeroService` whenever it creates a new `HeroListComponent`.
+includeShared('{ts}', 'di-create-injector-implicitly-1')
+makeExample('dependency-injection/dart/lib/car/car_injector.dart','injector-create-and-call')(format=".")
+includeShared('{ts}', 'di-create-injector-implicitly-2')
+includeShared('{ts}', 'di-singleton-services')
// Skip the testing section, for now.
// includeShared('{ts}', 'di-testing-component-1')
// includeShared('{ts}', 'di-testing-component-2')
+includeShared('{ts}', 'di-service-service-1')
+makeTabs(
`dependency-injection/dart/lib/heroes/hero_service_2.dart,
dependency-injection/dart/lib/heroes/hero_service_1.dart`,
null,
`lib/heroes/hero_service (v.2),
lib/heroes/hero_service (v.1)`)
+includeShared('{ts}', 'di-service-service-2')
+includeShared('{ts}', 'di-injectable-1')
:marked
Forgetting the `@Injectable()` can cause a runtime error:
code-example(format, language="html").
Cannot find reflection information on &lt;Type>
+includeShared('{ts}', 'di-injectable-2')
.callout.is-critical
header Always include the parentheses
:marked
Always use `@Injectable()`, not just `@Injectable`.
A metadata annotation must be either a reference to a A metadata annotation must be either a reference to a
compile-time constant variable or a call to a constant compile-time constant variable or a call to a constant
constructor such as `Injectable()`. constructor such as `Injectable()`.
If we forget the parentheses, the analyzer will complain: If we forget the parentheses, the analyzer will complain:
"Annotation creation must have arguments". If we try to run the "Annotation creation must have arguments". If we try to run the
app anyway, it won't work, and the console will say app anyway, it won't work, and the console will say
"expression must be a compile-time constant". "expression must be a compile-time constant".
+includeShared('{ts}', 'logger-service-1')
+makeExample( block real-logger
'dependency-injection/dart/lib/logger_service.dart',null, 'lib/logger_service') .l-sub-section
.l-sub-section :marked
A real implementation would probably use the
[logging package](https://pub.dartlang.org/packages/logging).
block optional-logger
//- TBC.
block provider-function-etc
//- N/A
block provider-ctor-args
- var _secondParam = 'named parameter, such as <code>useClass</code>'
:marked :marked
### Implementing a logger We supply two arguments (or more) to the `Provider` constructor.
Our examples use a simple logger. block dart-diff-const-metadata
A real implementation would probably use the .callout.is-helpful
[logging package](https://pub.dartlang.org/packages/logging). header Dart difference: Constants in metadata
:marked :marked
We're likely to need the same logger service everywhere in our application, In Dart, the value of a metadata annotation must be a compile-time constant.
so we put it in the `lib/` folder, and For that reason, we can't call functions to get values
we register it in the `providers` array of the metadata for our application root component, `AppComponent`. to use within an annotation.
+makeExample('dependency-injection/dart/lib/providers_component.dart','providers-logger', 'lib/app_component.dart (excerpt)') Instead, we use constant literals or constant constructors.
+includeShared('{ts}', 'logger-service-3') For example, a TypeScript program might use the
+includeShared('{ts}', 'logger-service-4') function call `provide(Logger, {useClass: BetterLogger})`,
+includeShared('{ts}', 'logger-service-5') which is equivalent to the TypeScript code
+makeExample('dependency-injection/dart/lib/providers_component.dart','provider-10-ctor')(format='.') `new Provider(Logger, {useClass: BetterLogger})`.
+includeShared('{ts}', 'logger-service-6') A Dart annotation would instead use the constant value `const Provider(Logger, useClass: BetterLogger)`.
+makeExample('dependency-injection/dart/lib/providers_component.dart','provider-10-logger')(format='.')
+includeShared('{ts}', 'logger-service-7')
+includeShared('{ts}', 'providers-1') block dart-diff-const-metadata-ctor
+makeExample('dependency-injection/dart/lib/providers_component.dart','providers-logger') .callout.is-helpful
+includeShared('{ts}', 'providers-2') header Dart difference: Constants in metadata
+includeShared('{ts}', 'providers-provide-1') :marked
:marked Because Dart annotations must be compile-time constants,
### The *Provider* class `useValue` is often used with string or list literals.
+includeShared('{ts}', 'providers-provide-1-1') However, `useValue` works with any constant object.
+makeExample('dependency-injection/dart/lib/providers_component.dart','providers-1')
+includeShared('{ts}', 'providers-provide-2') To create a class that can provide constant objects,
+makeExample('dependency-injection/dart/lib/providers_component.dart','providers-2') ensure all its instance variables are `final`,
// includeShared('{ts}', 'providers-provide-3') and give it a `const` constructor.
// includeShared('{ts}', 'providers-provide-4-1')
// Don't discuss provide function. Create a constant instance of the class by using `const` instead of `new`.
:marked
We supply two arguments (or more) to the `Provider` constructor. // - var stylePattern = { otl: /(useValue.*\))/gm };
+includeShared('{ts}', 'providers-provide-4-2') // +makeExample('dependency-injection/dart/lib/providers_component.dart','providers-9','', stylePattern)(format='.')
:marked
The second is a named parameter, such as `useClass`, block non-class-dep-eg
that we can think of as a *recipe* for creating the dependency value. span string, list, map, or maybe a function.
There are many ways to create dependency values... and many ways to write a recipe.
+includeShared('{ts}', 'providers-alternative-1') block config-obj-maps
+makeExample('dependency-injection/dart/lib/providers_component.dart','providers-4') | . They can be
.callout.is-helpful | <b><a href="https://api.dartlang.org/stable/dart-core/Map-class.html">Map</a></b>
header Dart difference: Constants in metadata | literals
block what-should-we-use-as-token
:marked :marked
In Dart, the value of a metadata annotation must be a compile-time constant. But what should we use as the token?
For that reason, we can't call functions to get values While we _could_ use **[Map][]**, we _should not_ because (like
to use within an annotation. `String`) `Map` is too general. Our app might depend on several maps, each
Instead, we use constant literals or constant constructors. for a different purpose.
For example, a TypeScript program might use the
function call `provide(Logger, {useClass: BetterLogger})`,
which is equivalent to the TypeScript code
`new Provider(Logger, {useClass: BetterLogger})`.
A Dart annotation would instead use the constant value `const Provider(Logger, useClass: BetterLogger)`.
+includeShared('{ts}', 'providers-alternative-2')
+makeExample('dependency-injection/dart/lib/providers_component.dart','EvenBetterLogger')
+includeShared('{ts}', 'providers-alternative-3')
+makeExample('dependency-injection/dart/lib/providers_component.dart','providers-5')(format=".")
+includeShared('{ts}', 'providers-aliased-1')
+makeExample('dependency-injection/dart/lib/providers_component.dart','providers-6a')(format=".")
+includeShared('{ts}', 'providers-aliased-2')
- var stylePattern = { otl: /(useExisting.*\))/gm };
+makeExample('dependency-injection/dart/lib/providers_component.dart','providers-6b','', stylePattern)(format=".")
+includeShared('{ts}', 'providers-value-1') [Map]: https://api.dartlang.org/stable/dart-core/Map-class.html
:marked
We can provide objects directly,
instead of asking the injector to create an instance of a class,
by specifying a `useValue` parameter to `Provider`.
Because Dart annotations must be compile-time constants, .callout.is-helpful
`useValue` is often used with string or list literals. header Dart difference: Interfaces are valid tokens
However, `useValue` works with any constant object. :marked
In TypeScript, interfaces don't work as provider tokens.
Dart doesn't have this limitation;
every class implicitly defines an interface,
so interface names are just class names.
`Map` is a *valid* token even though it's the name of an abstract class;
it's just *unsuitable* as a token because it's too general.
To create a class that can provide constant objects, block dart-map-alternative
make sure all its instance variables are `final`,
and give it a `const` constructor:
+makeExample('dependency-injection/dart/lib/app_config.dart','const-class','lib/app_config.dart (excerpt)')(format='.')
:marked
Create a constant instance of the class by using `const` instead of `new`:
+makeExample('dependency-injection/dart/lib/app_config.dart','const-object','lib/app_config.dart (excerpt)')(format='.')
:marked
Then specify the object using the `useValue` argument to `Provider`:
- var stylePattern = { otl: /(useValue.*\))/gm };
+makeExample('dependency-injection/dart/lib/providers_component.dart','providers-9','', stylePattern)(format='.')
:marked
See more `useValue` examples in the
[Non-class dependencies](#non-class-dependencies) and
[OpaqueToken](#opaquetoken) sections.
+includeShared('{ts}', 'providers-factory-1')
+makeExample('dependency-injection/dart/lib/heroes/hero_service.dart','internals', 'lib/heroes/hero_service.dart (excerpt)')(format='.')
+includeShared('{ts}', 'providers-factory-2')
+makeExample('dependency-injection/dart/lib/heroes/hero_service_provider.dart','factory', 'lib/heroes/hero_service_provider.dart (excerpt)')(format='.')
+includeShared('{ts}', 'providers-factory-3')
+makeExample('dependency-injection/dart/lib/heroes/hero_service_provider.dart','provider', 'lib/heroes/hero_service_provider.dart (excerpt)')(format='.')
+includeShared('{ts}', 'providers-factory-4')
+includeShared('{ts}', 'providers-factory-5')
- var stylePattern = { otl: /(providers.*),$/gm };
+makeTabs(
`dependency-injection/dart/lib/heroes/heroes_component.dart,
dependency-injection/dart/lib/heroes/heroes_component_1.dart`,
null,
`lib/heroes/heroes_component (v.3),
lib/heroes/heroes_component (v.2)`,
stylePattern)
+includeShared('{ts}', 'tokens-1')
+makeExample('dependency-injection/dart/lib/injector_component.dart','get-hero-service')(format='.')
+includeShared('{ts}', 'tokens-2')
// [PENDING: How about a better name than ProviderComponent8?]
+makeExample('dependency-injection/dart/lib/providers_component.dart','provider-8-ctor')(format=".")
+includeShared('{ts}', 'tokens-3')
.callout.is-helpful
header Dart difference: Interfaces are valid tokens
:marked :marked
In TypeScript, interfaces don't work as provider tokens. As an alternative to using a configuration `Map`, we can define
Dart doesn't have this problem; a custom configuration class:
every class implicitly defines an interface,
so interface names are just class names.
+includeShared('{ts}', 'tokens-non-class-deps-1')
:marked
We know we can register an object with a [value provider](#value-provider).
But what do we use for the token?
+includeShared('{ts}', 'tokens-opaque-1')
+makeExample('dependency-injection/dart/lib/providers_component.dart','opaque-token')(format='.')
+includeShared('{ts}', 'tokens-opaque-2')
+makeExample('dependency-injection/dart/lib/providers_component.dart','use-opaque-token')(format=".")
:marked
Here's an example of providing configuration information
for an injected class. First define the class:
+makeExample('dependency-injection/dart/lib/providers_component.dart','configurable-logger')(format=".")
:marked
Then inject that class and its configuration information:
+makeExample('dependency-injection/dart/lib/providers_component.dart','providers-7')(format=".")
+makeExample('dependency-injection/ts/app/app.config.ts','config-alt','app/app-config.ts (alternative config)')(format='.')
+includeShared('{ts}', 'summary') :marked
Defining a configuration class has a few benefits. One key benefit
is strong static checking: we'll be warned early if we misspell a property
name or assign it a value of the wrong type.
The Dart [cascade notation][cascade] (`..`) provides a convenient means of initializing
a configuration object.
+includeShared('{ts}', 'appendix-explicit-injector-1') If we use cascades, the configuration object can't be declared `const` and
+makeExample('dependency-injection/dart/lib/injector_component.dart', 'injector', 'lib/injector_component.dart') we can't use a [value provider](#value-provider).
+includeShared('{ts}', 'appendix-explicit-injector-2') A solution is to use a [factory provider](#factory-provider).
We illustrate this next. We also show how to provide and inject the
configuration object in our top-level `AppComponent`:
[cascade]: https://www.dartlang.org/docs/dart-up-and-running/ch02.html#cascade
+makeExcerpt('lib/app_component.dart','providers')
+makeExcerpt('lib/app_component.dart','ctor')

File diff suppressed because it is too large Load Diff