From 10877bdbfc376adb1a5b8104ebe74479d0f596a8 Mon Sep 17 00:00:00 2001 From: Kathy Walrath Date: Thu, 3 Mar 2016 22:50:42 -0800 Subject: [PATCH] docs(dependency-injection): add Dart version, tweak TS version closes #972 --- .../dart/.analysis_options | 16 + .../dart/lib/app_component.dart | 60 +-- .../dart/lib/app_component_1.dart | 10 +- .../dart/lib/app_component_2.dart | 24 +- .../dart/lib/app_config.dart | 32 +- .../dart/lib/car/car.dart | 12 +- .../dart/lib/car/car_component.dart | 28 +- .../dart/lib/car/car_creations.dart | 30 +- .../dart/lib/car/car_factory.dart | 8 +- .../dart/lib/car/car_injector.dart | 6 +- .../dart/lib/car/car_no_di.dart | 4 +- .../dart/lib/heroes/hero.dart | 1 - .../dart/lib/heroes/hero_list_component.dart | 13 +- .../lib/heroes/hero_list_component_1.dart | 11 +- .../lib/heroes/hero_list_component_2.dart | 14 +- .../dart/lib/heroes/hero_service.dart | 14 +- .../dart/lib/heroes/hero_service_1.dart | 1 - .../dart/lib/heroes/hero_service_2.dart | 6 +- .../lib/heroes/hero_service_provider.dart | 10 +- .../dart/lib/heroes/heroes_component.dart | 7 +- .../dart/lib/heroes/heroes_component_1.dart | 10 +- .../dart/lib/heroes/mock_heroes.dart | 1 - .../dart/lib/injector_component.dart | 33 +- .../dart/lib/logger_service.dart | 1 - .../dart/lib/providers_component.dart | 328 ++++++++-------- .../dart/lib/user_service.dart | 1 - .../dependency-injection/dart/pubspec.yaml | 2 +- .../dart/test/hero_list_component_test.dart | 4 +- .../dependency-injection/dart/web/index.html | 22 +- .../dependency-injection/dart/web/main_1.dart | 10 +- .../ts/app/injector.component.ts | 2 +- .../latest/guide/dependency-injection.jade | 280 +++++++++++++- .../ts/latest/guide/dependency-injection.jade | 363 ++++++++++++------ 33 files changed, 865 insertions(+), 499 deletions(-) create mode 100644 public/docs/_examples/dependency-injection/dart/.analysis_options diff --git a/public/docs/_examples/dependency-injection/dart/.analysis_options b/public/docs/_examples/dependency-injection/dart/.analysis_options new file mode 100644 index 0000000000..d8c582e96f --- /dev/null +++ b/public/docs/_examples/dependency-injection/dart/.analysis_options @@ -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 diff --git a/public/docs/_examples/dependency-injection/dart/lib/app_component.dart b/public/docs/_examples/dependency-injection/dart/lib/app_component.dart index 4e01554a42..1a9e4f075b 100644 --- a/public/docs/_examples/dependency-injection/dart/lib/app_component.dart +++ b/public/docs/_examples/dependency-injection/dart/lib/app_component.dart @@ -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'; @@ -17,33 +16,39 @@ import 'providers_component.dart'; @Component( selector: 'my-app', template: ''' -

{{title}}

- - - -

User

-

- {{userInfo}} - -

- - - ''', - directives: const [CarComponent, HeroesComponent, InjectorComponent, ProvidersComponent], +

{{title}}

+ + + +

User

+

+ {{userInfo}} + +

+ + ''', + 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 diff --git a/public/docs/_examples/dependency-injection/dart/lib/app_component_1.dart b/public/docs/_examples/dependency-injection/dart/lib/app_component_1.dart index 7c3b7b9808..2466b4894b 100644 --- a/public/docs/_examples/dependency-injection/dart/lib/app_component_1.dart +++ b/public/docs/_examples/dependency-injection/dart/lib/app_component_1.dart @@ -2,19 +2,19 @@ // #docregion import 'package:angular2/core.dart'; + import 'car/car_component.dart'; import 'heroes/heroes_component_1.dart'; @Component( selector: 'my-app', template: ''' -

{{title}}

