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 'app_config.dart';
@ -8,9 +5,8 @@ import 'car/car_component.dart';
import 'heroes/heroes_component.dart';
import 'logger_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 'test_component.dart';
import 'providers_component.dart';
@Component(
@ -31,21 +27,21 @@ import 'providers_component.dart';
CarComponent,
HeroesComponent,
InjectorComponent,
TestComponent,
ProvidersComponent
],
// #docregion providers
// #docregion providers
providers: const [
Logger,
UserService,
const Provider(AppConfig, useValue: config1)]
// #enddocregion providers
Logger, UserService,
const Provider(APP_CONFIG, useFactory: heroDiConfigFactory)]
// #enddocregion providers
)
class AppComponent {
final UserService _userService;
final String title;
//#docregion ctor
AppComponent(AppConfig config, this._userService)
// #docregion ctor
AppComponent(@Inject(APP_CONFIG) AppConfig config, this._userService)
: title = config.title;
// #enddocregion ctor
@ -61,7 +57,6 @@ class AppComponent {
return _userService.user;
}
String get userInfo => 'Current user, ${user.name}, is'
'${isAuthorized ? "" : " not"} authorized. ';
String get userInfo => 'Current user, ${user.name}, is' +
(isAuthorized ? '' : ' not') + ' authorized. ';
}
// #enddocregion

View File

@ -16,13 +16,3 @@ import 'heroes/heroes_component_1.dart';
class AppComponent {
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 [
Logger,
const Provider(AppConfig, useValue: config1)
])
// #docregion providers
const Provider(APP_CONFIG, useValue: heroDiConfig)
// #enddocregion providers
]
)
class AppComponent {
final String title;
// #docregion ctor
AppComponent(AppConfig config)
: title = config.title;
// #enddocregion
AppComponent(@Inject(APP_CONFIG) Map config)
: title = config['title'];
// #enddocregion ctor
}

View File

