docs(dependency-injection): add Dart version, tweak TS version
closes #972
This commit is contained in:
parent
e17821492e
commit
10877bdbfc
|
@ -0,0 +1,16 @@
|
|||
# Supported lint rules and documentation: http://dart-lang.github.io/linter/lints/
|
||||
linter:
|
||||
rules:
|
||||
- always_declare_return_types
|
||||
- camel_case_types
|
||||
- empty_constructor_bodies
|
||||
- annotate_overrides
|
||||
- avoid_init_to_null
|
||||
- constant_identifier_names
|
||||
- one_member_abstracts
|
||||
- slash_for_doc_comments
|
||||
- sort_constructors_first
|
||||
- unnecessary_brace_in_string_interp
|
||||
|
||||
analyzer:
|
||||
# strong-mode: true
|
|
@ -1,15 +1,14 @@
|
|||
// #docplaster
|
||||
|
||||
// #docregion
|
||||
|
||||
// #docregion imports
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
import 'app_config.dart';
|
||||
import 'car/car_component.dart';
|
||||
import 'heroes/heroes_component.dart';
|
||||
import 'app_config.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 'providers_component.dart';
|
||||
|
@ -24,26 +23,32 @@ import 'providers_component.dart';
|
|||
<h2>User</h2>
|
||||
<p id="user">
|
||||
{{userInfo}}
|
||||
<button (click)=\'nextUser()\'>Next User</button>
|
||||
<button (click)="nextUser()">Next User</button>
|
||||
<p>
|
||||
<my-heroes id="authorized" *ngIf="isAuthorized"></my-heroes>
|
||||
<my-heroes id="unauthorized" *ngIf="!isAuthorized"></my-heroes>
|
||||
''',
|
||||
directives: const [CarComponent, HeroesComponent, InjectorComponent, ProvidersComponent],
|
||||
<my-heroes id="unauthorized" *ngIf="!isAuthorized"></my-heroes>''',
|
||||
directives: const [
|
||||
CarComponent,
|
||||
HeroesComponent,
|
||||
InjectorComponent,
|
||||
ProvidersComponent
|
||||
],
|
||||
// #docregion providers
|
||||
providers: const [Logger, UserService, const Provider(Config, useValue: CONFIG)]
|
||||
providers: const [
|
||||
Logger,
|
||||
UserService,
|
||||
const Provider(AppConfig, useValue: config1)]
|
||||
// #enddocregion providers
|
||||
)
|
||||
)
|
||||
class AppComponent {
|
||||
UserService _userService;
|
||||
String title;
|
||||
final UserService _userService;
|
||||
final String title;
|
||||
|
||||
//#docregion ctor
|
||||
AppComponent(Config config, this._userService) {
|
||||
title = config.title;
|
||||
}
|
||||
|
||||
AppComponent(AppConfig config, this._userService)
|
||||
: title = config.title;
|
||||
// #enddocregion ctor
|
||||
|
||||
bool get isAuthorized {
|
||||
return user.isAuthorized;
|
||||
}
|
||||
|
@ -56,8 +61,7 @@ class AppComponent {
|
|||
return _userService.user;
|
||||
}
|
||||
|
||||
String get userInfo {
|
||||
return 'Current user, ${user.name}, is ${isAuthorized ? "" : "not"} authorized. ';
|
||||
}
|
||||
String get userInfo => 'Current user, ${user.name}, is'
|
||||
'${isAuthorized ? "" : " not"} authorized. ';
|
||||
}
|
||||
// #enddocregion
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
// #docregion
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
import 'car/car_component.dart';
|
||||
import 'heroes/heroes_component_1.dart';
|
||||
|
||||
|
@ -10,11 +11,10 @@ import 'heroes/heroes_component_1.dart';
|
|||
template: '''
|
||||
<h1>{{title}}</h1>
|
||||
<my-car></my-car>
|
||||
<my-heroes></my-heroes>
|
||||
''',
|
||||
<my-heroes></my-heroes>''',
|
||||
directives: const [CarComponent, HeroesComponent])
|
||||
class AppComponent {
|
||||
var title = 'Dependency Injection';
|
||||
final String title = 'Dependency Injection';
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
|
|
|
@ -2,34 +2,32 @@
|
|||
|
||||
// #docregion imports
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
import 'app_config.dart';
|
||||
import 'car/car_component.dart';
|
||||
import 'heroes/heroes_component_1.dart';
|
||||
import 'app_config.dart';
|
||||
import 'logger_service.dart';
|
||||
|
||||
// #enddocregion imports
|
||||
|
||||
@Component(
|
||||
selector: 'my-app',
|
||||
template: '''
|
||||
<h1>{{title}}</h1>
|
||||
<my-car></my-car>
|
||||
<my-heroes></my-heroes>
|
||||
''',
|
||||
<my-heroes></my-heroes>''',
|
||||
directives: const [
|
||||
CarComponent,
|
||||
HeroesComponent
|
||||
],
|
||||
providers: const [
|
||||
Logger,
|
||||
// #docregion provider-config
|
||||
const Provider('app.config', useValue: CONFIG)
|
||||
const Provider(AppConfig, useValue: config1)
|
||||
])
|
||||
class AppComponent {
|
||||
String title;
|
||||
final String title;
|
||||
|
||||
// #docregion ctor
|
||||
AppComponent(@Inject('app.config') Config config) {
|
||||
title = config.title;
|
||||
}
|
||||
AppComponent(AppConfig config)
|
||||
: title = config.title;
|
||||
// #enddocregion
|
||||
}
|
||||
// #enddocregion
|
||||
|
|
|
@ -1,29 +1,17 @@
|
|||
//#docregion
|
||||
|
||||
// #docregion
|
||||
// #docregion token
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
const APP_CONFIG = const OpaqueToken('app.config');
|
||||
// #enddocregion token
|
||||
|
||||
//#docregion config
|
||||
abstract class Config {
|
||||
final String apiEndpoint;
|
||||
//#docregion const-class
|
||||
@Injectable()
|
||||
class AppConfig {
|
||||
final apiEndpoint;
|
||||
final String title;
|
||||
|
||||
const Config({this.apiEndpoint, this.title});
|
||||
const AppConfig(this.apiEndpoint, this.title);
|
||||
}
|
||||
//#enddocregion const-class
|
||||
|
||||
class ConfigImpl implements Config {
|
||||
final String apiEndpoint;
|
||||
final String title;
|
||||
|
||||
const ConfigImpl({this.apiEndpoint, this.title});
|
||||
}
|
||||
|
||||
const CONFIG = const ConfigImpl(apiEndpoint: 'api.heroes.com', title: 'Dependency Injection');
|
||||
//#enddocregion config
|
||||
|
||||
//#docregion config-hash
|
||||
const CONFIG_HASH = const {'apiEndpoint': 'api.heroes.com', 'title': 'Dependency Injection'};
|
||||
//#enddocregion config-hash
|
||||
//#docregion const-object
|
||||
const config1 = const AppConfig('api.heroes.com', 'Dependency Injection');
|
||||
//#enddocregion const-object
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
// #docregion
|
||||
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
@Injectable()
|
||||
// #docregion engine
|
||||
class Engine {
|
||||
int cylinders = 4;
|
||||
final int cylinders = 4;
|
||||
}
|
||||
// #enddocregion engine
|
||||
|
||||
@Injectable()
|
||||
// #docregion tires
|
||||
class Tires {
|
||||
String make = 'Flintstone';
|
||||
|
@ -18,8 +19,8 @@ class Tires {
|
|||
@Injectable()
|
||||
class Car {
|
||||
//#docregion car-ctor
|
||||
Engine engine;
|
||||
Tires tires;
|
||||
final Engine engine;
|
||||
final Tires tires;
|
||||
String description = 'DI';
|
||||
|
||||
Car(this.engine, this.tires);
|
||||
|
@ -27,6 +28,7 @@ class Car {
|
|||
// #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
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
// #docregion
|
||||
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
import 'car.dart';
|
||||
import 'car_no_di.dart' as carNoDi;
|
||||
import 'car_factory.dart';
|
||||
import 'car_creations.dart' as carCreations;
|
||||
import 'car_factory.dart';
|
||||
import 'car_injector.dart';
|
||||
import 'car_no_di.dart' as carNoDi;
|
||||
|
||||
@Component(
|
||||
selector: 'my-car',
|
||||
|
@ -17,13 +17,13 @@ import 'car_injector.dart';
|
|||
<div id="factory">{{factoryCar.drive()}}</div>
|
||||
<div id="simple">{{simpleCar.drive()}}</div>
|
||||
<div id="super">{{superCar.drive()}}</div>
|
||||
<div id="test">{{testCar.drive()}}</div>
|
||||
''',
|
||||
<div id="test">{{testCar.drive()}}</div>''',
|
||||
providers: const [Car, Engine, Tires])
|
||||
class CarComponent {
|
||||
Car car;
|
||||
final Car car;
|
||||
|
||||
CarComponent(this.car);
|
||||
|
||||
CarComponent(this.car) {}
|
||||
Car factoryCar = (new CarFactory()).createCar();
|
||||
Car injectorCar = useInjector();
|
||||
carNoDi.Car noDiCar = new carNoDi.Car();
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
// Examples with car and engine variations
|
||||
|
||||
// #docplaster
|
||||
// Examples with car and engine variations
|
||||
import 'car.dart';
|
||||
|
||||
///////// example 1 ////////////
|
||||
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
|
||||
|
@ -17,19 +15,18 @@ Car simpleCar() {
|
|||
|
||||
//#docregion car-ctor-instantiation-with-param
|
||||
class Engine2 implements Engine {
|
||||
int cylinders;
|
||||
final int cylinders;
|
||||
|
||||
Engine2(this.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 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;
|
||||
}
|
||||
|
@ -37,7 +34,7 @@ Car superCar() {
|
|||
|
||||
//#docregion car-ctor-instantiation-with-mocks
|
||||
class MockEngine extends Engine {
|
||||
int cylinders = 8;
|
||||
final int cylinders = 8;
|
||||
}
|
||||
|
||||
class MockTires extends Tires {
|
||||
|
@ -46,11 +43,10 @@ class MockTires extends Tires {
|
|||
|
||||
//#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
|
||||
//#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;
|
||||
}
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
// #docregion
|
||||
|
||||
import 'car.dart';
|
||||
|
||||
// BAD pattern!
|
||||
class CarFactory {
|
||||
Car createCar() {
|
||||
var car = new Car(createEngine(), createTires());
|
||||
car.description = 'Factory';
|
||||
return car;
|
||||
return new Car(createEngine(), createTires())
|
||||
..description = 'Factory';
|
||||
}
|
||||
|
||||
Engine createEngine() => new Engine();
|
||||
|
||||
Tires createTires() => new Tires();
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// #docplaster
|
||||
|
||||
//#docregion
|
||||
import 'package:angular2/core.dart';
|
||||
import 'car.dart';
|
||||
|
||||
import '../logger_service.dart';
|
||||
import 'car.dart';
|
||||
|
||||
//#docregion injector
|
||||
Car useInjector() {
|
||||
|
@ -24,8 +24,8 @@ Car useInjector() {
|
|||
//#docregion injector-call
|
||||
var car = injector.get(Car);
|
||||
//#enddocregion injector-call
|
||||
|
||||
//#enddocregion injector-create-and-call
|
||||
|
||||
car.description = 'Injector';
|
||||
var logger = injector.get(Logger);
|
||||
logger.log('Injector car.drive() said: ' + car.drive());
|
||||
|
|
|
@ -13,10 +13,10 @@ class Car {
|
|||
engine = new Engine();
|
||||
tires = new Tires();
|
||||
}
|
||||
|
||||
//#enddocregion car-ctor
|
||||
|
||||
// Method using the engine and tires
|
||||
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
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// #docregion
|
||||
|
||||
class Hero {
|
||||
num id;
|
||||
String name;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// #docregion
|
||||
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
import 'hero.dart';
|
||||
import 'hero_service.dart';
|
||||
|
||||
|
@ -9,11 +9,10 @@ import 'hero_service.dart';
|
|||
template: '''
|
||||
<div *ngFor="#hero of heroes">
|
||||
{{hero.id}} - {{hero.name}}
|
||||
({{hero.isSecret ? \'secret\' : \'public\'}})
|
||||
</div>
|
||||
''')
|
||||
({{hero.isSecret ? 'secret' : 'public'}})
|
||||
</div>''')
|
||||
class HeroListComponent {
|
||||
List<Hero> heroes;
|
||||
final List<Hero> heroes;
|
||||
|
||||
//#docregion ctor-signature
|
||||
HeroListComponent(HeroService heroService) : heroes = heroService.getHeroes();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// #docregion
|
||||
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
import 'hero.dart';
|
||||
import 'mock_heroes.dart';
|
||||
|
||||
|
@ -9,8 +9,7 @@ import 'mock_heroes.dart';
|
|||
template: '''
|
||||
<div *ngFor="#hero of heroes">
|
||||
{{hero.id}} - {{hero.name}}
|
||||
</div>
|
||||
''')
|
||||
</div>''')
|
||||
class HeroListComponent {
|
||||
List<Hero> heroes = HEROES;
|
||||
final List<Hero> heroes = HEROES;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// #docregion
|
||||
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
import 'hero.dart';
|
||||
import 'hero_service.dart';
|
||||
|
||||
|
@ -9,12 +9,12 @@ import 'hero_service.dart';
|
|||
template: '''
|
||||
<div *ngFor="#hero of heroes">
|
||||
{{hero.id}} - {{hero.name}}
|
||||
</div>
|
||||
''')
|
||||
</div>''')
|
||||
class HeroListComponent {
|
||||
List<Hero> heroes;
|
||||
final List<Hero> heroes;
|
||||
|
||||
//#docregion ctor
|
||||
HeroListComponent(HeroService heroService) : heroes = heroService.getHeroes();
|
||||
HeroListComponent(HeroService heroService)
|
||||
: heroes = heroService.getHeroes();
|
||||
//#enddocregion ctor
|
||||
}
|
||||
|
|
|
@ -1,22 +1,24 @@
|
|||
// #docregion
|
||||
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
import '../logger_service.dart';
|
||||
import 'hero.dart';
|
||||
import 'mock_heroes.dart';
|
||||
import '../logger_service.dart';
|
||||
|
||||
@Injectable()
|
||||
class HeroService {
|
||||
// #docregion internals
|
||||
Logger _logger;
|
||||
bool _isAuthorized;
|
||||
final Logger _logger;
|
||||
final bool _isAuthorized;
|
||||
|
||||
HeroService(this._logger, this._isAuthorized);
|
||||
|
||||
List<Hero> getHeroes() {
|
||||
var auth = _isAuthorized ? 'authorized' : 'unauthorized';
|
||||
_logger.log('Getting heroes for ${auth} user.');
|
||||
return HEROES.where((hero) => _isAuthorized || !hero.isSecret).toList();
|
||||
_logger.log('Getting heroes for $auth user.');
|
||||
return HEROES
|
||||
.where((hero) => _isAuthorized || !hero.isSecret)
|
||||
.toList();
|
||||
}
|
||||
// #enddocregion internals
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// #docregion
|
||||
|
||||
import 'hero.dart';
|
||||
import 'mock_heroes.dart';
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
// #docregion
|
||||
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
import '../logger_service.dart';
|
||||
import 'hero.dart';
|
||||
import 'mock_heroes.dart';
|
||||
import '../logger_service.dart';
|
||||
|
||||
@Injectable()
|
||||
class HeroService {
|
||||
Logger _logger;
|
||||
final Logger _logger;
|
||||
|
||||
//#docregion ctor
|
||||
HeroService(this._logger);
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
// #docregion
|
||||
|
||||
import 'package:angular2/core.dart';
|
||||
import 'hero_service.dart';
|
||||
|
||||
import '../logger_service.dart';
|
||||
import '../user_service.dart';
|
||||
import 'hero_service.dart';
|
||||
|
||||
// #docregion factory
|
||||
@Injectable()
|
||||
heroServiceFactory(Logger logger, UserService userService) =>
|
||||
new HeroService(logger, userService.user.isAuthorized);
|
||||
// #enddocregion factory
|
||||
|
||||
// #docregion provider
|
||||
const heroServiceProvider =
|
||||
const Provider(HeroService, useFactory: heroServiceFactory, deps: const [Logger, UserService]);
|
||||
const heroServiceProvider = const Provider(HeroService,
|
||||
useFactory: heroServiceFactory,
|
||||
deps: const [Logger, UserService]);
|
||||
// #enddocregion provider
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// #docregion
|
||||
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
import 'hero_list_component.dart';
|
||||
import 'hero_service_provider.dart';
|
||||
|
||||
|
@ -8,8 +8,7 @@ import 'hero_service_provider.dart';
|
|||
selector: 'my-heroes',
|
||||
template: '''
|
||||
<h2>Heroes</h2>
|
||||
<hero-list></hero-list>
|
||||
''',
|
||||
<hero-list></hero-list>''',
|
||||
providers: const [heroServiceProvider],
|
||||
directives: const [HeroListComponent])
|
||||
class HeroesComponent {}
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
// #docplaster
|
||||
|
||||
// #docregion
|
||||
// #docregion v1
|
||||
import 'package:angular2/core.dart';
|
||||
import 'hero_list_component.dart';
|
||||
|
||||
import 'hero_list_component.dart';
|
||||
// #enddocregion v1
|
||||
import 'hero_service.dart';
|
||||
|
||||
// #docregion v1
|
||||
|
||||
@Component(
|
||||
selector: 'my-heroes',
|
||||
template: '''
|
||||
<h2>Heroes</h2>
|
||||
<hero-list></hero-list>
|
||||
''',
|
||||
<hero-list></hero-list>''',
|
||||
// #enddocregion v1
|
||||
// #docregion providers
|
||||
providers: const [HeroService],
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// #docregion
|
||||
|
||||
import 'hero.dart';
|
||||
|
||||
List<Hero> HEROES = [
|
||||
|
|
|
@ -2,21 +2,21 @@
|
|||
|
||||
//#docregion
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
import 'car/car.dart';
|
||||
import 'heroes/hero.dart';
|
||||
import 'heroes/hero_service.dart';
|
||||
import 'heroes/hero_service_provider.dart';
|
||||
import 'logger_service.dart';
|
||||
import 'package:dependency_injection/heroes/hero.dart';
|
||||
|
||||
//#docregion injector
|
||||
@Component(
|
||||
selector: 'my-injectors',
|
||||
template: '''
|
||||
<h2>Other Injections</h2>
|
||||
<div id="car"> {{car.drive()}}</div>
|
||||
<div id="car">{{car.drive()}}</div>
|
||||
<div id="hero">{{hero.name}}</div>
|
||||
<div id="rodent">{{rodent}}</div>
|
||||
''',
|
||||
<div id="rodent">{{rodent}}</div>''',
|
||||
providers: const [
|
||||
Car,
|
||||
Engine,
|
||||
|
@ -25,20 +25,9 @@ import 'package:dependency_injection/heroes/hero.dart';
|
|||
Logger
|
||||
])
|
||||
class InjectorComponent {
|
||||
Injector _injector;
|
||||
|
||||
InjectorComponent(this._injector) {
|
||||
car = _injector.get(Car);
|
||||
heroService = _injector.get(HeroService);
|
||||
hero = heroService.getHeroes()[0];
|
||||
}
|
||||
|
||||
final Injector _injector;
|
||||
Car car;
|
||||
|
||||
//#docregion get-hero-service
|
||||
HeroService heroService;
|
||||
|
||||
//#enddocregion get-hero-service
|
||||
Hero hero;
|
||||
|
||||
String get rodent {
|
||||
|
@ -48,6 +37,14 @@ class InjectorComponent {
|
|||
}
|
||||
return "R.O.U.S.'s? I don't think they exist!";
|
||||
}
|
||||
|
||||
InjectorComponent(this._injector) {
|
||||
car = _injector.get(Car);
|
||||
//#docregion get-hero-service
|
||||
heroService = _injector.get(HeroService);
|
||||
//#enddocregion get-hero-service
|
||||
hero = heroService.getHeroes()[0];
|
||||
}
|
||||
}
|
||||
//#enddocregion injector
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// #docregion
|
||||
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
@Injectable()
|
||||
|
|
|
@ -2,22 +2,19 @@
|
|||
|
||||
//#docplaster
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
import 'app_config.dart';
|
||||
import 'heroes/hero_service_provider.dart';
|
||||
import 'heroes/hero_service.dart';
|
||||
import 'logger_service.dart';
|
||||
import 'user_service.dart';
|
||||
|
||||
// #docregion import-optional
|
||||
import 'package:angular2/core.dart' show Optional;
|
||||
// #enddocregion import-optional
|
||||
|
||||
const template = '{{log}}';
|
||||
|
||||
//////////////////////////////////////////
|
||||
@Component(selector: 'provider-1', template: '{{log}}', providers:
|
||||
@Component(
|
||||
selector: 'provider-1',
|
||||
template: '{{log}}',
|
||||
providers:
|
||||
//#docregion providers-1
|
||||
const [Logger]
|
||||
const [Logger]
|
||||
//#enddocregion providers-1
|
||||
)
|
||||
class ProviderComponent1 {
|
||||
|
@ -25,14 +22,16 @@ class ProviderComponent1 {
|
|||
|
||||
ProviderComponent1(Logger logger) {
|
||||
logger.log('Hello from logger provided with Logger class');
|
||||
log = logger.logs[0];
|
||||
log = logger.logs.last;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
@Component(selector: 'provider-2', template: '{{log}}', providers:
|
||||
@Component(
|
||||
selector: 'provider-2',
|
||||
template: '{{log}}',
|
||||
providers:
|
||||
//#docregion providers-2
|
||||
const [const Provider(Logger, useClass: Logger)]
|
||||
const [const Provider(Logger, useClass: Logger)]
|
||||
//#enddocregion providers-2
|
||||
)
|
||||
class ProviderComponent2 {
|
||||
|
@ -40,36 +39,32 @@ class ProviderComponent2 {
|
|||
|
||||
ProviderComponent2(Logger logger) {
|
||||
logger.log('Hello from logger provided with Provider class and useClass');
|
||||
log = logger.logs[0];
|
||||
log = logger.logs.last;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
@Component(
|
||||
selector: 'provider-3',
|
||||
template: '{{log}}',
|
||||
providers: const [const Provider(Logger, useClass: Logger)]
|
||||
/*
|
||||
//#docregion providers-3
|
||||
const [provide(Logger, useClass: Logger)]
|
||||
//#enddocregion providers-3
|
||||
*/
|
||||
)
|
||||
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[0];
|
||||
log = logger.logs.last;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
@Injectable()
|
||||
class BetterLogger extends Logger {}
|
||||
|
||||
@Component(selector: 'provider-4', template: '{{log}}', providers:
|
||||
@Component(
|
||||
selector: 'provider-4',
|
||||
template: '{{log}}',
|
||||
providers:
|
||||
//#docregion providers-4
|
||||
const [const Provider(Logger, useClass: BetterLogger)]
|
||||
const [const Provider(Logger, useClass: BetterLogger)]
|
||||
//#enddocregion providers-4
|
||||
)
|
||||
class ProviderComponent4 {
|
||||
|
@ -77,31 +72,32 @@ class ProviderComponent4 {
|
|||
|
||||
ProviderComponent4(Logger logger) {
|
||||
logger.log('Hello from logger provided with useClass:BetterLogger');
|
||||
log = logger.logs[0];
|
||||
log = logger.logs.last;
|
||||
}
|
||||
}
|
||||
//////////////////////////////////////////
|
||||
|
||||
// #docregion EvenBetterLogger
|
||||
@Injectable()
|
||||
class EvenBetterLogger implements Logger {
|
||||
UserService _userService;
|
||||
|
||||
List<String> logs = [];
|
||||
final UserService _userService;
|
||||
@override List<String> logs = [];
|
||||
|
||||
EvenBetterLogger(this._userService);
|
||||
|
||||
log(String message) {
|
||||
message = 'Message to ${ _userService.user.name}: ${ message}.';
|
||||
print(message);
|
||||
logs.add(message);
|
||||
@override void log(String message) {
|
||||
var msg = 'Message to ${_userService.user.name}: $message.';
|
||||
print(msg);
|
||||
logs.add(msg);
|
||||
}
|
||||
}
|
||||
|
||||
// #enddocregion EvenBetterLogger
|
||||
@Component(selector: 'provider-5', template: '{{log}}', providers:
|
||||
@Component(
|
||||
selector: 'provider-5',
|
||||
template: '{{log}}',
|
||||
providers:
|
||||
//#docregion providers-5
|
||||
const [UserService, const Provider(Logger, useClass: EvenBetterLogger)]
|
||||
const [UserService, const Provider(Logger, useClass: EvenBetterLogger)]
|
||||
//#enddocregion providers-5
|
||||
)
|
||||
class ProviderComponent5 {
|
||||
|
@ -109,29 +105,30 @@ class ProviderComponent5 {
|
|||
|
||||
ProviderComponent5(Logger logger) {
|
||||
logger.log('Hello from EvenBetterlogger');
|
||||
log = logger.logs[0];
|
||||
log = logger.logs.last;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
@Injectable()
|
||||
class NewLogger extends Logger implements OldLogger {}
|
||||
|
||||
class OldLogger {
|
||||
List<String> logs = [];
|
||||
|
||||
log(String message) {
|
||||
void log(String message) {
|
||||
throw new Exception('Should not call the old logger!');
|
||||
}
|
||||
}
|
||||
|
||||
@Component(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
|
||||
@Component(
|
||||
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
|
||||
)
|
||||
class ProviderComponent6a {
|
||||
String log;
|
||||
|
@ -144,18 +141,22 @@ class ProviderComponent6a {
|
|||
// 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 == null || newLogger.logs.isEmpty
|
||||
? oldLogger.logs[0]
|
||||
: newLogger.logs[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Component(selector: 'provider-6b', template: '{{log}}', providers:
|
||||
//#docregion providers-6b
|
||||
const [
|
||||
NewLogger,
|
||||
// Alias OldLogger w/ reference to NewLogger
|
||||
const Provider(OldLogger, useExisting: NewLogger)
|
||||
//#enddocregion providers-6b
|
||||
])
|
||||
@Component(
|
||||
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
|
||||
)
|
||||
class ProviderComponent6b {
|
||||
String log;
|
||||
|
||||
|
@ -167,43 +168,46 @@ class ProviderComponent6b {
|
|||
log = newLogger.logs[0];
|
||||
}
|
||||
}
|
||||
//////////////////////////////////////////
|
||||
|
||||
// #docregion silent-logger
|
||||
// #docregion opaque-token
|
||||
const loggerPrefix = const OpaqueToken('Logger prefix');
|
||||
// #enddocregion opaque-token
|
||||
|
||||
// An object in the shape of the logger service
|
||||
class SilentLogger /*implements Logger*/ {
|
||||
const SilentLogger({this.logs});
|
||||
// #docregion configurable-logger
|
||||
@Injectable()
|
||||
class ConfigurableLogger extends Logger {
|
||||
final String _prefix;
|
||||
|
||||
final List<String> logs;
|
||||
// #docregion use-opaque-token
|
||||
ConfigurableLogger(@Inject(loggerPrefix) this._prefix);
|
||||
// #enddocregion use-opaque-token
|
||||
|
||||
log(String message) {}
|
||||
@override
|
||||
void log(String msg) {
|
||||
super.log('$_prefix: $msg');
|
||||
}
|
||||
}
|
||||
// #enddocregion configurable-logger
|
||||
|
||||
const silentLogger = const SilentLogger(
|
||||
logs: const ['Silent logger says "Shhhhh!". Provided via "useValue"']);
|
||||
// #enddocregion silent-logger
|
||||
|
||||
@Component(selector: 'provider-7', template: '{{log}}', providers:
|
||||
@Component(selector: 'provider-7', template: '{{message}}',
|
||||
//#docregion providers-7
|
||||
const [const Provider(SilentLogger, useValue: silentLogger)]
|
||||
providers: const [
|
||||
const Provider(Logger, useClass: ConfigurableLogger),
|
||||
//#docregion providers-usevalue
|
||||
const Provider(loggerPrefix, useValue: 'Testing')
|
||||
//#enddocregion providers-usevalue
|
||||
]
|
||||
//#enddocregion providers-7
|
||||
/*
|
||||
//#docregion providers-7-unchecked
|
||||
const [const Provider(Logger, useValue: silentLogger)]
|
||||
//#enddocregion providers-7-unchecked
|
||||
*/
|
||||
)
|
||||
class ProviderComponent7 {
|
||||
String log;
|
||||
String message;
|
||||
|
||||
ProviderComponent7(SilentLogger logger) {
|
||||
logger.log('Hello from logger provided with useValue');
|
||||
log = logger.logs[0];
|
||||
ProviderComponent7(Logger logger) {
|
||||
logger.log('Hello from configurable logger.');
|
||||
message = logger.logs.last;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////
|
||||
@Component(selector: 'provider-8', template: '{{log}}', providers: const [
|
||||
const Provider(HeroService, useFactory: heroServiceFactory),
|
||||
Logger,
|
||||
|
@ -219,111 +223,86 @@ class ProviderComponent8 {
|
|||
var log = 'Hero service injected successfully';
|
||||
}
|
||||
|
||||
/////////////////
|
||||
@Component(selector: 'provider-9a', template: '{{log}}', providers:
|
||||
/*
|
||||
// #docregion providers-9a-interface
|
||||
// WOKRKS! Can use abstract class as provider token
|
||||
[provide(Config, {useValue: CONFIG})]
|
||||
// #enddocregion providers-9a-interface
|
||||
*/
|
||||
|
||||
// #docregion providers-9a
|
||||
// Use string as provider token
|
||||
const [const Provider('app.config', useValue: CONFIG_HASH)]
|
||||
//#enddocregion providers-9a
|
||||
@Component(
|
||||
selector: 'provider-9',
|
||||
template: '{{log}}',
|
||||
providers:
|
||||
// #docregion providers-9
|
||||
const [const Provider(AppConfig, useValue: config1)]
|
||||
// #enddocregion providers-9
|
||||
)
|
||||
class ProviderComponent9a implements OnInit {
|
||||
Config _config;
|
||||
|
||||
class ProviderComponent9 implements OnInit {
|
||||
AppConfig _config;
|
||||
String log;
|
||||
|
||||
/*
|
||||
// #docregion provider-9a-ctor-interface
|
||||
// WORKS! Can inject using the abstract class as the parameter type
|
||||
Config _config;
|
||||
ProviderComponent9(AppConfig this._config);
|
||||
|
||||
ProviderComponent9a(this._config);
|
||||
// #enddocregion provider-9a-ctor-interface
|
||||
*/
|
||||
|
||||
// #docregion provider-9a-ctor
|
||||
|
||||
// @Inject(token) to inject the dependency
|
||||
ProviderComponent9a(@Inject('app.config') Map config) {
|
||||
_config = new ConfigImpl(apiEndpoint: config['apiEndpoint'], title: config['title']);
|
||||
}
|
||||
|
||||
// #enddocregion provider-9a-ctor
|
||||
ngOnInit() {
|
||||
log = '\'app.config\' Application title is ' + _config.title;
|
||||
@override
|
||||
void ngOnInit() {
|
||||
log = 'appConfigToken Application title is ${_config.title}';
|
||||
}
|
||||
}
|
||||
|
||||
@Component(selector: 'provider-9b', template: '{{log}}', providers:
|
||||
// #docregion providers-9b
|
||||
const [const Provider(APP_CONFIG, useValue: CONFIG_HASH)]) // #enddocregion providers-9b
|
||||
class ProviderComponent9b
|
||||
implements OnInit {
|
||||
Config _config;
|
||||
|
||||
String log;
|
||||
|
||||
// #docregion provider-9b-ctor
|
||||
ProviderComponent9b(@Inject(APP_CONFIG) Map config) {
|
||||
_config = new ConfigImpl(apiEndpoint: config['apiEndpoint'], title: config['title']);
|
||||
}
|
||||
|
||||
// #enddocregion provider-9b-ctor
|
||||
ngOnInit() {
|
||||
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[0];
|
||||
log = logger.logs.last;
|
||||
}
|
||||
}
|
||||
|
||||
// Optional logger
|
||||
// Optional logger, can be null
|
||||
@Component(selector: 'provider-10b', template: '{{log}}')
|
||||
class ProviderComponent10b implements OnInit {
|
||||
Logger _logger;
|
||||
|
||||
class ProviderComponent10b {
|
||||
// #docregion provider-10-ctor
|
||||
final Logger _logger;
|
||||
String log;
|
||||
|
||||
ProviderComponent10b(@Optional() this._logger);
|
||||
|
||||
ProviderComponent10b(@Optional() Logger this._logger) {
|
||||
// . . .
|
||||
// #enddocregion provider-10-ctor
|
||||
ngOnInit() {
|
||||
// #docregion provider-10-logger
|
||||
|
||||
// No logger? Make one!
|
||||
if (_logger == null) {
|
||||
_logger = new Logger();
|
||||
// #enddocregion provider-10-logger
|
||||
_logger.log('Optional logger was not available.');
|
||||
} else {
|
||||
_logger.log('Hello from the injected logger.');
|
||||
log = _logger.logs[0];
|
||||
}
|
||||
log = _logger.logs[0];
|
||||
_logger == null ? log = 'No logger exists' : log = _logger.logs.last;
|
||||
// #docregion provider-10-ctor
|
||||
}
|
||||
// #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
|
||||
}
|
||||
// #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: '''
|
||||
|
@ -337,11 +316,10 @@ class ProviderComponent10b implements OnInit {
|
|||
<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="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>''',
|
||||
directives: const [
|
||||
ProviderComponent1,
|
||||
ProviderComponent2,
|
||||
|
@ -352,9 +330,9 @@ class ProviderComponent10b implements OnInit {
|
|||
ProviderComponent6b,
|
||||
ProviderComponent7,
|
||||
ProviderComponent8,
|
||||
ProviderComponent9a,
|
||||
ProviderComponent9b,
|
||||
ProviderComponent9,
|
||||
ProviderComponent10a,
|
||||
ProviderComponent10b
|
||||
ProviderComponent10b,
|
||||
ProviderComponent10c
|
||||
])
|
||||
class ProvidersComponent {}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// #docregion
|
||||
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
@Injectable()
|
||||
|
|
|
@ -5,7 +5,7 @@ version: 0.0.1
|
|||
environment:
|
||||
sdk: '>=1.13.0 <2.0.0'
|
||||
dependencies:
|
||||
angular2: 2.0.0-beta.11
|
||||
angular2: 2.0.0-beta.12
|
||||
browser: ^0.10.0
|
||||
dart_to_js_script_rewriter: ^0.1.0
|
||||
transformers:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// A simple test
|
||||
// More details will be in the testing chapter.
|
||||
import 'package:angular2/angular2.dart';
|
||||
import 'package:angular2/core.dart';
|
||||
import 'package:dependency_injection/heroes/hero.dart';
|
||||
import 'package:dependency_injection/heroes/hero_list_component.dart';
|
||||
import 'package:dependency_injection/heroes/hero_service.dart';
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
<!-- #docregion -->
|
||||
<!--#docregion-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<head>
|
||||
<title>Dependency Injection</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script type="application/dart" src="main.dart"></script>
|
||||
</head>
|
||||
<body>
|
||||
<my-app>Loading my-app ...</my-app>
|
||||
<my-providers>Loading my-providers ...</my-providers>
|
||||
</body>
|
||||
|
||||
<script defer src="main.dart" type="application/dart"></script>
|
||||
<script defer src="packages/browser/dart.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<my-app>Loading...</my-app>
|
||||
<my-providers>Loading my-providers ...</my-providers>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import 'package:angular2/platform/browser.dart';
|
||||
import 'package:dependency_injection/app_component.dart';
|
||||
import 'package:dependency_injection/heroes/hero_service.dart';
|
||||
//#docregion bootstrap
|
||||
|
||||
main() {
|
||||
// Injecting services in bootstrap works but is discouraged
|
||||
bootstrap(AppComponent, [HeroService]);
|
||||
//#enddocregion bootstrap
|
||||
//#docregion bootstrap
|
||||
bootstrap(AppComponent,
|
||||
[HeroService]); // DISCOURAGED (but works)
|
||||
//#enddocregion bootstrap
|
||||
}
|
|
@ -12,7 +12,7 @@ import {Logger} from './logger.service';
|
|||
selector: 'my-injectors',
|
||||
template: `
|
||||
<h2>Other Injections</h2>
|
||||
<div id="car"> {{car.drive()}}</div>
|
||||
<div id="car">{{car.drive()}}</div>
|
||||
<div id="hero">{{hero.name}}</div>
|
||||
<div id="rodent">{{rodent}}</div>
|
||||
`,
|
||||
|
|
|
@ -1,11 +1,279 @@
|
|||
include ../_util-fns
|
||||
|
||||
+includeShared('{ts}', 'intro')
|
||||
|
||||
:marked
|
||||
We're working on the Dart version of this chapter.
|
||||
In the meantime, please see these resources:
|
||||
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).
|
||||
|
||||
* [Dependency Injection](/docs/ts/latest/guide/dependency-injection.html):
|
||||
The TypeScript version of this chapter
|
||||
+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
|
||||
: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')
|
||||
|
||||
* [Dart source code](https://github.com/angular/angular.io/tree/master/public/docs/_examples/dependency-injection/dart):
|
||||
A preliminary version of the example code that will appear in this chapter
|
||||
+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 <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
|
||||
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
|
||||
:marked
|
||||
### Implementing a logger
|
||||
|
||||
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')
|
||||
|
||||
+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
|
||||
: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=".")
|
||||
|
||||
+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`.
|
||||
|
||||
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,
|
||||
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
|
||||
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=".")
|
||||
|
||||
|
||||
+includeShared('{ts}', 'summary')
|
||||
|
||||
+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')
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
include ../_util-fns
|
||||
|
||||
// #docregion intro
|
||||
:marked
|
||||
**Dependency injection** is an important application design pattern.
|
||||
Angular has its own dependency injection framework, and
|
||||
|
@ -7,18 +9,21 @@ include ../_util-fns
|
|||
|
||||
In this chapter we'll learn what DI is and why we want it.
|
||||
Then we'll learn [how to use it](#angular-di) in an Angular app.
|
||||
|
||||
// #enddocregion intro
|
||||
:marked
|
||||
[Run the live example](/resources/live-examples/dependency-injection/ts/plnkr.html)
|
||||
|
||||
// #docregion why-1
|
||||
<a id="why-di"></a>
|
||||
.l-main-section
|
||||
:marked
|
||||
## Why dependency injection?
|
||||
|
||||
Let's start with the following code.
|
||||
|
||||
// #enddocregion why-1
|
||||
+makeExample('dependency-injection/ts/app/car/car-no-di.ts', 'car', 'app/car/car.ts (without DI)')
|
||||
|
||||
// #docregion why-2
|
||||
- var lang = current.path[1]
|
||||
- var prefix = lang == 'dart' ? '' : 'this.'
|
||||
:marked
|
||||
Our `Car` creates everything it needs inside its constructor.
|
||||
What's the problem?
|
||||
|
@ -31,7 +36,7 @@ include ../_util-fns
|
|||
|
||||
What if the `Engine` class evolves and its constructor requires a parameter?
|
||||
Our `Car` is broken and stays broken until we rewrite it along the lines of
|
||||
`this.engine = new Engine(theNewParameter)`.
|
||||
`#{prefix}engine = new Engine(theNewParameter)`.
|
||||
We didn't care about `Engine` constructor parameters when we first wrote `Car`.
|
||||
We don't really care about them now.
|
||||
But we'll *have* to start caring because
|
||||
|
@ -58,40 +63,45 @@ include ../_util-fns
|
|||
if we can't swap in low-pressure tires during the test?
|
||||
|
||||
We have no control over the car's hidden dependencies.
|
||||
When we can't control the dependencies, a class become difficult to test.
|
||||
When we can't control the dependencies, a class becomes difficult to test.
|
||||
|
||||
How can we make `Car` more robust, flexible, and testable?
|
||||
|
||||
That's super easy. We probably already know what to do. We change our `Car` constructor to a version with DI:
|
||||
That's super easy. We change our `Car` constructor to a version with DI:
|
||||
|
||||
<a id="ctor-injection"></a>
|
||||
|
||||
// #enddocregion why-2
|
||||
+makeTabs(
|
||||
'dependency-injection/ts/app/car/car.ts, dependency-injection/ts/app/car/car-no-di.ts',
|
||||
'car-ctor, car-ctor',
|
||||
'app/car/car.ts (excerpt with DI), app/car/car.ts (excerpt without DI)')(format=".")
|
||||
|
||||
// #docregion why-3-1
|
||||
:marked
|
||||
See what happened? We moved the definition of the dependencies to the constructor.
|
||||
Our `Car` class no longer creates an engine or tires.
|
||||
It just consumes them.
|
||||
|
||||
// #enddocregion why-3-1
|
||||
// TypeScript only
|
||||
.l-sub-section
|
||||
:marked
|
||||
We also leverage TypeScript's constructor syntax for declaring parameters and properties simultaneously.
|
||||
// #docregion why-3-2
|
||||
:marked
|
||||
Now we create a car by passing the engine and tires to the constructor.
|
||||
|
||||
// #enddocregion why-3-2
|
||||
- var stylePattern = { otl: /(new Car.*$)/gm };
|
||||
+makeExample('dependency-injection/ts/app/car/car-creations.ts', 'car-ctor-instantiation', '', stylePattern)(format=".")
|
||||
|
||||
// #docregion why-4
|
||||
:marked
|
||||
How cool is that?
|
||||
The definition of the engine and tire dependencies are decoupled from the `Car` class itself.
|
||||
The definition of the engine and tire dependencies are
|
||||
decoupled from the `Car` class itself.
|
||||
We can pass in any kind of engine or tires we like, as long as they
|
||||
conform to the general API requirements of an engine or tires.
|
||||
|
||||
If someone extends the `Engine` class, that is not `Car`'s problem.
|
||||
// #enddocregion why-4
|
||||
// Must copy the following, due to indented +make.
|
||||
.l-sub-section
|
||||
:marked
|
||||
The _consumer_ of `Car` has the problem. The consumer must update the car creation code to
|
||||
|
@ -103,16 +113,16 @@ include ../_util-fns
|
|||
:marked
|
||||
The critical point is this: `Car` itself did not have to change.
|
||||
We'll take care of the consumer's problem soon enough.
|
||||
|
||||
// #docregion why-6
|
||||
:marked
|
||||
The `Car` class is much easier to test because we are in complete control
|
||||
of its dependencies.
|
||||
We can pass mocks to the constructor that do exactly what we want them to do
|
||||
during each test:
|
||||
|
||||
// #enddocregion why-6
|
||||
- var stylePattern = { otl: /(new Car.*$)/gm };
|
||||
+makeExample('dependency-injection/ts/app/car/car-creations.ts', 'car-ctor-instantiation-with-mocks', '', stylePattern)(format=".")
|
||||
|
||||
// #docregion why-7
|
||||
:marked
|
||||
**We just learned what dependency injection is**.
|
||||
|
||||
|
@ -126,9 +136,9 @@ include ../_util-fns
|
|||
We need something that takes care of assembling these parts for us.
|
||||
|
||||
We could write a giant class to do that:
|
||||
|
||||
// #enddocregion why-7
|
||||
+makeExample('dependency-injection/ts/app/car/car-factory.ts', null, 'app/car/car-factory.ts')
|
||||
|
||||
// #docregion why-8
|
||||
:marked
|
||||
It's not so bad now with only three creation methods.
|
||||
But maintaining it will be hairy as the application grows.
|
||||
|
@ -143,9 +153,9 @@ include ../_util-fns
|
|||
We register some classes with this injector, and it figures out how to create them.
|
||||
|
||||
When we need a `Car`, we simply ask the injector to get it for us and we're good to go.
|
||||
|
||||
// #enddocregion why-8
|
||||
+makeExample('dependency-injection/ts/app/car/car-injector.ts','injector-call')(format=".")
|
||||
|
||||
// #docregion why-9
|
||||
:marked
|
||||
Everyone wins. The `Car` knows nothing about creating an `Engine` or `Tires`.
|
||||
The consumer knows nothing about creating a `Car`.
|
||||
|
@ -156,7 +166,8 @@ include ../_util-fns
|
|||
|
||||
Now that we know what dependency injection is and appreciate its benefits,
|
||||
let's see how it is implemented in Angular.
|
||||
|
||||
// #enddocregion why-9
|
||||
// #docregion di-1
|
||||
<a id="angular-di"></a>
|
||||
.l-main-section
|
||||
:marked
|
||||
|
@ -170,7 +181,7 @@ include ../_util-fns
|
|||
|
||||
We'll begin with a simplified version of the `HeroesComponent`
|
||||
that we built in the [The Tour of Heroes](../tutorial/).
|
||||
|
||||
// #enddocregion di-1
|
||||
+makeTabs(
|
||||
`dependency-injection/ts/app/heroes/heroes.component.1.ts,
|
||||
dependency-injection/ts/app/heroes/hero-list.component.1.ts,
|
||||
|
@ -181,34 +192,36 @@ include ../_util-fns
|
|||
app/heroes/hero-list.component.ts,
|
||||
app/heroes/hero.ts,
|
||||
app/heroes/mock-heroes.ts`)
|
||||
|
||||
// #docregion di-2
|
||||
:marked
|
||||
The `HeroesComponent` is the root component of the *Heroes* feature area.
|
||||
It governs all the child components of this area.
|
||||
Our stripped down version has only one child, `HeroListComponent`,
|
||||
which displays a list of heroes.
|
||||
.l-sub-section
|
||||
:marked
|
||||
Do we really need so many files? Of course not!
|
||||
We're going *beyond* the strictly necessary
|
||||
in order to illustrate patterns that work well in real applications.
|
||||
// #enddocregion di-2
|
||||
// #docregion di-3
|
||||
:marked
|
||||
Right now `HeroListComponent` gets heroes from `HEROES`, an in-memory collection
|
||||
defined in another file and imported by this component.
|
||||
defined in another file.
|
||||
That may suffice in the early stages of development, but it's far from ideal.
|
||||
As soon as we try to test this component or want to get our heroes data from a remote server,
|
||||
we'll have to change the implementation of `heroes` and
|
||||
fix every other use of the `HEROES` mock data.
|
||||
|
||||
Let's make a service that hides how we get hero data.
|
||||
// #enddocregion di-3
|
||||
|
||||
// Unnecessary for Dart
|
||||
.l-sub-section
|
||||
:marked
|
||||
Write this service in its own file. See [this note](#forward-ref) to understand why.
|
||||
|
||||
+makeExample('dependency-injection/ts/app/heroes/hero.service.1.ts',null, 'app/heroes/hero.service.ts' )
|
||||
// #docregion di-4
|
||||
:marked
|
||||
Our `HeroService` exposes a `getHeroes` method that returns
|
||||
the same mock data as before, but none of its consumers need to know that.
|
||||
// #enddocregion di-4
|
||||
// #docregion di-5
|
||||
.l-sub-section
|
||||
:marked
|
||||
We aren't even pretending this is a real service.
|
||||
|
@ -217,24 +230,30 @@ include ../_util-fns
|
|||
[ES2015 promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
|
||||
We'd also have to rewrite the way components consume our service.
|
||||
This is important in general, but not to our current story.
|
||||
// #enddocregion di-5
|
||||
// #docregion di-6
|
||||
:marked
|
||||
A service is nothing more than a class in Angular 2.
|
||||
It remains nothing more than a class until we register it with an Angular injector.
|
||||
|
||||
// #enddocregion di-6
|
||||
// #docregion di-configure-injector-1
|
||||
:marked
|
||||
### Configuring the injector
|
||||
|
||||
<a id="bootstrap"></a>
|
||||
We don't have to create an Angular injector.
|
||||
Angular creates an application-wide injector for us during the bootstrap process.
|
||||
|
||||
// #enddocregion di-configure-injector-1
|
||||
+makeExample('dependency-injection/ts/app/main.ts', 'bootstrap', 'app/main.ts (excerpt)')(format='.')
|
||||
// #docregion di-configure-injector-2
|
||||
:marked
|
||||
We do have to configure the injector by registering the **providers**
|
||||
that create the services our application requires.
|
||||
We'll explain what [providers](#providers) are later in this chapter.
|
||||
Before we do, let's see an example of provider registration during bootstrapping:
|
||||
|
||||
// #enddocregion di-configure-injector-2
|
||||
+makeExample('dependency-injection/ts/app/main.1.ts', 'bootstrap')(format='.')
|
||||
// #docregion di-configure-injector-3
|
||||
:marked
|
||||
The injector now knows about our `HeroService`.
|
||||
An instance of our `HeroService` will be available for injection across our entire application.
|
||||
|
@ -247,36 +266,42 @@ include ../_util-fns
|
|||
The preferred approach is to register application providers in application components.
|
||||
Because the `HeroService` is used within the *Heroes* feature area —
|
||||
and nowhere else — the ideal place to register it is in the top-level `HeroesComponent`.
|
||||
|
||||
// #enddocregion di-configure-injector-3
|
||||
// #docregion di-register-providers-1
|
||||
:marked
|
||||
### Registering providers in a component
|
||||
Here's a revised `HeroesComponent` that registers the `HeroService`.
|
||||
|
||||
// #enddocregion di-register-providers-1
|
||||
+makeExample('dependency-injection/ts/app/heroes/heroes.component.1.ts',null,'app/heroes/heroes.component.ts')
|
||||
// #docregion di-register-providers-2
|
||||
:marked
|
||||
Look closely at the `providers` part of the `@Component` metadata:
|
||||
|
||||
// #enddocregion di-register-providers-2
|
||||
+makeExample('dependency-injection/ts/app/heroes/heroes.component.1.ts','providers')(format='.')
|
||||
// #docregion di-register-providers-3
|
||||
:marked
|
||||
An instance of the `HeroService` is now available for injection in this `HeroesComponent`
|
||||
and all of its child components.
|
||||
|
||||
The `HeroesComponent` itself doesn't happen to need the `HeroService`.
|
||||
But its child `HeroListComponent` does, so we head there next.
|
||||
|
||||
// #enddocregion di-register-providers-3
|
||||
// #docregion di-prepare-for-injection-1
|
||||
:marked
|
||||
### Preparing the HeroListComponent for injection
|
||||
|
||||
The `HeroListComponent` should get heroes from the injected `HeroService`.
|
||||
Per the dependency injection pattern, the component must ask for the service in its constructor, [as we explained
|
||||
earlier](#ctor-injection).
|
||||
It's a small change:
|
||||
// #enddocregion di-prepare-for-injection-1
|
||||
+makeTabs(
|
||||
`dependency-injection/ts/app/heroes/hero-list.component.2.ts,
|
||||
dependency-injection/ts/app/heroes/hero-list.component.1.ts`,
|
||||
null,
|
||||
`app/heroes/hero-list.component (with DI),
|
||||
app/heroes/hero-list.component (without DI)`)
|
||||
:marked
|
||||
|
||||
// Must copy the following, due to indented +make.
|
||||
.l-sub-section
|
||||
:marked
|
||||
### Focus on the constructor
|
||||
|
@ -285,6 +310,7 @@ include ../_util-fns
|
|||
|
||||
+makeExample('dependency-injection/ts/app/heroes/hero-list.component.2.ts', 'ctor')(format=".")
|
||||
|
||||
// TypeScript only
|
||||
:marked
|
||||
We're writing in TypeScript and have followed the parameter name with a type annotation, `:HeroService`.
|
||||
The class is also decorated with the `@Component` decorator (scroll up to confirm that fact).
|
||||
|
@ -295,14 +321,15 @@ include ../_util-fns
|
|||
|
||||
That's how the Angular injector knows to inject an instance of the `HeroService` when it
|
||||
creates a new `HeroListComponent`.
|
||||
<a id="di-metadata"></a>
|
||||
// #docregion di-create-injector-implicitly-1
|
||||
:marked
|
||||
<a id="di-metadata"></a>
|
||||
### Creating the injector (implicitly)
|
||||
When we introduced the idea of an injector above, we showed how to create
|
||||
an injector and use it to create a new `Car`.
|
||||
|
||||
// #enddocregion di-create-injector-implicitly-1
|
||||
+makeExample('dependency-injection/ts/app/car/car-injector.ts','injector-create-and-call')(format=".")
|
||||
|
||||
// #docregion di-create-injector-implicitly-2
|
||||
:marked
|
||||
We won't find code like that in the Tour of Heroes or any of our other samples.
|
||||
We *could* write [code with an explicit injector](#explicit-injector) if we *had* to, but we rarely do.
|
||||
|
@ -310,7 +337,9 @@ include ../_util-fns
|
|||
when it creates components for us — whether through HTML markup, as in `<hero-list></hero-list>`,
|
||||
or after navigating to a component with the [router](./router.html).
|
||||
If we let Angular do its job, we'll enjoy the benefits of automated dependency injection.
|
||||
|
||||
// #enddocregion di-create-injector-implicitly-2
|
||||
// #docregion di-singleton-services
|
||||
:marked
|
||||
### Singleton services
|
||||
Dependencies are singletons within the scope of an injector.
|
||||
In our example, a single `HeroService` instance is shared among the
|
||||
|
@ -319,19 +348,25 @@ include ../_util-fns
|
|||
However, Angular DI is an hierarchical injection
|
||||
system, which means that nested injectors can create their own service instances.
|
||||
Learn more about that in the [Hierarchical Injectors](./hierarchical-dependency-injection.html) chapter.
|
||||
// #enddocregion di-singleton-services
|
||||
|
||||
// Skip this for Dart, for now
|
||||
// #docregion di-testing-component-1
|
||||
:marked
|
||||
### Testing the component
|
||||
We emphasized earlier that designing a class for dependency injection makes the class easier to test.
|
||||
Listing dependencies as constructor parameters may be all we need to test application parts effectively.
|
||||
|
||||
For example, we can create a new `HeroListComponent` with a mock service that we can manipulate
|
||||
under test:
|
||||
|
||||
// #enddocregion di-testing-component-1
|
||||
+makeExample('dependency-injection/ts/app/test.component.ts', 'spec')(format='.')
|
||||
// #docregion di-testing-component-2
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more in [Testing](../testing/index.html).
|
||||
|
||||
// #enddocregion di-testing-component-2
|
||||
// #docregion di-service-service-1
|
||||
:marked
|
||||
### When the service needs a service
|
||||
Our `HeroService` is very simple. It doesn't have any dependencies of its own.
|
||||
|
@ -342,49 +377,61 @@ include ../_util-fns
|
|||
adding a constructor that takes a `Logger` parameter.
|
||||
|
||||
Here is the revision compared to the original.
|
||||
|
||||
// #enddocregion di-service-service-1
|
||||
+makeTabs(
|
||||
`dependency-injection/ts/app/heroes/hero.service.2.ts,
|
||||
dependency-injection/ts/app/heroes/hero.service.1.ts`,
|
||||
null,
|
||||
`app/heroes/hero.service (v.2),
|
||||
app/heroes/hero.service (v.1)`)
|
||||
|
||||
// #docregion di-service-service-2
|
||||
:marked
|
||||
The constructor now asks for an injected instance of a `Logger` and stores it in a private property called `_logger`.
|
||||
We call that property within our `getHeroes` method when anyone asks for heroes.
|
||||
|
||||
// #enddocregion di-service-service-2
|
||||
// #docregion di-injectable-1
|
||||
- var lang = current.path[1]
|
||||
- var decoration = lang == 'dart' ? 'annotation' : 'decoration'
|
||||
- var tsmetadata = lang == 'ts' ? 'As <a href="#di-metadata">we mentioned earlier</a>, <b>TypeScript only generates metadata for classes that have a decorator.</b>' : ''
|
||||
:marked
|
||||
<a id="injectable"></a>
|
||||
### Why @Injectable?
|
||||
Notice the `@Injectable()` decoration above the service class.
|
||||
Notice the `@Injectable()` #{decoration} above the service class.
|
||||
We haven't seen `@Injectable()` before.
|
||||
As it happens, we could have added it to our first version of `HeroService`.
|
||||
We didn't bother because we didn't need it then.
|
||||
|
||||
We need it now... now that our service has an injected dependency.
|
||||
We need it because Angular requires constructor parameter metadata in order to inject a `Logger`.
|
||||
As [we mentioned earlier](#di-metadata), **TypeScript only generates metadata for classes that have a decorator.**
|
||||
We need it because Angular requires constructor parameter metadata in order to inject a `Logger`. !{tsmetadata}
|
||||
// #enddocregion di-injectable-1
|
||||
|
||||
// #docregion di-injectable-2
|
||||
- var lang = current.path[1]
|
||||
- var a_decorator = lang == 'dart' ? 'an annotation' : 'a decorator'
|
||||
- var decorated = lang == 'dart' ? 'annotated' : 'decorated'
|
||||
- var any_decorator = lang == 'dart' ? '' : 'TypeScript generates metadata for any class with a decorator, and any decorator will do.'
|
||||
.callout.is-helpful
|
||||
header Always add @Injectable()
|
||||
:marked
|
||||
We recommend adding `@Injectable()` to every service class, even those that don't have dependencies
|
||||
and, therefore, do not technically require it. Here's why:
|
||||
ul(style="font-size:inherit")
|
||||
li <b>Future proofing:</b> No need to remember <code>@Injectable</code> when we add a dependency later.
|
||||
li <b>Consistency:</b> All services follow the same rules, and we don't have to wonder why a decorator is missing.
|
||||
li <b>Future proofing:</b> No need to remember <code>@Injectable()</code> when we add a dependency later.
|
||||
li <b>Consistency:</b> All services follow the same rules, and we don't have to wonder why #{a_decorator} is missing.
|
||||
.l-sub-section
|
||||
:marked
|
||||
The `HeroesComponent` has an injected dependency too. Why don't we add `@Injectable()` to the `HeroesComponent`?
|
||||
|
||||
We *can* add it if we really want to. It isn't necessary because the `HeroesComponent` is already decorated with `@Component`.
|
||||
TypeScript generates metadata for *any* class with a decorator, and *any* decorator will do.
|
||||
|
||||
.alert.is-critical
|
||||
We *can* add it if we really want to. It isn't necessary because
|
||||
the `HeroesComponent` is already #{decorated} with `@Component`. #{any_decorator}
|
||||
// #enddocregion di-injectable-2
|
||||
.callout.is-critical
|
||||
header Always include the parentheses
|
||||
:marked
|
||||
**Always include the parentheses!** Always call `@Injectable()`.
|
||||
Always use `@Injectable()`, not just `@Injectable`.
|
||||
Our application will fail mysteriously if we forget the parentheses.
|
||||
|
||||
// #docregion logger-service-1
|
||||
.l-main-section
|
||||
:marked
|
||||
## Creating and registering a logger service
|
||||
|
@ -393,18 +440,23 @@ include ../_util-fns
|
|||
1. Register it with the application.
|
||||
|
||||
The logger service implementation is no big deal.
|
||||
|
||||
// #enddocregion logger-service-1
|
||||
+makeExample(
|
||||
'dependency-injection/ts/app/logger.service.ts',null, 'app/logger.service')
|
||||
|
||||
// Copied into Dart, due to different directory structure
|
||||
:marked
|
||||
We're likely to need the same logger service everywhere in our application,
|
||||
so we put it at the root level of the application in the `app/` folder, and
|
||||
we register it in the `providers` array of the metadata for our application root component, `AppComponent`.
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-logger', 'app/app.component.ts (excerpt)')
|
||||
// #docregion logger-service-3
|
||||
:marked
|
||||
If we forget to register the logger, Angular throws an exception when it first looks for the logger:
|
||||
code-example(format, language="html").
|
||||
EXCEPTION: No provider for Logger! (HeroListComponent -> HeroService -> Logger)
|
||||
// #enddocregion logger-service-3
|
||||
// #docregion logger-service-4
|
||||
:marked
|
||||
That's Angular telling us that the dependency injector couldn't find the *provider* for the logger.
|
||||
It needed that provider to create a `Logger` to inject into a new
|
||||
|
@ -419,20 +471,32 @@ code-example(format, language="html").
|
|||
|
||||
Our `HeroService` currently requires a `Logger`. What if we could get by without a logger?
|
||||
We'd use it if we had it, ignore it if we didn't. We can do that.
|
||||
// #enddocregion logger-service-4
|
||||
|
||||
// TypeScript only?
|
||||
:marked
|
||||
First import the `@Optional()` decorator.
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','import-optional')(format='.')
|
||||
|
||||
// #docregion logger-service-5
|
||||
- var lang = current.path[1]
|
||||
- var rewrite = lang == 'dart' ? 'Just rewrite' : 'Then rewrite'
|
||||
- var decorator = lang == 'dart' ? 'annotation' : 'decorator'
|
||||
:marked
|
||||
Then rewrite the constructor with `@Optional()` decorator preceding the private `_logger` parameter.
|
||||
#{rewrite} the constructor with the `@Optional()` #{decorator} preceding the private `_logger` parameter.
|
||||
That tells the injector that `_logger` is optional.
|
||||
// #enddocregion logger-service-5
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-10-ctor')(format='.')
|
||||
// #docregion logger-service-6
|
||||
:marked
|
||||
Be prepared for a null logger. If we don't register one somewhere up the line,
|
||||
the injector will inject `null`. We have a method that logs.
|
||||
What can we do to avoid a null reference exception?
|
||||
|
||||
We could substitute a *do-nothing* logger stub so that calling methods continue to work:
|
||||
// #enddocregion logger-service-6
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-10-logger')(format='.')
|
||||
// #docregion logger-service-7
|
||||
:marked
|
||||
Obviously we'd take a more sophisticated approach if the logger were optional
|
||||
in multiple locations.
|
||||
|
@ -440,7 +504,10 @@ code-example(format, language="html").
|
|||
But enough about optional loggers. In our sample application, the `Logger` is required.
|
||||
We must register a `Logger` with the application injector using *providers*,
|
||||
as we learn in the next section.
|
||||
// #enddocregion logger-service-7
|
||||
|
||||
// #docregion providers-1
|
||||
:marked
|
||||
<a id="providers"></a>
|
||||
.l-main-section
|
||||
:marked
|
||||
|
@ -453,62 +520,94 @@ code-example(format, language="html").
|
|||
We must register a service *provider* with the injector, or it won't know how to create the service.
|
||||
|
||||
Earlier we registered the `Logger` service in the `providers` array of the metadata for the `AppComponent` like this:
|
||||
// #enddocregion providers-1
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-logger')
|
||||
// #docregion providers-2
|
||||
- var lang = current.path[1]
|
||||
- var implements = lang == 'dart' ? 'implements' : 'looks and behaves like a '
|
||||
- var objectlike = lang == 'dart' ? '' : 'an object that behaves like '
|
||||
- var loggerlike = lang == 'dart' ? '' : 'We could provide a logger-like object. '
|
||||
:marked
|
||||
The `providers` array appears to hold a service class.
|
||||
In reality it holds an instance of the [Provider](/docs/ts/latest/api/core/Provider-class.html) class that can create that service.
|
||||
|
||||
There are many ways to *provide* something that looks and behaves like a `Logger`.
|
||||
There are many ways to *provide* something that #{implements} `Logger`.
|
||||
The `Logger` class itself is an obvious and natural provider — it has the right shape and it's designed to be created.
|
||||
But it's not the only way.
|
||||
|
||||
We can configure the injector with alternative providers that can deliver an object that behaves like a `Logger`.
|
||||
We could provide a substitute class. We could provide a logger-like object.
|
||||
We can configure the injector with alternative providers that can deliver #{objectlike} a `Logger`.
|
||||
We could provide a substitute class. #{loggerlike}
|
||||
We could give it a provider that calls a logger factory function.
|
||||
Any of these approaches might be a good choice under the right circumstances.
|
||||
|
||||
What matters is that the injector has a provider to go to when it needs a `Logger`.
|
||||
|
||||
// #enddocregion providers-2
|
||||
// #docregion providers-provide-1
|
||||
:marked
|
||||
<a id="provide"></a>
|
||||
### The *provide* function
|
||||
// #enddocregion providers-provide-1
|
||||
|
||||
// Don't mention provide function in Dart
|
||||
:marked
|
||||
### The *Provider* class and *provide* function
|
||||
// #docregion providers-provide-1-1
|
||||
:marked
|
||||
We wrote the `providers` array like this:
|
||||
// #enddocregion providers-provide-1-1
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-1')
|
||||
// #docregion providers-provide-2
|
||||
:marked
|
||||
This is actually a short-hand expression for a provider registration that creates a new instance of the
|
||||
[Provider](/docs/ts/latest/api/core/Provider-class.html) class.
|
||||
// #enddocregion providers-provide-2
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-2')
|
||||
// #docregion providers-provide-3
|
||||
// Skip for Dart, where the provide() function won't pass type checking.
|
||||
:marked
|
||||
The [provide](/docs/ts/latest/api/core/provide-function.html) function is the more common, friendlier way to create a `Provider`:
|
||||
// #enddocregion providers-provide-3
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-3')
|
||||
// #docregion providers-provide-4-1
|
||||
// Modified for Dart.
|
||||
:marked
|
||||
In both approaches — `Provider` class and `provide` function —
|
||||
we supply two arguments.
|
||||
|
||||
// #enddocregion providers-provide-4-1
|
||||
// #docregion providers-provide-4-2
|
||||
:marked
|
||||
The first is the [token](#token) that serves as the key for both locating a dependency value
|
||||
and registering the provider.
|
||||
// #enddocregion providers-provide-4-2
|
||||
|
||||
// Dart is different here (uses an optional parameter)
|
||||
:marked
|
||||
The second is a provider definition object,
|
||||
which 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.
|
||||
|
||||
// #docregion providers-alternative-1
|
||||
:marked
|
||||
<a id="class-provider"></a>
|
||||
### Alternative class providers
|
||||
|
||||
Occasionally we'll ask a different class to provide the service.
|
||||
The following code tells the injector
|
||||
to return a `BetterLogger` when something asks for the `Logger`.
|
||||
|
||||
// #enddocregion providers-alternative-1
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-4')
|
||||
// #docregion providers-alternative-2
|
||||
:marked
|
||||
### Class provider with dependencies
|
||||
Maybe an `EvenBetterLogger` could display the user name in the log message.
|
||||
This logger gets the user from the injected `UserService`,
|
||||
which happens also to be injected at the application level.
|
||||
// #enddocregion providers-alternative-2
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','EvenBetterLogger')
|
||||
// #docregion providers-alternative-3
|
||||
:marked
|
||||
Configure it like we did `BetterLogger`.
|
||||
// #enddocregion providers-alternative-3
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-5')(format=".")
|
||||
// #docregion providers-aliased-1
|
||||
:marked
|
||||
### Aliased class providers
|
||||
|
||||
|
@ -525,16 +624,21 @@ code-example(format, language="html").
|
|||
|
||||
We certainly do not want two different `NewLogger` instances in our app.
|
||||
Unfortunately, that's what we get if we try to alias `OldLogger` to `NewLogger` with `useClass`.
|
||||
|
||||
// #enddocregion providers-aliased-1
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-6a')(format=".")
|
||||
// #docregion providers-aliased-2
|
||||
:marked
|
||||
The solution: Alias with the `useExisting` option.
|
||||
// #enddocregion providers-aliased-2
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-6b')(format=".")
|
||||
|
||||
// #docregion providers-value-1
|
||||
<a id="value-provider"></a>
|
||||
:marked
|
||||
### Value providers
|
||||
// #enddocregion providers-value-1
|
||||
|
||||
// Typescript only
|
||||
:marked
|
||||
Sometimes it's easier to provide a ready-made object rather than ask the injector to create it from a class.
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','silent-logger')(format=".")
|
||||
:marked
|
||||
|
@ -542,6 +646,7 @@ code-example(format, language="html").
|
|||
which makes this object play the logger role.
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-7')(format=".")
|
||||
|
||||
// #docregion providers-factory-1
|
||||
<a id="factory-provider"></a>
|
||||
:marked
|
||||
### Factory providers
|
||||
|
@ -571,20 +676,24 @@ code-example(format, language="html").
|
|||
Why? We don't know either. Stuff like this happens.
|
||||
:marked
|
||||
Instead the `HeroService` constructor takes a boolean flag to control display of secret heroes.
|
||||
// #enddocregion providers-factory-1
|
||||
+makeExample('dependency-injection/ts/app/heroes/hero.service.ts','internals', 'app/heroes/hero.service.ts (excerpt)')(format='.')
|
||||
// #docregion providers-factory-2
|
||||
:marked
|
||||
We can inject the `Logger`, but we can't inject the boolean `isAuthorized`.
|
||||
We'll have to take over the creation of new instances of this `HeroService` with a factory provider.
|
||||
|
||||
A factory provider needs a factory function:
|
||||
// #enddocregion providers-factory-2
|
||||
+makeExample('dependency-injection/ts/app/heroes/hero.service.provider.ts','factory', 'app/heroes/hero.service.provider.ts (excerpt)')(format='.')
|
||||
// #docregion providers-factory-3
|
||||
:marked
|
||||
Although the `HeroService` has no access to the `UserService`, our factory function does.
|
||||
|
||||
We inject both the `Logger` and the `UserService` into the factory provider and let the injector pass them along to the factory function:
|
||||
// #enddocregion providers-factory-3
|
||||
+makeExample('dependency-injection/ts/app/heroes/hero.service.provider.ts','provider', 'app/heroes/hero.service.provider.ts (excerpt)')(format='.')
|
||||
:marked
|
||||
|
||||
// #docregion providers-factory-4
|
||||
.l-sub-section
|
||||
:marked
|
||||
The `useFactory` field tells Angular that the provider is a factory function
|
||||
|
@ -593,6 +702,8 @@ code-example(format, language="html").
|
|||
The `deps` property is an array of [provider tokens](#token).
|
||||
The `Logger` and `UserService` classes serve as tokens for their own class providers.
|
||||
The injector resolves these tokens and injects the corresponding services into the matching factory function parameters.
|
||||
// #enddocregion providers-factory-4
|
||||
// #docregion providers-factory-5
|
||||
:marked
|
||||
Notice that we captured the factory provider in an exported variable, `heroServiceProvider`.
|
||||
This extra step makes the factory provider reusable.
|
||||
|
@ -601,6 +712,7 @@ code-example(format, language="html").
|
|||
In our sample, we need it only in the `HeroesComponent`,
|
||||
where it replaces the previous `HeroService` registration in the metadata `providers` array.
|
||||
Here we see the new and the old implementation side-by-side:
|
||||
// #enddocregion providers-factory-5
|
||||
+makeTabs(
|
||||
`dependency-injection/ts/app/heroes/heroes.component.ts,
|
||||
dependency-injection/ts/app/heroes/heroes.component.1.ts`,
|
||||
|
@ -608,6 +720,7 @@ code-example(format, language="html").
|
|||
`app/heroes/heroes.component (v.3),
|
||||
app/heroes/heroes.component (v.2)`)
|
||||
|
||||
// #docregion tokens-1
|
||||
<a id="token"></a>
|
||||
.l-main-section
|
||||
:marked
|
||||
|
@ -620,41 +733,56 @@ code-example(format, language="html").
|
|||
In all previous examples, the dependency value has been a class *instance*, and
|
||||
the class *type* served as its own lookup key.
|
||||
Here we get a `HeroService` directly from the injector by supplying the `HeroService` type as the token:
|
||||
// #enddocregion tokens-1
|
||||
+makeExample('dependency-injection/ts/app/injector.component.ts','get-hero-service')(format='.')
|
||||
// #docregion tokens-2
|
||||
:marked
|
||||
We have similar good fortune when we write a constructor that requires an injected class-based dependency.
|
||||
We define a constructor parameter with the `HeroService` class type,
|
||||
and Angular knows to inject the
|
||||
service associated with that `HeroService` class token:
|
||||
// #enddocregion tokens-2
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-8-ctor')(format=".")
|
||||
// #docregion tokens-3
|
||||
:marked
|
||||
This is especially convenient when we consider that most dependency values are provided by classes.
|
||||
// #enddocregion tokens-3
|
||||
|
||||
// #docregion tokens-non-class-deps-1
|
||||
- var lang = current.path[1]
|
||||
- var objectexamples = lang == 'dart' ? 'a string or list literal, or maybe a function' : 'a string, a function, or an object'
|
||||
// Is function injection useful? Should we show it?
|
||||
:marked
|
||||
### Non-class dependencies
|
||||
|
||||
What if the dependency value isn't a class?
|
||||
Sometimes the thing we want to inject is a string, a function, or an object.
|
||||
Sometimes the thing we want to inject is #{objectexamples}.
|
||||
// #enddocregion tokens-non-class-deps-1
|
||||
|
||||
// TS/JS only
|
||||
:marked
|
||||
Applications often define configuration objects with lots of small facts like the title of the application or the address of a web API endpoint.
|
||||
These configuration objects aren't always instances of a class. They tend to be object hashes like this one:
|
||||
|
||||
+makeExample('dependency-injection/ts/app/app.config.ts','config','app/app-config.ts (excerpt)')(format='.')
|
||||
|
||||
// TypeScript only?
|
||||
:marked
|
||||
We'd like to make this `config` object available for injection.
|
||||
We know we can register an object with a [value provider](#value-provider).
|
||||
But what do we use for the token?
|
||||
We don't have a class to serve as a token. There is no `Config` class.
|
||||
|
||||
// begin Typescript only
|
||||
// Typescript only
|
||||
<a id="interface"></a>
|
||||
:marked
|
||||
### Interfaces aren't valid tokens
|
||||
.l-sub-section
|
||||
:marked
|
||||
### TypeScript interfaces aren't valid tokens
|
||||
|
||||
The `CONFIG` constant has an interface, `Config`. Unfortunately, we
|
||||
cannot use an interface as a token:
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-9a-interface')(format=".")
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-9a-ctor-interface')(format=".")
|
||||
:marked
|
||||
cannot use a TypeScript interface as a token:
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-9a-interface')(format=".")
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-9a-ctor-interface')(format=".")
|
||||
:marked
|
||||
That seems strange if we're used to dependency injection in strongly typed languages, where
|
||||
an interface is the preferred dependency lookup key.
|
||||
|
||||
|
@ -662,16 +790,29 @@ code-example(format, language="html").
|
|||
The TypeScript interface disappears from the generated JavaScript.
|
||||
There is no interface type information left for Angular to find at runtime.
|
||||
// end Typescript only
|
||||
<a id="string-token"></a>
|
||||
|
||||
// #docregion tokens-opaque-1
|
||||
<a id="opaque-token"></a>
|
||||
- var lang = current.path[1]
|
||||
- var opaquetoken = lang == 'dart' ? '<code>OpaqueToken</code>' : '<a href="/docs/ts/latest/api/core/OpaqueToken-class.html"><code>OpaqueToken</code></a>'
|
||||
h3 OpaqueToken
|
||||
p.
|
||||
The solution is to define and use an !{opaquetoken}.
|
||||
The definition looks like this:
|
||||
// #enddocregion tokens-opaque-1
|
||||
+makeExample('dependency-injection/ts/app/app.config.ts','token')(format='.')
|
||||
:marked
|
||||
### String tokens
|
||||
Fortunately, we can register any dependency provider with a string token.
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-9a')(format=".")
|
||||
:marked
|
||||
Now we inject the configuration object into any constructor that needs it, with
|
||||
the help of an `@Inject` decorator that tells Angular how to find the configuration dependency value.
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-9a-ctor')(format=".")
|
||||
We register the dependency provider using the `OpaqueToken` object:
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-9b')(format=".")
|
||||
// #docregion tokens-opaque-2
|
||||
- var lang = current.path[1]
|
||||
- var decorated = lang == 'dart' ? 'annotated' : 'decorated'
|
||||
- var configuration = lang == 'dart' ? '' : 'configuration'
|
||||
:marked
|
||||
Now we can inject the #{configuration} object into any constructor that needs it, with
|
||||
the help of an `@Inject` #{decorator} that tells Angular how to find the #{configuration} dependency value.
|
||||
// #enddocregion tokens-opaque-2
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-9b-ctor')(format=".")
|
||||
|
||||
// begin Typescript only
|
||||
.l-sub-section
|
||||
|
@ -681,32 +822,13 @@ code-example(format, language="html").
|
|||
:marked
|
||||
// end typescript only
|
||||
|
||||
<a id="opaque-token"></a>
|
||||
// Skip for Dart (we have another example)
|
||||
:marked
|
||||
### OpaqueToken
|
||||
Alternatively, we could define and use an [OpaqueToken](/docs/ts/latest/api/core/OpaqueToken-class.html)
|
||||
rather than rely on magic strings that may collide with other developers' string choices.
|
||||
|
||||
The definition looks like this:
|
||||
+makeExample('dependency-injection/ts/app/app.config.ts','token')(format='.')
|
||||
:marked
|
||||
Substitute `APP_CONFIG` for the magic string when registering the provider and defining the constructor parameter:
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-9b')(format=".")
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-9b-ctor')(format=".")
|
||||
:marked
|
||||
Here's how we provide and inject the configuration object in our top-level `AppComponent`.
|
||||
Or we can provide and inject the configuration object in our top-level `AppComponent`.
|
||||
+makeExample('dependency-injection/ts/app/app.component.ts','providers', 'app/app.component.ts (providers)')(format=".")
|
||||
+makeExample('dependency-injection/ts/app/app.component.ts','ctor', 'app/app.component.ts (constructor)')(format=".")
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Angular itself uses `OpaqueToken` objects to register all of its own non-class dependencies. For example,
|
||||
[HTTP_PROVIDERS](/docs/ts/latest/api/http/HTTP_PROVIDERS-let.html)
|
||||
is the `OpaqueToken` associated with an array of providers for persisting data with the Angular `Http` client.
|
||||
|
||||
Internally, the `Provider` turns both the string and the class type into an `OpaqueToken`
|
||||
and keys its *token-provider* map with that.
|
||||
|
||||
// #docregion summary
|
||||
.l-main-section
|
||||
:marked
|
||||
## Summary
|
||||
|
@ -719,15 +841,18 @@ code-example(format, language="html").
|
|||
We can learn more about its advanced features, beginning with its support for
|
||||
nested injectors, in the
|
||||
[Hierarchical Dependency Injection](hierarchical-dependency-injection.html) chapter.
|
||||
// #enddocregion summary
|
||||
|
||||
// #docregion appendix-explicit-injector-1
|
||||
.l-main-section
|
||||
<a id="explicit-injector"></a>
|
||||
:marked
|
||||
### Appendix: Working with injectors directly
|
||||
We rarely work directly with an injector.
|
||||
Here's an `InjectorComponent` that does.
|
||||
|
||||
// #enddocregion appendix-explicit-injector-1
|
||||
+makeExample('dependency-injection/ts/app/injector.component.ts', 'injector', 'app/injector.component.ts')
|
||||
// #docregion appendix-explicit-injector-2
|
||||
:marked
|
||||
The `Injector` is itself an injectable service.
|
||||
|
||||
|
@ -755,7 +880,9 @@ code-example(format, language="html").
|
|||
|
||||
Framework developers may take this approach when they
|
||||
must acquire services generically and dynamically.
|
||||
// #enddocregion appendix-explicit-injector-2
|
||||
|
||||
// TypeScript only? Unnecessary for Dart
|
||||
.l-main-section
|
||||
<a id="forward-ref"></a>
|
||||
:marked
|
||||
|
|
Loading…
Reference in New Issue