- - - ''', +

{{title}}

+ + ''', directives: const [CarComponent, HeroesComponent]) class AppComponent { - var title = 'Dependency Injection'; + final String title = 'Dependency Injection'; } // #enddocregion diff --git a/public/docs/_examples/dependency-injection/dart/lib/app_component_2.dart b/public/docs/_examples/dependency-injection/dart/lib/app_component_2.dart index 7bf4249ee2..4463b7c59c 100644 --- a/public/docs/_examples/dependency-injection/dart/lib/app_component_2.dart +++ b/public/docs/_examples/dependency-injection/dart/lib/app_component_2.dart @@ -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: ''' -

{{title}}

- - - ''', +

{{title}}

+ + ''', 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 diff --git a/public/docs/_examples/dependency-injection/dart/lib/app_config.dart b/public/docs/_examples/dependency-injection/dart/lib/app_config.dart index 8fc877f188..7785eff1b4 100644 --- a/public/docs/_examples/dependency-injection/dart/lib/app_config.dart +++ b/public/docs/_examples/dependency-injection/dart/lib/app_config.dart @@ -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 diff --git a/public/docs/_examples/dependency-injection/dart/lib/car/car.dart b/public/docs/_examples/dependency-injection/dart/lib/car/car.dart index 4c8d1025e7..cacd0d9b69 100644 --- a/public/docs/_examples/dependency-injection/dart/lib/car/car.dart +++ b/public/docs/_examples/dependency-injection/dart/lib/car/car.dart @@ -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 diff --git a/public/docs/_examples/dependency-injection/dart/lib/car/car_component.dart b/public/docs/_examples/dependency-injection/dart/lib/car/car_component.dart index e8e3ccc0e0..b7e663b019 100644 --- a/public/docs/_examples/dependency-injection/dart/lib/car/car_component.dart +++ b/public/docs/_examples/dependency-injection/dart/lib/car/car_component.dart @@ -1,29 +1,29 @@ // #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', template: ''' -

Cars