@ -1,17 +1,27 @@
// #docregion
// #docregion token
import 'package:angular2/core.dart';
//#docregion const-class
@Injectable()
const APP_CONFIG = const OpaqueToken('app.config');
// #enddocregion token
// #docregion config
const Map heroDiConfig = const <String,String>{
'apiEndpoint' : 'api.heroes.com',
'title' : 'Dependency Injection'
};
// #enddocregion config
// #docregion config-alt
class AppConfig {
final apiEndpoint;
final String title;
const AppConfig(this.apiEndpoint, this.title);
String apiEndpoint;
String title;
}
//#enddocregion const-class
//#docregion const-object
const config1 = const AppConfig('api.heroes.com', 'Dependency Injection');
//#enddocregion const-object
AppConfig heroDiConfigFactory() => new AppConfig()
..apiEndpoint = 'api.heroes.com'
..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';
@Injectable()
// #docregion engine
class Engine {
final int cylinders = 4;
final int cylinders;
Engine() : cylinders = 4;
Engine.withCylinders(this.cylinders);
}
// #enddocregion engine
@Injectable()
// #docregion tires
class Tires {
String make = 'Flintstone';
String model = 'Square';
}
// #enddocregion tires
@Injectable()
class Car {
//#docregion car-ctor
@ -24,11 +22,10 @@ class Car {
String description = 'DI';
Car(this.engine, this.tires);
// #enddocregion car-ctor
// Method using the engine and tires
String drive() => '$description car with ${engine.cylinders} cylinders'
' and ${tires.make} tires.';
String drive() => '$description car with '
'${engine.cylinders} cylinders and '
'${tires.make} tires.';
}
// #enddocregion car

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,45 +1,18 @@
// #docregion
import 'hero.dart';
List<Hero> HEROES = [
new Hero()
..id = 11
..isSecret = false
..name = 'Mr. Nice',
new Hero()
..id = 12
..isSecret = false
..name = 'Narco',
new Hero()
..id = 13
..isSecret = false
..name = 'Bombasto',
new Hero()
..id = 14
..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'
];
List<Hero> HEROES = <Map>[
{'id': 11, 'isSecret': false, 'name': 'Mr. Nice'},
{'id': 12, 'isSecret': false, 'name': 'Narco'},
{'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'}
].map(_initHero).toList();
Hero _initHero(Map heroProperties) => new Hero(
heroProperties['id'], heroProperties['name'], heroProperties['isSecret']);

View File

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

View File

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

View File

@ -1,6 +1,5 @@
// Examples of provider arrays
//#docplaster
// #docplaster
import 'package:angular2/core.dart';
import 'app_config.dart';
@ -9,20 +8,22 @@ import 'heroes/hero_service.dart';
import 'logger_service.dart';
import 'user_service.dart';
// TODO file an issue: cannot use the following const in metadata.
const template = '{{log}}';
@Component(
selector: 'provider-1',
template: '{{log}}',
providers:
//#docregion providers-1
const [Logger]
//#enddocregion providers-1
// #docregion providers-1, providers-logger
providers: const [Logger]
// #enddocregion providers-1, providers-logger
)
class ProviderComponent1 {
String log;
ProviderComponent1(Logger logger) {
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',
template: '{{log}}',
providers:
//#docregion providers-2
const [const Provider(Logger, useClass: Logger)]
//#enddocregion providers-2
// #docregion providers-2
const [const Provider(Logger, useClass: Logger)]
// #enddocregion providers-2
)
class ProviderComponent2 {
String log;
ProviderComponent2(Logger logger) {
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(
selector: 'provider-3',
template: '{{log}}',
providers: const [const Provider(Logger, useClass: Logger)])
selector: 'provider-3',
template: '{{log}}',
providers: const [const Provider(Logger, useClass: Logger)]
)
class ProviderComponent3 {
String log;
ProviderComponent3(Logger logger) {
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',
template: '{{log}}',
providers:
//#docregion providers-4
const [const Provider(Logger, useClass: BetterLogger)]
//#enddocregion providers-4
// #docregion providers-4
const [const Provider(Logger, useClass: BetterLogger)]
// #enddocregion providers-4
)
class ProviderComponent4 {
String log;
ProviderComponent4(Logger logger) {
logger.log('Hello from logger provided with useClass:BetterLogger');
log = logger.logs.last;
log = logger.logs[0];
}
}
// #docregion EvenBetterLogger
@Injectable()
class EvenBetterLogger implements Logger {
class EvenBetterLogger extends Logger {
final UserService _userService;
@override List<String> logs = [];
EvenBetterLogger(this._userService);
@override void log(String message) {
var msg = 'Message to ${_userService.user.name}: $message.';
print(msg);
logs.add(msg);
var name = _userService.user.name;
super.log('Message to $name: $message');
}
}
// #enddocregion EvenBetterLogger
@Component(
selector: 'provider-5',
template: '{{log}}',
providers:
//#docregion providers-5
const [UserService, const Provider(Logger, useClass: EvenBetterLogger)]
//#enddocregion providers-5
// #docregion providers-5
const [UserService, const Provider(Logger, useClass: EvenBetterLogger)]
// #enddocregion providers-5
)
class ProviderComponent5 {
String log;
ProviderComponent5(Logger logger) {
logger.log('Hello from EvenBetterlogger');
log = logger.logs.last;
log = logger.logs[0];
}
}
@Injectable()
class NewLogger extends Logger implements OldLogger {}
class OldLogger {
List<String> logs = [];
class OldLogger extends Logger {
@override
void log(String message) {
throw new Exception('Should not call the old logger!');
}
@ -124,26 +139,23 @@ class OldLogger {
selector: 'provider-6a',
template: '{{log}}',
providers:
//#docregion providers-6a
const [NewLogger,
// Not aliased! Creates two instances of `NewLogger`
const Provider(OldLogger, useClass: NewLogger)]
//#enddocregion providers-6a
// #docregion providers-6a
const [NewLogger,
// Not aliased! Creates two instances of `NewLogger`
const Provider(OldLogger, useClass: NewLogger)]
// #enddocregion providers-6a
)
class ProviderComponent6a {
String log;
ProviderComponent6a(NewLogger newLogger, OldLogger oldLogger) {
if (identical(newLogger, oldLogger)) {
if (newLogger == oldLogger) {
throw new Exception('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.
log = newLogger.logs == null || newLogger.logs.isEmpty
? oldLogger.logs[0]
: newLogger.logs[0];
log = newLogger.logs.isEmpty ? oldLogger.logs[0] : newLogger.logs[0];
}
}
@ -151,17 +163,17 @@ class ProviderComponent6a {
selector: 'provider-6b',
template: '{{log}}',
providers:
//#docregion providers-6b
const [NewLogger,
// Alias OldLogger with reference to NewLogger
const Provider(OldLogger, useExisting: NewLogger)]
//#enddocregion providers-6b
// #docregion providers-6b
const [NewLogger,
// Alias OldLogger with reference to NewLogger
const Provider(OldLogger, useExisting: NewLogger)]
// #enddocregion providers-6b
)
class ProviderComponent6b {
String log;
ProviderComponent6b(NewLogger newLogger, OldLogger oldLogger) {
if (!identical(newLogger, oldLogger)) {
if (newLogger != oldLogger) {
throw new Exception('expected the two loggers to be the same instance');
}
oldLogger.log('Hello from NewLogger (via aliased OldLogger)');
@ -169,140 +181,102 @@ class ProviderComponent6b {
}
}
// #docregion opaque-token
const loggerPrefix = const OpaqueToken('Logger prefix');
// #enddocregion opaque-token
// #docregion silent-logger
// #docregion const-class
class SilentLogger implements Logger {
@override
final List<String> logs = const ['Silent logger says "Shhhhh!". Provided via "useValue"'];
// #docregion configurable-logger
@Injectable()
class ConfigurableLogger extends Logger {
final String _prefix;
// #docregion use-opaque-token
ConfigurableLogger(@Inject(loggerPrefix) this._prefix);
// #enddocregion use-opaque-token
const SilentLogger();
@override
void log(String msg) {
super.log('$_prefix: $msg');
}
void log(String message) { }
}
// #enddocregion configurable-logger
// #enddocregion const-class
// #docregion const-object
@Component(selector: 'provider-7', template: '{{message}}',
//#docregion providers-7
providers: const [
const Provider(Logger, useClass: ConfigurableLogger),
//#docregion providers-usevalue
const Provider(loggerPrefix, useValue: 'Testing')
//#enddocregion providers-usevalue
]
//#enddocregion providers-7
const silentLogger = const SilentLogger();
// #enddocregion const-object
// #enddocregion silent-logger
@Component(
selector: 'provider-7',
template: '{{log}}',
providers:
// #docregion providers-7
const [const Provider(Logger, useValue: silentLogger)]
// #enddocregion providers-7
)
class ProviderComponent7 {
String message;
String log;
ProviderComponent7(Logger logger) {
logger.log('Hello from configurable logger.');
message = logger.logs.last;
logger.log('Hello from logger provided with useValue');
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(
selector: 'provider-9',
template: '{{log}}',
providers:
// #docregion providers-9
const [const Provider(AppConfig, useValue: config1)]
// #enddocregion providers-9
selector: 'provider-8',
template: '{{log}}',
providers: const [heroServiceProvider, 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 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 {
AppConfig _config;
Map _config;
String log;
ProviderComponent9(AppConfig this._config);
// #docregion provider-9-ctor
ProviderComponent9(@Inject(APP_CONFIG) this._config);
// #enddocregion provider-9-ctor
@override
void ngOnInit() {
log = 'appConfigToken Application title is ${_config.title}';
log = 'APP_CONFIG Application title is ${_config['title']}';
}
}
// Normal required logger
@Component(selector: 'provider-10a', template: '{{log}}',
//#docregion providers-logger
providers: const [Logger]
//#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
// Sample providers 1 to 7 illustrate a required logger dependency.
// Optional logger, can be null.
@Component(selector: 'provider-10', template: '{{log}}')
class ProviderComponent10 implements OnInit {
final Logger _logger;
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
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
}
// Optional logger, non null
@Component(selector: 'provider-10c', template: '{{log}}')
class ProviderComponent10c {
// #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
@override
void ngOnInit() {
log = _logger == null ? 'Optional logger was not available' : _logger.logs[0];
}
// #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(
selector: 'my-providers',
template: '''
@ -310,20 +284,20 @@ class DoNothingLogger extends Logger {
<div id="p1"><provider-1></provider-1></div>
<div id="p2"><provider-2></provider-2></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="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="p8"><provider-9></provider-9></div>
<div id="p10a"><provider-10a></provider-10a></div>
<div id="p10b"><provider-10b></provider-10b></div>
<div id="p10c"><provider-10c></provider-10c></div>''',
<div id="p9"><provider-9></provider-9></div>
<div id="p10"><provider-10></provider-10></div>''',
directives: const [
ProviderComponent1,
ProviderComponent2,
ProviderComponent3,
ProviderComponent3a,
ProviderComponent4,
ProviderComponent5,
ProviderComponent6a,
@ -331,8 +305,6 @@ class DoNothingLogger extends Logger {
ProviderComponent7,
ProviderComponent8,
ProviderComponent9,
ProviderComponent10a,
ProviderComponent10b,
ProviderComponent10c
ProviderComponent10
])
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
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 {
String name;
bool isAuthorized;
final String name;
final bool isAuthorized;
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
List<Hero> expectedHeroes = [
new Hero()
..id = 1
..name = 'hero1',
new Hero()
..id = 2
..name = 'hero2'
..isSecret = true
new Hero(1, 'hero1'),
new Hero(2, 'hero2', true)
];
class HeroServiceMock implements HeroService {

View File

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

View File

@ -1,10 +1,21 @@
import 'package:angular2/platform/browser.dart';
import 'package:dependency_injection/app_component.dart';
import 'package:dependency_injection/heroes/hero_service.dart';
// **WARNING**
// To try out this version of the app, ensure that you update:
// - web/index.html
// - pubspec.yaml
// to refer to this file instead of main.dart
main() {
//#docregion bootstrap
bootstrap(AppComponent,
[HeroService]); // DISCOURAGED (but works)
//#enddocregion bootstrap
import 'package:angular2/platform/browser.dart';
import 'package:dependency_injection/app_component_1.dart';
import 'package:dependency_injection/heroes/hero_service_1.dart';
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 () {
expectedMsg = 'Message to Bob: Hello from EvenBetterlogger.';
expectedMsg = 'Message to Bob: Hello from EvenBetterlogger';
expect(element(by.css('#p5')).getText()).toEqual(expectedMsg);
});
@ -121,28 +121,18 @@ describe('Dependency Injection Tests', 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);
});
it('P9a (string token) 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 () {
it('P9 (OpaqueToken) displays as expected', function () {
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 () {
expectedMsg = 'Hello from the required logger.';
expect(element(by.css('#p10a')).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);
it('P10 (optional dependency) displays as expected', function () {
expectedMsg = 'Optional logger was not available';
expect(element(by.css('#p10')).getText()).toEqual(expectedMsg);
})
});

View File

@ -19,14 +19,3 @@ import { HeroesComponent } from './heroes/heroes.component.1';
export class AppComponent {
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 { 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';
// #enddocregion imports
@ -17,23 +18,19 @@ import { Logger } from './logger.service';
<my-heroes></my-heroes>
`,
directives:[CarComponent, HeroesComponent],
// #docregion providers
providers: [
Logger,
// #docregion provider-config
provide('app.config', {useValue: CONFIG})
// #enddocregion provider-config
// #docregion providers
provide(APP_CONFIG, {useValue: HERO_DI_CONFIG})
// #enddocregion providers
]
// #docregion providers
})
export class AppComponent {
title:string;
// #docregion ctor
constructor(@Inject('app.config') config:Config) {
constructor(@Inject(APP_CONFIG) config:AppConfig) {
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 { HeroesComponent } from './heroes/heroes.component';
import { APP_CONFIG,
Config, CONFIG } from './app.config';
import { APP_CONFIG, AppConfig,
HERO_DI_CONFIG } from './app.config';
import { Logger } from './logger.service';
import { User, UserService } from './user.service';
@ -33,22 +33,21 @@ import { ProvidersComponent } from './providers.component';
`,
directives:[CarComponent, HeroesComponent,
InjectorComponent, TestComponent, ProvidersComponent],
// #docregion providers
// #docregion providers
providers: [
Logger,
UserService,
provide(APP_CONFIG, {useValue: CONFIG})
provide(APP_CONFIG, {useValue: HERO_DI_CONFIG})
]
// #enddocregion providers
// #enddocregion providers
})
export class AppComponent {
title:string;
//#docregion ctor
// #docregion ctor
constructor(
@Inject(APP_CONFIG) config:Config,
@Inject(APP_CONFIG) config:AppConfig,
private userService: UserService) {
this.title = config.title;
}
// #enddocregion ctor
@ -62,4 +61,3 @@ export class AppComponent {
`${this.isAuthorized ? '' : 'not'} authorized. `;
}
}
// #enddocregion

View File

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

View File

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

View File

@ -1,21 +1,15 @@
// #docregion
import { Injectable } from '@angular/core';
// #docregion engine
export class Engine {
public cylinders = 4; // default
public cylinders = 4;
}
// #enddocregion engine
// #docregion tires
export class Tires {
public make = 'Flintstone';
public model = 'Square';
}
// #enddocregion tires
@Injectable()
// #docregion car
export class Car {
//#docregion car-ctor
public description = 'DI';
@ -29,4 +23,3 @@ export class Car {
`${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">
{{hero.id}} - {{hero.name}}
</div>
`,
`
})
export class HeroListComponent {
heroes = HEROES;

View File

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

View File

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

View File

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

View File

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

View File

@ -2,14 +2,14 @@
import { Hero } from './hero';
export var HEROES: Hero[] = [
{ "id": 11, isSecret: false, "name": "Mr. Nice" },
{ "id": 12, isSecret: false, "name": "Narco" },
{ "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" }
{ id: 11, isSecret: false, name: "Mr. Nice" },
{ id: 12, isSecret: false, name: "Narco" },
{ 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" }
];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,23 +1,22 @@
// #docregion
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 {
constructor(
public name:string,
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
The complete source code for the example app in this chapter is
[in GitHub](https://github.com/angular/angular.io/tree/master/public/docs/_examples/dependency-injection/dart).
block ctor-syntax
.l-sub-section
:marked
We also leveraged Dart's constructor syntax for declaring parameters and
initializing properties simultaneously.
+includeShared('{ts}', 'why-1')
+makeExample('dependency-injection/dart/lib/car/car_no_di.dart', 'car', 'lib/car/car.dart (without DI)')
+includeShared('{ts}', 'why-2')
+makeTabs(
'dependency-injection/dart/lib/car/car.dart, dependency-injection/dart/lib/car/car_no_di.dart',
'car-ctor, car-ctor',
'lib/car/car.dart (excerpt with DI), lib/car/car.dart (excerpt without DI)')(format=".")
+includeShared('{ts}', 'why-3-1')
+includeShared('{ts}', 'why-3-2')
- var stylePattern = { otl: /(new Car.*$)/gm };
+makeExample('dependency-injection/dart/lib/car/car_creations.dart', 'car-ctor-instantiation', '', stylePattern)(format=".")
+includeShared('{ts}', 'why-4')
.l-sub-section
block service-in-its-own-file
//- N/A
block one-class-per-file-ts-tradeoffs
//- N/A
block injectable-not-always-needed-in-ts
//- The [Angular 2 Dart Transformer](https://github.com/angular/angular/wiki/Angular-2-Dart-Transformer)
//- generates static code to replace the use of dart:mirrors. It requires that types be
//- identified as targets for static code generation. Generally this is achieved
//- by marking the class as @Injectable (though there are other mechanisms).
block ts-any-decorator-will-do
//- N/A
block always-include-paren
:marked
The _consumer_ of `Car` has the problem. The consumer must update the car creation code to
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`.
Always write `@Injectable()`, not just `@Injectable`.
A metadata annotation must be either a reference to a
compile-time constant variable or a call to a constant
constructor such as `Injectable()`.
If we forget the parentheses, the analyzer will complain:
"Annotation creation must have arguments". If we try to run the
app anyway, it won't work, and the console will say
"expression must be a compile-time constant".
+includeShared('{ts}', 'logger-service-1')
+makeExample(
'dependency-injection/dart/lib/logger_service.dart',null, 'lib/logger_service')
.l-sub-section
block real-logger
.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
### Implementing a logger
We supply two arguments (or more) to the `Provider` constructor.
Our examples use a simple logger.
A real implementation would probably use the
[logging package](https://pub.dartlang.org/packages/logging).
:marked
We're likely to need the same logger service everywhere in our application,
so we put it in the `lib/` folder, and
we register it in the `providers` array of the metadata for our application root component, `AppComponent`.
+makeExample('dependency-injection/dart/lib/providers_component.dart','providers-logger', 'lib/app_component.dart (excerpt)')
+includeShared('{ts}', 'logger-service-3')
+includeShared('{ts}', 'logger-service-4')
+includeShared('{ts}', 'logger-service-5')
+makeExample('dependency-injection/dart/lib/providers_component.dart','provider-10-ctor')(format='.')
+includeShared('{ts}', 'logger-service-6')
+makeExample('dependency-injection/dart/lib/providers_component.dart','provider-10-logger')(format='.')
+includeShared('{ts}', 'logger-service-7')
block dart-diff-const-metadata
.callout.is-helpful
header Dart difference: Constants in metadata
:marked
In Dart, the value of a metadata annotation must be a compile-time constant.
For that reason, we can't call functions to get values
to use within an annotation.
Instead, we use constant literals or constant constructors.
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-1')
+makeExample('dependency-injection/dart/lib/providers_component.dart','providers-logger')
+includeShared('{ts}', 'providers-2')
+includeShared('{ts}', 'providers-provide-1')
:marked
### The *Provider* class
+includeShared('{ts}', 'providers-provide-1-1')
+makeExample('dependency-injection/dart/lib/providers_component.dart','providers-1')
+includeShared('{ts}', 'providers-provide-2')
+makeExample('dependency-injection/dart/lib/providers_component.dart','providers-2')
// includeShared('{ts}', 'providers-provide-3')
// includeShared('{ts}', 'providers-provide-4-1')
// Don't discuss provide function.
:marked
We supply two arguments (or more) to the `Provider` constructor.
+includeShared('{ts}', 'providers-provide-4-2')
:marked
The second is a named parameter, such as `useClass`,
that we can think of as a *recipe* for creating the dependency value.
There are many ways to create dependency values... and many ways to write a recipe.
+includeShared('{ts}', 'providers-alternative-1')
+makeExample('dependency-injection/dart/lib/providers_component.dart','providers-4')
.callout.is-helpful
header Dart difference: Constants in metadata
block dart-diff-const-metadata-ctor
.callout.is-helpful
header Dart difference: Constants in metadata
:marked
Because Dart annotations must be compile-time constants,
`useValue` is often used with string or list literals.
However, `useValue` works with any constant object.
To create a class that can provide constant objects,
ensure all its instance variables are `final`,
and give it a `const` constructor.
Create a constant instance of the class by using `const` instead of `new`.
// - var stylePattern = { otl: /(useValue.*\))/gm };
// +makeExample('dependency-injection/dart/lib/providers_component.dart','providers-9','', stylePattern)(format='.')
block non-class-dep-eg
span string, list, map, or maybe a function.
block config-obj-maps
| . They can be
| <b><a href="https://api.dartlang.org/stable/dart-core/Map-class.html">Map</a></b>
| literals
block what-should-we-use-as-token
:marked
In Dart, the value of a metadata annotation must be a compile-time constant.
For that reason, we can't call functions to get values
to use within an annotation.
Instead, we use constant literals or constant constructors.
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=".")
But what should we use as the token?
While we _could_ use **[Map][]**, we _should not_ because (like
`String`) `Map` is too general. Our app might depend on several maps, each
for a different purpose.
+includeShared('{ts}', 'providers-value-1')
: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`.
[Map]: https://api.dartlang.org/stable/dart-core/Map-class.html
Because Dart annotations must be compile-time constants,
`useValue` is often used with string or list literals.
However, `useValue` works with any constant object.
.callout.is-helpful
header Dart difference: Interfaces are valid tokens
: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,
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
block dart-map-alternative
:marked
In TypeScript, interfaces don't work as provider tokens.
Dart doesn't have this problem;
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=".")
As an alternative to using a configuration `Map`, we can define
a custom configuration class:
+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')
+makeExample('dependency-injection/dart/lib/injector_component.dart', 'injector', 'lib/injector_component.dart')
+includeShared('{ts}', 'appendix-explicit-injector-2')
If we use cascades, the configuration object can't be declared `const` and
we can't use a [value provider](#value-provider).
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