-
{{car.drive()}}
-
{{noDiCar.drive()}}
-
{{injectorCar.drive()}}
-
{{factoryCar.drive()}}
-
{{simpleCar.drive()}}
-
{{superCar.drive()}}
-
{{testCar.drive()}}
- ''', +

Cars

+
{{car.drive()}}
+
{{noDiCar.drive()}}
+
{{injectorCar.drive()}}
+
{{factoryCar.drive()}}
+
{{simpleCar.drive()}}
+
{{superCar.drive()}}
+
{{testCar.drive()}}
''', 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(); diff --git a/public/docs/_examples/dependency-injection/dart/lib/car/car_creations.dart b/public/docs/_examples/dependency-injection/dart/lib/car/car_creations.dart index 33dfa05828..d0e6be3c1c 100644 --- a/public/docs/_examples/dependency-injection/dart/lib/car/car_creations.dart +++ b/public/docs/_examples/dependency-injection/dart/lib/car/car_creations.dart @@ -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; } diff --git a/public/docs/_examples/dependency-injection/dart/lib/car/car_factory.dart b/public/docs/_examples/dependency-injection/dart/lib/car/car_factory.dart index 76b8a4cc63..7610e28b14 100644 --- a/public/docs/_examples/dependency-injection/dart/lib/car/car_factory.dart +++ b/public/docs/_examples/dependency-injection/dart/lib/car/car_factory.dart @@ -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(); } diff --git a/public/docs/_examples/dependency-injection/dart/lib/car/car_injector.dart b/public/docs/_examples/dependency-injection/dart/lib/car/car_injector.dart index 1dbadd9fab..d3da37a9e7 100644 --- a/public/docs/_examples/dependency-injection/dart/lib/car/car_injector.dart +++ b/public/docs/_examples/dependency-injection/dart/lib/car/car_injector.dart @@ -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()); diff --git a/public/docs/_examples/dependency-injection/dart/lib/car/car_no_di.dart b/public/docs/_examples/dependency-injection/dart/lib/car/car_no_di.dart index 32972c3dc1..86a4177b2d 100644 --- a/public/docs/_examples/dependency-injection/dart/lib/car/car_no_di.dart +++ b/public/docs/_examples/dependency-injection/dart/lib/car/car_no_di.dart @@ -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 diff --git a/public/docs/_examples/dependency-injection/dart/lib/heroes/hero.dart b/public/docs/_examples/dependency-injection/dart/lib/heroes/hero.dart index ea10276b7c..2a76844f9c 100644 --- a/public/docs/_examples/dependency-injection/dart/lib/heroes/hero.dart +++ b/public/docs/_examples/dependency-injection/dart/lib/heroes/hero.dart @@ -1,5 +1,4 @@ // #docregion - class Hero { num id; String name; diff --git a/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_list_component.dart b/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_list_component.dart index c4eeb14f1b..0c79b2f600 100644 --- a/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_list_component.dart +++ b/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_list_component.dart @@ -1,19 +1,18 @@ // #docregion - import 'package:angular2/core.dart'; + import 'hero.dart'; import 'hero_service.dart'; @Component( selector: 'hero-list', template: ''' -
- {{hero.id}} - {{hero.name}} - ({{hero.isSecret ? \'secret\' : \'public\'}}) -
- ''') +
+ {{hero.id}} - {{hero.name}} + ({{hero.isSecret ? 'secret' : 'public'}}) +
''') class HeroListComponent { - List heroes; + final List heroes; //#docregion ctor-signature HeroListComponent(HeroService heroService) : heroes = heroService.getHeroes(); diff --git a/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_list_component_1.dart b/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_list_component_1.dart index 3aeef5aefb..366cf570b0 100644 --- a/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_list_component_1.dart +++ b/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_list_component_1.dart @@ -1,16 +1,15 @@ // #docregion - import 'package:angular2/core.dart'; + import 'hero.dart'; import 'mock_heroes.dart'; @Component( selector: 'hero-list', template: ''' -
- {{hero.id}} - {{hero.name}} -
- ''') +
+ {{hero.id}} - {{hero.name}} +
''') class HeroListComponent { - List heroes = HEROES; + final List heroes = HEROES; } diff --git a/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_list_component_2.dart b/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_list_component_2.dart index 2b76419505..637ca74b00 100644 --- a/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_list_component_2.dart +++ b/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_list_component_2.dart @@ -1,20 +1,20 @@ // #docregion - import 'package:angular2/core.dart'; + import 'hero.dart'; import 'hero_service.dart'; @Component( selector: 'hero-list', template: ''' -
- {{hero.id}} - {{hero.name}} -
- ''') +
+ {{hero.id}} - {{hero.name}} +
''') class HeroListComponent { - List heroes; + final List heroes; //#docregion ctor - HeroListComponent(HeroService heroService) : heroes = heroService.getHeroes(); + HeroListComponent(HeroService heroService) + : heroes = heroService.getHeroes(); //#enddocregion ctor } diff --git a/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_service.dart b/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_service.dart index 2dda48cb70..59074e3cd4 100644 --- a/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_service.dart +++ b/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_service.dart @@ -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 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 } diff --git a/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_service_1.dart b/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_service_1.dart index 7ee6a642b4..f400d86fc4 100644 --- a/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_service_1.dart +++ b/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_service_1.dart @@ -1,5 +1,4 @@ // #docregion - import 'hero.dart'; import 'mock_heroes.dart'; diff --git a/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_service_2.dart b/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_service_2.dart index 25146a76f9..badd8f548e 100644 --- a/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_service_2.dart +++ b/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_service_2.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); diff --git a/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_service_provider.dart b/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_service_provider.dart index b783f0edc5..7e457e2db9 100644 --- a/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_service_provider.dart +++ b/public/docs/_examples/dependency-injection/dart/lib/heroes/hero_service_provider.dart @@ -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 diff --git a/public/docs/_examples/dependency-injection/dart/lib/heroes/heroes_component.dart b/public/docs/_examples/dependency-injection/dart/lib/heroes/heroes_component.dart index 45e310cd30..d90871de73 100644 --- a/public/docs/_examples/dependency-injection/dart/lib/heroes/heroes_component.dart +++ b/public/docs/_examples/dependency-injection/dart/lib/heroes/heroes_component.dart @@ -1,15 +1,14 @@ // #docregion - import 'package:angular2/core.dart'; + import 'hero_list_component.dart'; import 'hero_service_provider.dart'; @Component( selector: 'my-heroes', template: ''' -

Heroes

- - ''', +

Heroes

+ ''', providers: const [heroServiceProvider], directives: const [HeroListComponent]) class HeroesComponent {} diff --git a/public/docs/_examples/dependency-injection/dart/lib/heroes/heroes_component_1.dart b/public/docs/_examples/dependency-injection/dart/lib/heroes/heroes_component_1.dart index 80d7ad8f2e..19cde705ce 100644 --- a/public/docs/_examples/dependency-injection/dart/lib/heroes/heroes_component_1.dart +++ b/public/docs/_examples/dependency-injection/dart/lib/heroes/heroes_component_1.dart @@ -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: ''' -

Heroes

- - ''', +

Heroes

+ ''', // #enddocregion v1 // #docregion providers providers: const [HeroService], diff --git a/public/docs/_examples/dependency-injection/dart/lib/heroes/mock_heroes.dart b/public/docs/_examples/dependency-injection/dart/lib/heroes/mock_heroes.dart index 9f49bf208c..2501f92081 100644 --- a/public/docs/_examples/dependency-injection/dart/lib/heroes/mock_heroes.dart +++ b/public/docs/_examples/dependency-injection/dart/lib/heroes/mock_heroes.dart @@ -1,5 +1,4 @@ // #docregion - import 'hero.dart'; List HEROES = [ diff --git a/public/docs/_examples/dependency-injection/dart/lib/injector_component.dart b/public/docs/_examples/dependency-injection/dart/lib/injector_component.dart index 24e1598f13..ecc85f9c39 100644 --- a/public/docs/_examples/dependency-injection/dart/lib/injector_component.dart +++ b/public/docs/_examples/dependency-injection/dart/lib/injector_component.dart @@ -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: ''' -

Other Injections

-
{{car.drive()}}
-
{{hero.name}}
-
{{rodent}}
- ''', +

Other Injections

+
{{car.drive()}}
+
{{hero.name}}
+
{{rodent}}
''', 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 diff --git a/public/docs/_examples/dependency-injection/dart/lib/logger_service.dart b/public/docs/_examples/dependency-injection/dart/lib/logger_service.dart index 452daefd22..38b4450f25 100644 --- a/public/docs/_examples/dependency-injection/dart/lib/logger_service.dart +++ b/public/docs/_examples/dependency-injection/dart/lib/logger_service.dart @@ -1,5 +1,4 @@ // #docregion - import 'package:angular2/core.dart'; @Injectable() diff --git a/public/docs/_examples/dependency-injection/dart/lib/providers_component.dart b/public/docs/_examples/dependency-injection/dart/lib/providers_component.dart index 83d461deb2..34a1c82f3e 100644 --- a/public/docs/_examples/dependency-injection/dart/lib/providers_component.dart +++ b/public/docs/_examples/dependency-injection/dart/lib/providers_component.dart @@ -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 logs = []; + final UserService _userService; + @override List 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 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 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,129 +223,103 @@ 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 logs = []; + @override void log(String msg) => logs.add(msg); +} +// #enddocregion provider-10-logger + @Component( selector: 'my-providers', template: ''' -

Provider variations

-
-
-
-
-
-
-
-
-
-
-
-
-
- ''', +

Provider variations

+
+
+
+
+
+
+
+
+
+
+
+
+
''', directives: const [ ProviderComponent1, ProviderComponent2, @@ -352,9 +330,9 @@ class ProviderComponent10b implements OnInit { ProviderComponent6b, ProviderComponent7, ProviderComponent8, - ProviderComponent9a, - ProviderComponent9b, + ProviderComponent9, ProviderComponent10a, - ProviderComponent10b + ProviderComponent10b, + ProviderComponent10c ]) class ProvidersComponent {} diff --git a/public/docs/_examples/dependency-injection/dart/lib/user_service.dart b/public/docs/_examples/dependency-injection/dart/lib/user_service.dart index 682a42b967..5b6cabf960 100644 --- a/public/docs/_examples/dependency-injection/dart/lib/user_service.dart +++ b/public/docs/_examples/dependency-injection/dart/lib/user_service.dart @@ -1,5 +1,4 @@ // #docregion - import 'package:angular2/core.dart'; @Injectable() diff --git a/public/docs/_examples/dependency-injection/dart/pubspec.yaml b/public/docs/_examples/dependency-injection/dart/pubspec.yaml index 6a42a54bd3..d472d2099f 100644 --- a/public/docs/_examples/dependency-injection/dart/pubspec.yaml +++ b/public/docs/_examples/dependency-injection/dart/pubspec.yaml @@ -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: diff --git a/public/docs/_examples/dependency-injection/dart/test/hero_list_component_test.dart b/public/docs/_examples/dependency-injection/dart/test/hero_list_component_test.dart index 0dd76b4f15..1c45babc68 100644 --- a/public/docs/_examples/dependency-injection/dart/test/hero_list_component_test.dart +++ b/public/docs/_examples/dependency-injection/dart/test/hero_list_component_test.dart @@ -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'; @@ -31,4 +31,4 @@ void main() { expect(hlc.heroes.length, expectedHeroes.length); }); } -//#enddocregion spec \ No newline at end of file +//#enddocregion spec diff --git a/public/docs/_examples/dependency-injection/dart/web/index.html b/public/docs/_examples/dependency-injection/dart/web/index.html index dd40fd6c61..cd3dfdb6cf 100644 --- a/public/docs/_examples/dependency-injection/dart/web/index.html +++ b/public/docs/_examples/dependency-injection/dart/web/index.html @@ -1,15 +1,15 @@ - + - - Dependency Injection - - - - - -Loading my-app ... -Loading my-providers ... - + + Dependency Injection + + + + + + Loading... + Loading my-providers ... + diff --git a/public/docs/_examples/dependency-injection/dart/web/main_1.dart b/public/docs/_examples/dependency-injection/dart/web/main_1.dart index ff9166ccf1..b621869c60 100644 --- a/public/docs/_examples/dependency-injection/dart/web/main_1.dart +++ b/public/docs/_examples/dependency-injection/dart/web/main_1.dart @@ -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 -} \ No newline at end of file + //#docregion bootstrap + bootstrap(AppComponent, + [HeroService]); // DISCOURAGED (but works) + //#enddocregion bootstrap +} diff --git a/public/docs/_examples/dependency-injection/ts/app/injector.component.ts b/public/docs/_examples/dependency-injection/ts/app/injector.component.ts index 300eaee9d3..9914e7d029 100644 --- a/public/docs/_examples/dependency-injection/ts/app/injector.component.ts +++ b/public/docs/_examples/dependency-injection/ts/app/injector.component.ts @@ -12,7 +12,7 @@ import {Logger} from './logger.service'; selector: 'my-injectors', template: `

Other Injections

-
{{car.drive()}}
+
{{car.drive()}}
{{hero.name}}
{{rodent}}
`, diff --git a/public/docs/dart/latest/guide/dependency-injection.jade b/public/docs/dart/latest/guide/dependency-injection.jade index 72fdaa1a49..ee0d41deef 100644 --- a/public/docs/dart/latest/guide/dependency-injection.jade +++ b/public/docs/dart/latest/guide/dependency-injection.jade @@ -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') diff --git a/public/docs/ts/latest/guide/dependency-injection.jade b/public/docs/ts/latest/guide/dependency-injection.jade index 8c6017ccf8..eded8d62ac 100644 --- a/public/docs/ts/latest/guide/dependency-injection.jade +++ b/public/docs/ts/latest/guide/dependency-injection.jade @@ -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 .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: - +// #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 .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 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`. - +// #docregion di-create-injector-implicitly-1 :marked + ### 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 ``, 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 we mentioned earlier, TypeScript only generates metadata for classes that have a decorator.' : '' +:marked ### 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 Future proofing: No need to remember @Injectable when we add a dependency later. - li Consistency: All services follow the same rules, and we don't have to wonder why a decorator is missing. + li Future proofing: No need to remember @Injectable() when we add a dependency later. + li Consistency: 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 .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 - ### 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 ### 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 :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 :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 .l-main-section :marked @@ -620,58 +733,86 @@ 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 -: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 - That seems strange if we're used to dependency injection in strongly typed languages, where - an interface is the preferred dependency lookup key. + The `CONFIG` constant has an interface, `Config`. Unfortunately, we + 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. - It's not Angular's fault. An interface is a TypeScript design-time artifact. JavaScript doesn't have interfaces. - The TypeScript interface disappears from the generated JavaScript. - There is no interface type information left for Angular to find at runtime. + It's not Angular's fault. An interface is a TypeScript design-time artifact. JavaScript doesn't have interfaces. + The TypeScript interface disappears from the generated JavaScript. + There is no interface type information left for Angular to find at runtime. // end Typescript only - + +// #docregion tokens-opaque-1 + +- var lang = current.path[1] +- var opaquetoken = lang == 'dart' ? 'OpaqueToken' : 'OpaqueToken' +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 - +// 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 :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 :marked