docs: remove public/docs/dart (#2956)

* tutorial when back ported

* api when backported

* guide when backported

* quickstart, cheatsheet and glossary when backported

* remove Jekyll front matter from each back ported file

* backport Dart _util-fns.jade

* remove docs/dart

* gitignore docs/dart

* only include Dart in language menu if it exists

* README: remove mention of Dart
This commit is contained in:
Patrice Chalin 2016-12-09 12:05:58 -08:00 committed by Filipe Silva
parent cf97326344
commit e469929123
69 changed files with 6 additions and 3810 deletions

3
.gitignore vendored
View File

@ -32,4 +32,5 @@ public/docs/*/latest/guide/cheatsheet.json
protractor-results.txt
link-checker-results.txt
*a2docs.css
/dist
/dist
/public/docs/dart

View File

@ -35,19 +35,13 @@ if not install [nvm](https://github.com/creationix/nvm) to get node going on you
- this repo
- [angular/angular source code repo](https://github.com/angular/angular)
- (OPTIONAL) [dart-lang/angular2 source code repo](https://github.com/dart-lang/angular2);
cloning the Angular Dart repo is only necessary if you plan on doing full site builds
to the same parent directory. The **cloned repo directories must be siblings**, with the latter two repo directories named **angular** and **angular-dart**, respectively.
to the same parent directory. The **cloned repo directories must be siblings**, with the latter named **angular**.
1. cd into root directory `angular.io/`
1. Install local npm packages by running `./scripts/install.sh`
1. (OPTIONAL) If you intend on doing **full site builds** then you must have the
Angular Dart repo (see the Clone step above), _and_ Dart tooling available.
Both can be installed by running `./scripts/deploy-install.sh`
1. See [below](#code-sample-development) for code sample development preparation.
## Content Development
@ -116,8 +110,7 @@ You must check that your example is free of lint errors.
All samples should be covered to some degree by end-to-end tests:
- `gulp run-e2e-tests` to run all TypeScript and JavaScript tests
- `gulp run-e2e-tests --lang=dart` to run all Dart tests
- `gulp run-e2e-tests --lang=all` to run TypeScript, JavaScript, and Dart tests
- `gulp run-e2e-tests --lang=all` to run TypeScript and JavaScript tests
- `gulp run-e2e-tests --filter=quickstart` to filter the examples to run, by name
- `gulp run-e2e-tests --fast` to ignore npm install, webdriver update and boilerplate copy

View File

@ -72,5 +72,6 @@ if current.path[4] !== 'change-log'
mixin tree(public.docs.ts, "/docs/ts", "Angular for TypeScript")
mixin tree(public.docs.js, "/docs/js", "Angular for JavaScript")
//- Disable cross-language link for API entry pages (but keep for top API search page):
if ! (current.path[3] === 'api' && public.docs[current.path[1]][current.path[2]][current.path[3]][current.path[4]])
- var isApiEntryPage = current.path[3] === 'api' && public.docs[current.path[1]][current.path[2]][current.path[3]][current.path[4]]
if public.docs.dart && !isApiEntryPage
mixin tree(public.docs.dart, "/docs/dart", "Angular for Dart")

View File

@ -1,83 +0,0 @@
{
"index": {
"icon": "home",
"title": "Angular Docs",
"subtitle": "Dart",
"menuTitle": "Docs Home",
"banner": "AngularDart is <b>2.2</b>. View the <a href='https://github.com/dart-lang/angular2/blob/master/CHANGELOG.md' target='_blank'>change log</a> to see enhancements, fixes, and breaking changes."
},
"cli-quickstart": {
"icon": "query-builder",
"title": "CLI Quickstart",
"subtitle": "TypeScript",
"description": "Use the CLI tool to quickly build Angular applications",
"hide": true
},
"quickstart": {
"icon": "query-builder",
"title": "Quickstart",
"subtitle": "Dart",
"banner": "A quick look at Angular basics"
},
"tutorial": {
"icon": "list",
"title": "Tutorial",
"subtitle": "Dart"
},
"guide": {
"icon": "list",
"title": "Guide",
"subtitle": "Dart"
},
"cookbook": {
"icon": "list",
"title": "Cookbook",
"subtitle": "Dart",
"banner": "How to solve common implementation challenges."
},
"api/": {
"icon": "book",
"title": "API Reference",
"subtitle": "Dart",
"reference": true
},
"cheatsheet": {
"title": "Cheat Sheet",
"subtitle": "Dart",
"reference": false
},
"glossary": {
"title": "Glossary",
"subtitle": "Dart",
"intro": "Brief definitions of the most important words in the Angular vocabulary",
"reference": false
},
"resources": {
"icon": "play-circle-fill",
"title": "Angular Resources",
"subtitle": "Dart",
"resources": true
},
"help": {
"icon": "chat",
"title": "Help & Support",
"subtitle": "From our team & community",
"resources": true
},
"styleguide": {
"title": "Docs Style Guide",
"subtitle": "Dart",
"intro": "Design & Layout Patterns For Documentation"
}
}

View File

@ -1,72 +0,0 @@
include ../../../_includes/_util-fns
//- See the _util-fns file included above for a description of the use of these variables.
- var _docsFor = 'dart';
- var _decorator = 'annotation';
- var _Array = 'List';
- var _array = 'list';
- var _a = 'an';
- var _an = 'a';
- var _priv = '_';
- var _Lang = 'Dart';
- var _Promise = 'Future';
- var _FutureUrl = 'https://api.dartlang.org/dart_async/Future.html';
- var _PromiseLinked = '<a href="' + _FutureUrl + '">' + _Promise + '</a>';
- var _Observable = 'Stream';
- var _liveLink = 'sample repo';
- var _truthy = 'true';
- var _falsey = 'false';
- var _appDir = 'lib';
- var _indexHtmlDir = 'web';
- var _mainDir = 'web';
- var _ngRepoURL = 'https://github.com/dart-lang/angular2';
//- Don't override this value quite yet:
//- var _ngDocRepoURL = 'https://github.com/dart-lang/site-webdev';
- var _qsRepo = 'https://github.com/angular-examples/quickstart'
- var _qsRepoZip = _qsRepo + '/archive/master.zip';
- var _npm = 'pub';
//- NgModule related
- var _AppModuleVsAppComp = 'AppComponent'
- var _appModuleTsVsAppCompTs = 'app/app_component.dart'
- var _appModuleTsVsMainTs = 'web/main.dart'
- var _bootstrapModule = 'bootstrap'
- var _declsVsDirectives = 'directives'
- var _moduleVsComp = 'component'
- var _moduleVsRootComp = 'root component'
- var _platformBrowserDynamicVsBootStrap = 'bootstrap'
- var adjustTsExamplePathForDart = function(_path) {
- if(!_path) return _path;
- var path = _path.trim();
- var folder = getFolder(path);
- var extn = getExtn(path);
- // if(extn == 'dart') return path;
- var baseName = getBaseFileName(path) || path; // TODO: have getBaseFileName() return path
- var baseNameNoExt = baseName.substr(0,baseName.length - (extn.length + 1));
- var inWebFolder = baseNameNoExt.match(/^(main|index)(\.\d)?$/);
- // Adjust the folder path, e.g., ts -> dart
- folder = folder.replace(/(^|\/)ts($|\/)/, '$1dart$2').replace(/(^|\/)app($|\/)/, inWebFolder ? '$1web$2' : '$1lib$2');
- // Special case not handled above: e.g., index.html -> web/index.html
- if(baseNameNoExt.match(/^(index|styles)(\.\d)?$/) && !folder.match(/web$/)) folder = (folder ? folder + '/' : '') + 'web';
- // In file name, replace special characters with underscore
- baseNameNoExt = baseNameNoExt.replace(/[\-\.]/g, '_');
- // Adjust the file extension
- if(extn == 'ts') extn = 'dart';
- return (folder ? folder + '/' : '') + baseNameNoExt + (extn ? '.' + extn : '');
- };
- var adjustTsExampleTitleForDart = function(_title) {
- if(!_title || !adjustTsExamplePathForDart) return _title;
- var title = _title.trim();
- // Assume title is a path if it ends with an extension like '.foo',
- // optionally followed by some comment in parentheses.
- var matches = title.match(/(.*\.\w+)($|\s*\([\w ]+\)$)/);
- if(matches && matches.length == 3) {
- // e.g. matches == ['abc.ts (excerpt)', 'abc.ts', ' (excerpt)']
- var path = adjustTsExamplePathForDart(matches[1]);
- title = path + matches[2];
- }
- return title;
- }

View File

@ -1,5 +0,0 @@
{
"index" : {
"title" : "API Reference"
}
}

View File

@ -1,12 +0,0 @@
include ../_util-fns
:marked
> **Known issues:** The Angular issue tracker contains
[all known issues][api-issues]; if you notice others, please
[report them][new-issue]. Thanks!
[new-issue]: !{_ngRepoURL}/issues/new?title=%5BAPI%20docs%5D
[api-issues]: !{_ngRepoURL}/issues
//- ?q=label%3Aapi
api-list(src="api-list.json" lang="dart")

View File

@ -1,4 +0,0 @@
- var base = current.path[4] ? '.' : './guide';
.l-content-small.docs-content.cheatsheet
ngio-cheatsheet(src= base + '/cheatsheet.json')

View File

@ -1,72 +0,0 @@
{
"index": {
"title": "Cookbook",
"navTitle": "Overview",
"intro": "A collection of recipes for common Angular application scenarios"
},
"a1-a2-quick-reference": {
"title": "Angular 1 to 2 Quick Reference",
"navTitle": "Angular 1 to 2 Quick Ref",
"intro": "Learn how Angular 1 concepts and techniques map to Angular 2",
"hide": true
},
"ngmodule-faq": {
"title": "Angular Module FAQs",
"intro": "Answers to frequently asked questions about @NgModule",
"hide": true
},
"component-communication": {
"title": "Component Interaction",
"intro": "Share information between different directives and components"
},
"component-relative-paths": {
"title": "Component-relative Paths",
"intro": "Use relative URLs for component templates and styles.",
"hide": true
},
"dependency-injection": {
"title": "Dependency Injection",
"intro": "Techniques for Dependency Injection",
"hide": true
},
"dynamic-forms": {
"title": "Dynamic Form",
"intro": "Render dynamic forms with NgFormModel",
"hide": true
},
"form-validation": {
"title": "Form Validation",
"intro": "Validate user's form entries",
"hide": true
},
"i18n": {
"title": "Internationalization (i18n)",
"intro": "Translate the app's template text into multiple languages",
"hide": true
},
"set-document-title": {
"title": "Set the Document Title",
"intro": "Setting the document or window title using the Title service."
},
"ts-to-js": {
"title": "TypeScript to JavaScript",
"intro": "Convert Angular TypeScript examples into ES6 and ES5 JavaScript",
"hide": true
},
"visual-studio-2015": {
"title": "Visual Studio 2015 QuickStart",
"intro": "Use Visual Studio 2015 with the QuickStart files",
"hide": true
}
}

View File

@ -1 +0,0 @@
include ../../../_includes/_ts-temp

View File

@ -1 +0,0 @@
include ../../../_includes/_ts-temp

View File

@ -1 +0,0 @@
include ../../../_includes/_ts-temp

View File

@ -1 +0,0 @@
include ../../../_includes/_ts-temp

View File

@ -1 +0,0 @@
include ../../../_includes/_ts-temp

View File

@ -1 +0,0 @@
include ../../../_includes/_ts-temp

View File

@ -1 +0,0 @@
include ../../../_includes/_ts-temp

View File

@ -1 +0,0 @@
include ../../../_includes/_ts-temp

View File

@ -1 +0,0 @@
include ../../../_includes/_ts-temp

View File

@ -1 +0,0 @@
include ../../../_includes/_ts-temp

View File

@ -1 +0,0 @@
include ../../../_includes/_ts-temp

View File

@ -1 +0,0 @@
include ../../../_includes/_ts-temp

View File

@ -1 +0,0 @@
include ../../../_includes/_ts-temp

View File

@ -1 +0,0 @@
include ../../../_includes/_ts-temp

View File

@ -1,59 +0,0 @@
extends ../../ts/_cache/glossary.jade
block includes
include _util-fns
block annotation-defn
:marked
When unqualified, _annotation_ refers to a Dart [metadata
annotation][metadata] (as opposed to, say, a type annotation). A metadata
annotation begins with the character `@`, followed by either a reference
to a compile-time constant (such as [`Component`](#component)) or a call
to a constant constructor. See the [Dart Language Guide][metadata] for
details.
The corresponding term in TypeScript and JavaScript is
[_decorator_](https://angular.io/docs/ts/latest/glossary.html#decorator).
[metadata]: https://www.dartlang.org/guides/language/language-tour#metadata
block bootstrap-defn-top
:marked
You launch an Angular application by "bootstrapping" it with the
[bootstrap][bootstrap] method. Bootstraping identifies an
application's top level "root" [component](#component), which is
the first component that is loaded for the application, and optionally
registers service [providers](#provider) with the [dependency injection
system](#dependency-injection).
For more information, see the [Setup](!{docsLatest}/guide/setup.html) page.
[bootstrap]: !{docsLatest}/api/angular2.platform.browser/bootstrap.html
block decorator-defn
:marked
When used in this guide, these JavaScript terms are taken as synonymous with
[annotation](#annotation).
block module-defn
//- Taken from the Dart Difference in guide/architecture.jade
:marked
In this guide, the term _module_ refers to a Dart compilation unit, such
as a library, or a package. (If a Dart file has no `library` or `part`
directive, then that file itself is a library and thus a compilation
unit.) For more information about compilation units, see
the chapter on "Libraries and Scripts" in the
[Dart Language Specification](https://www.dartlang.org/docs/spec/).
block append snake-case-defn
:marked
Library and file names are often spelled in snake_case. Examples include:
`angular_tour_of_heroes` and `app_component.dart`.
block zone-defn
:marked
Zones are a mechanism for encapsulating and intercepting
a Dart application's asynchronous activity.
Learn more about zones in this [article][zones].
[zones]: https://www.dartlang.org/articles/libraries/zones

View File

@ -1,197 +0,0 @@
{
"index": {
"title": "Documentation Overview",
"navTitle": "Overview",
"intro": "How to read and use this documentation",
"nextable": true,
"basics": true
},
"setup": {
"title": "Setup for Development",
"navTitle": "Setup",
"intro": "How to use Dart tools to create and run Angular apps",
"nextable": true,
"hideNextPage": true,
"basics": true
},
"learning-angular": {
"title": "Learning Angular",
"navTitle": "Learning Angular",
"intro": "A suggested path through the documentation for Angular newcomers",
"nextable": true,
"hideNextPage": true,
"basics": true
},
"architecture": {
"title": "Architecture Overview",
"navTitle": "Architecture",
"intro": "The basic building blocks of Angular applications",
"nextable": true,
"basics": true
},
"displaying-data": {
"title": "Displaying Data",
"intro": "Property binding helps show app data in the UI.",
"nextable": true,
"basics": true
},
"user-input": {
"title": "User Input",
"intro": "User input triggers DOM events. We listen to those events with event bindings that funnel updated values back into our components and models.",
"nextable": true,
"basics": true
},
"forms": {
"title": "Forms",
"intro": "A form creates a cohesive, effective, and compelling data entry experience. An Angular form coordinates a set of data-bound user controls, tracks changes, validates input, and presents errors.",
"nextable": true,
"basics": true
},
"dependency-injection": {
"title": "Dependency Injection",
"intro": "Angular's dependency injection system creates and delivers dependent services \"just-in-time\".",
"nextable": true,
"basics": true
},
"template-syntax": {
"title": "Template Syntax",
"intro": "Learn how to write templates that display data and consume user events with the help of data binding.",
"nextable": true,
"basics": true
},
"cheatsheet": {
"title": "Cheat Sheet",
"subtitle": "Dart",
"nextable": true,
"basics": true
},
"style-guide": {
"hide": true,
"title": "Style Guide",
"intro": "Write Angular with style.",
"basics": true
},
"glossary": {
"title": "Glossary",
"intro": "Brief definitions of the most important words in the Angular vocabulary",
"basics": true
},
"change-log": {
"hide": true,
"title": "Change Log",
"intro": "An annotated history of recent documentation improvements.",
"basics": true
},
"ngmodule": {
"hide": true,
"title": "Angular Modules (NgModule)",
"intro": "Define application modules with @NgModule"
},
"animations": {
"hide": true,
"title": "Animations",
"intro": "A guide to Angular's animation system."
},
"attribute-directives": {
"title": "Attribute Directives",
"intro": "Attribute directives attach behavior to elements."
},
"browser-support": {
"hide": true,
"title": "Browser support",
"intro": "Browser support and polyfills guide."
},
"component-styles": {
"title": "Component Styles",
"intro": "Learn how to apply CSS styles to components."
},
"hierarchical-dependency-injection": {
"title": "Hierarchical Dependency Injectors",
"navTitle": "Hierarchical Injectors",
"intro": "Angular's hierarchical dependency injection system supports nested injectors in parallel with the component tree."
},
"server-communication": {
"title": "HTTP Client",
"intro": "Use an HTTP Client to talk to a remote server."
},
"lifecycle-hooks": {
"title": "Lifecycle Hooks",
"intro": "Angular calls lifecycle hook methods on directives and components as it creates, changes, and destroys them."
},
"npm-packages": {
"hide": true,
"title": "Npm Packages",
"intro": "Recommended npm packages, and how to specify package dependencies"
},
"pipes": {
"title": "Pipes",
"intro": "Pipes transform displayed values within a template."
},
"router": {
"title": "Routing & Navigation",
"intro": "Discover the basics of screen navigation with the Angular Router."
},
"security": {
"title": "Security",
"intro": "Developing for content security in Angular applications"
},
"setup-systemjs-anatomy": {
"hide": true,
"title": "Setup Anatomy",
"intro": "Inside the local development environment for SystemJS"
},
"structural-directives": {
"title": "Structural Directives",
"intro": "Angular has a powerful template engine that lets us easily manipulate the DOM structure of our elements."
},
"testing": {
"hide": true,
"title": "Testing",
"intro": "Techniques and practices for testing an Angular app"
},
"typescript-configuration": {
"hide": true,
"title": "TypeScript Configuration",
"intro": "TypeScript configuration for Angular developers"
},
"upgrade": {
"hide": true,
"title": "Upgrading from 1.x",
"intro": "Incrementally upgrade an Angular 1 application to Angular 2."
},
"webpack": {
"hide": true,
"title": "Webpack: an introduction",
"intro": "Create Angular applications with a Webpack based tooling"
}
}

View File

@ -1 +0,0 @@
include ../../../_includes/_ts-temp

View File

@ -1,72 +0,0 @@
extends ../../../ts/_cache/guide/architecture.jade
block includes
include ../_util-fns
- var _library_module = 'library'
- var _at_angular = 'angular2'
:marked
Angular is a framework to help us build client applications in HTML and
either JavaScript or a language (like Dart or TypeScript) that compiles to JavaScript.
block angular-parts
:marked
Angular for Dart is published as the `angular2` package, which
(like many other Dart packages) is available via the Pub tool.
block modules-in-dart
.callout.is-helpful
header Dart difference: Modules are compilation units or packages
:marked
In this guide, the term _module_ refers to a Dart compilation unit, such
as a library, or a package. (If a Dart file has no `library` or `part`
directive, then that file itself is a library and thus a compilation
unit.) For more information about compilation units, see
the chapter on "Libraries and Scripts" in the
[Dart Language Specification](https://www.dartlang.org/docs/spec/).
block modules-are-optional
//- N/A
block export-qualifier
.callout.is-helpful
header Dart difference: Public names are exported by default
:marked
Contrary to TypeScript, a Dart library always exports all names and
declarations in its **public** namespace, making explicit `export`
qualifiers unnecessary.
When we say that a module _exports_ a declaration, we mean that the
declaration is _public_. For more details about name spaces and export
statements, see the section on "Exports" in the
[Dart Language Specification](https://www.dartlang.org/docs/spec/).
block ts-import
//- N/A
block angular-library-modules
:marked
Angular ships as a collection of libraries within the
[**angular2**](https://pub.dartlang.org/packages/angular2) package.
block angular-imports
+makeExcerpt('app/app.component.ts', 'import')
block ts-decorator
:marked
Annotations often have configuration parameters.
The `@Component` annotation takes parameters to provide the
information Angular needs to create and present the component and its view.
Here are a few of the possible `@Component` parameters:
block dart-bool
.callout.is-helpful
header Dart difference: Only true is true
:marked
In Dart, **the only value that is true is the boolean value `true`**; all
other values are false. JavaScript and TypeScript, in contrast, treat values
such as 1 and most non-null objects as true. For this reason, the JavaScript
and TypeScript versions of this app can use just `selectedHero` as the value
of the `*ngIf` expression. The Dart version must use a boolean operator such
as `!=` instead.

View File

@ -1,9 +0,0 @@
extends ../../../ts/_cache/guide/attribute-directives.jade
block includes
include ../_util-fns
block highlight-directive-1
:marked
We begin by importing the Angular `core`.
Then we define the directive metadata by means of the `@Directive` annotation.

View File

@ -1 +0,0 @@
include ../../../_includes/_ts-temp

View File

@ -1 +0,0 @@
extends ../cheatsheet

View File

@ -1,34 +0,0 @@
extends ../../../ts/_cache/guide/component-styles.jade
block includes
include ../_util-fns
//- TODO: consider adding material equivalent to TS Appendices 1 & 2 if relevant.
block style-url
:marked
Note that the URLs in `styleUrls` are relative to the component.
block module-bundlers
//- TODO: determine if an equivalent of the TS material is relevant for Dart.
//- Leaving empty for now.
block css-import-url
:marked
In *this* case the URL is relative to the CSS file into which we are importing.
.alert.is-important
:marked
URLs are currently not interpreted in this way, see
[issue 8518](https://github.com/angular/angular/issues/8518).
Until this issue is fixed, absolute package-reference style URLs must
be given as is illustrated below.
block module-id
:marked
Thankfully, this is the default interpretation of relative URLs in
Angular2 for Dart:
+makeExcerpt('app/quest-summary.component.ts', 'urls', '')

View File

@ -1,156 +0,0 @@
extends ../../../ts/_cache/guide/dependency-injection.jade
block includes
include ../_util-fns
- var _thisDot = '';
block ctor-syntax
.l-sub-section
:marked
We also leveraged Dart's constructor syntax for declaring parameters and
initializing properties simultaneously.
block register-provider-ngmodule
:marked
Before we do, let's see an example of provider registration during bootstrapping:
+makeExcerpt('app/main.1.ts (discouraged)', 'bootstrap-discouraged', '')
:marked
The injector now knows about our `HeroService`.
An instance of our `HeroService` will be available for injection across our entire application.
Of course we can't help wondering about that comment telling us not to do it this way.
It *will* work. It's just not a best practice.
The bootstrap provider option is intended for configuring and overriding Angular's own
preregistered services, such as its routing support.
The preferred approach is to register application providers in application components.
Because the `HeroService` is used within the *Heroes* feature area &mdash;
and nowhere else &mdash; the ideal place to register it is in the top-level `HeroesComponent`.
block ngmodule-vs-component
:marked
Look at the `providers` part of the `@Component` annotation.
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.
block injectable-not-always-needed-in-ts
//- The [Angular Dart Transformer](https://github.com/dart-lang/angular2/wiki/Transformer)
//- generates static code to replace the use of dart:mirrors. It requires that types be
//- identified as targets for static code generation. Generally this is achieved
//- by marking the class as @Injectable (though there are other mechanisms).
block always-include-paren
:marked
Always write `@Injectable()`, not just `@Injectable`.
A metadata annotation must be either a reference to a
compile-time constant variable or a call to a constant
constructor such as `Injectable()`.
If we forget the parentheses, the analyzer will complain:
"Annotation creation must have arguments". If we try to run the
app anyway, it won't work, and the console will say
"expression must be a compile-time constant".
block real-logger
.l-sub-section
:marked
A real implementation would probably use the
[logging package](https://pub.dartlang.org/packages/logging).
block provider-shorthand
:marked
This is actually a shorthand expression for a provider registration
that creates a new instance of the
[Provider](../api/angular2.core/Provider-class.html) class:
block provider-ctor-args
- var _secondParam = 'named parameter, such as <code>useClass</code>'
:marked
We supply two arguments (or more) to the `Provider` constructor.
block dart-diff-const-metadata
.callout.is-helpful
header Dart difference: Constants in metadata
:marked
In Dart, the value of a metadata annotation must be a compile-time constant.
For that reason, we can't call functions to get values
to use within an annotation.
Instead, we use constant literals or constant constructors.
For example, a TypeScript program will use the
object literal `{ provide: Logger, useClass: BetterLogger }`.
A Dart annotation would instead use the constant value
`const Provider(Logger, useClass: BetterLogger)`.
block dart-diff-const-metadata-ctor
.callout.is-helpful
header Dart difference: Constants in metadata
:marked
Because Dart annotations must be compile-time constants,
`useValue` is often used with string or list literals.
However, `useValue` works with any constant object.
To create a class that can provide constant objects,
ensure all its instance variables are `final`,
and give it a `const` constructor.
Create a constant instance of the class by using `const` instead of `new`.
// - var stylePattern = { otl: /(useValue.*\))/gm };
// +makeExample('dependency-injection/dart/lib/providers_component.dart','providers-9','', stylePattern)(format='.')
block non-class-dep-eg
span string, list, map, or maybe a function.
block config-obj-maps
| . They can be
| <b><a href="https://api.dartlang.org/stable/dart-core/Map-class.html">Map</a></b>
| literals
block what-should-we-use-as-token
:marked
But what should we use as the token?
While we _could_ use **[Map][]**, we _should not_ because (like
`String`) `Map` is too general. Our app might depend on several maps, each
for a different purpose.
[Map]: https://api.dartlang.org/stable/dart-core/Map-class.html
.callout.is-helpful
header Dart difference: Interfaces are valid tokens
:marked
In TypeScript, interfaces don't work as provider tokens.
Dart doesn't have this limitation;
every class implicitly defines an interface,
so interface names are just class names.
`Map` is a *valid* token even though it's the name of an abstract class;
it's just *unsuitable* as a token because it's too general.
block dart-map-alternative
:marked
As an alternative to using a configuration `Map`, we can define
a custom configuration class:
+makeExcerpt('lib/app_config.dart (alternative config)','config-alt')
:marked
Defining a configuration class has a few benefits. One key benefit
is strong static checking: we'll be warned early if we misspell a property
name or assign it a value of the wrong type.
The Dart [cascade notation][cascade] (`..`) provides a convenient means of initializing
a configuration object.
If we use cascades, the configuration object can't be declared `const` and
we can't use a [value provider](#value-provider).
A solution is to use a [factory provider](#factory-provider).
We illustrate this next. We also show how to provide and inject the
configuration object in our top-level `AppComponent`:
[cascade]: https://www.dartlang.org/docs/dart-up-and-running/ch02.html#cascade
+makeExcerpt('lib/app_component.dart','providers')
+makeExcerpt('lib/app_component.dart','ctor')

View File

@ -1,20 +0,0 @@
extends ../../../ts/_cache/guide/displaying-data.jade
block includes
include ../_util-fns
- var _iterableUrl = 'https://api.dartlang.org/stable/dart-core/Iterable-class.html';
- var _boolean = 'boolean';
block hero-class
:marked
We've defined a class with a constructor, two properties (`id` and `name`),
and a `toString()` method.
block final-code
+makeTabs(`displaying-data/dart/lib/app_component.dart,
displaying-data/dart/lib/hero.dart,
displaying-data/dart/pubspec.yaml,
displaying-data/dart/web/index.html,
displaying-data/dart/web/main.dart`,
',,,,final',
'lib/app_component.dart, lib/hero.dart, pubspec.yaml, web/index.html, web/main.dart')

View File

@ -1,656 +0,0 @@
include ../_util-fns
:marked
Weve all used a form to log in, submit a help request, place an order, book a flight,
schedule a meeting, and perform countless other data entry tasks.
Forms are the mainstay of business applications.
Any seasoned web developer can slap together an HTML form with all the right tags.
It's more challenging to create a cohesive data entry experience that guides the
user efficiently and effectively through the workflow behind the form.
*That* takes design skills that are, to be frank, well out of scope for this chapter.
It also takes framework support for
**two-way data binding, change tracking, validation, and error handling**
... which we shall cover in this chapter on Angular forms.
We will build a simple form from scratch, one step at a time. Along the way we'll learn:
- How to build an Angular form with a component and template
- The `ngModel` two-way data binding syntax for reading and writing values to input controls
- The `ngControl` directive to track the change state and validity of form controls
- The special CSS classes that `ngControl` adds to form controls and how to use them to provide strong visual feedback
- How to display validation errors to users and enable/disable form controls
- How to share information across controls with template reference variables
Run the <live-example></live-example>.
.l-main-section
:marked
## Template-driven forms
Many of us will build forms by writing templates in the Angular
template syntax
<!-- link to ./template-syntax.html -->
with the form-specific directives and techniques described in this chapter.
.l-sub-section
:marked
That's not the only way to create a form but it's the way we'll cover in this chapter.
:marked
We can build almost any form we need with an Angular template—login forms, contact forms, pretty much any business form.
We can lay out the controls creatively, bind them to data, specify validation rules and display validation errors,
conditionally enable or disable specific controls, trigger built-in visual feedback, and much more.
It will be pretty easy because Angular handles many of the repetitive, boilerplate tasks we'd
otherwise wrestle with ourselves.
We'll discuss and learn to build a template-driven form that looks like this:
figure.image-display
img(src="/resources/images/devguide/forms/hero-form-1.png" width="400px" alt="Clean Form")
:marked
Here at the *Hero Employment Agency* we use this form to maintain personal information about the
heroes in our stable. Every hero needs a job. It's our company mission to match the right hero with the right crisis!
Two of the three fields on this form are required. Required fields have a green bar on the left to make them easy to spot.
If we delete the hero name, the form displays a validation error in an attention-grabbing style:
figure.image-display
img(src="/resources/images/devguide/forms/hero-form-2.png" width="400px" alt="Invalid, Name Required")
:marked
Note that the submit button is disabled, and the "required" bar to the left of the input control changed from green to red.
.l-sub-section
:marked
We'll customize the colors and location of the "required" bar with standard CSS.
:marked
We'll build this form in small steps:
1. Create the `Hero` model class.
1. Create the component that controls the form.
1. Create a template with the initial form layout.
1. Add the **ngModel** directive to each form input control.
1. Add the **ngControl** directive to each form input control.
1. Add custom CSS to provide visual feedback.
1. Show and hide validation error messages.
1. Handle form submission with **ngSubmit**.
1. Change the form's display after submission.
:marked
## Setup
Create a new project folder (`angular_forms`) and create 3 files:
`pubspec.yaml`, `web/index.html`, and `web/main.dart`.
(These files should be familiar from the
[QuickStart](../quickstart.html).) Put these contents in the files:
+makeTabs('forms/dart/pubspec.yaml, forms/dart/web/index.html, forms/dart/web/main.dart', ',initial,', 'pubspec.yaml, web/index.html, web/main.dart')
:marked
So that the code can run,
let's create a stub for the `<hero-form>` component.
Create a new directory called `lib`.
In it, put a file called `hero_form_component.dart`
with the following code:
+makeExample('forms/dart/lib/hero_form_component_initial.dart', null, 'lib/hero_form_component.dart')
:marked
The app should now run, but it won't do anything interesting.
Let's add some data.
## Create the Hero model class
As users enter form data, we'll capture their changes and update an instance of a model.
We can't lay out the form until we know what the model looks like.
A model can be as simple as a "property bag" that holds facts about a thing of application importance.
That describes well our `Hero` class with its three required fields (`id`, `name`, `power`)
and one optional field (`alterEgo`).
In the `lib` directory, add a file called `hero.dart`
with the following code:
+makeExample('forms/dart/lib/hero.dart', 'all', 'lib/hero.dart')
:marked
It's an anemic model with few requirements and no behavior. Perfect for our demo.
The `alterEgo` is optional, so the constructor lets us omit it: note the
`[]` in `[this.alterEgo]`.
We can create a new hero like this:
+makeExample('forms/dart/lib/hero.dart', 'newhero')(format=".")
:marked
.l-main-section
:marked
## Create a form component
An Angular form has two parts: an HTML-based template and a code-based component to handle data and user interactions.
We begin with the component because it states, in brief, what the Hero editor can do.
Edit `hero_form_component.dart`, replacing all of its contents
with the following code:
+makeExample('forms/dart/lib/hero_form_component.dart', null, 'lib/hero_form_component.dart')
:marked
Theres nothing special about this component, nothing form-specific,
nothing to distinguish it from any component we've written before.
Understanding this component requires only the Angular concepts covered in previous chapters.
1. The code imports a standard set of symbols from the Angular library.
1. The `@Component` selector value of "hero-form" means we can drop this form in a parent template with a `<hero-form>` tag.
1. The `moduleId` property sets the base for module-relative URLs such as the `templateUrl`.
1. The `templateUrl` property points to a separate file for template HTML called `hero_form_component.html`.
1. We defined dummy data for `model` and `powers`, as befits a demo.
Down the road, we can inject a data service to get and save real data
or perhaps expose these properties as inputs and outputs
<!--TODO: link to (./template-syntax.html#inputs-outputs) --> for binding to a
parent component. None of this concerns us now, and these future changes won't affect our form.
1. We threw in a `diagnostic` property to return a
string describing our model.
It'll help us see what we're doing during our development; we've left ourselves a cleanup note to discard it later.
Why isn't the template inline in the component file?
Inline templates can be nice when they are short,
but most form templates aren't short. Dart files generally aren't the best place to
write (or read) large stretches of HTML, and few editors are much help with files that have a mix of HTML and code.
It's also nice to have short files with a clear and obvious purpose.
We made a good choice to put the HTML template elsewhere. Let's write it.
<!-- NOTE: I deleted the Dart equivalent of "Revise the app.ts"
because we started with example-specific index.html & main.dart
files. -->
.l-main-section
:marked
## Create an initial HTML form template
Create a new file under `lib` called `hero_form_component.html`,
and put the following template code in it:
+makeExample('forms/dart/lib/hero_form_component_initial.html', null, 'lib/hero_form_component.html')
:marked
That is plain old HTML 5. We're presenting two of the `Hero` fields, `name` and `alterEgo`, and
opening them up for user input in input boxes.
The Name `<input>` control has the HTML5 `required` attribute;
the Alter Ego `<input>` control does not because `alterEgo` is optional.
We've got a Submit button at the bottom with some classes on it.
**We are not using Angular yet**. There are no bindings. No extra directives. Just layout.
The `container`,`form-group`, `form-control`, and `btn` classes are [Bootstrap](http://getbootstrap.com/) CSS. Purely cosmetic.
We're using Bootstrap to gussy up our form.
Hey, what's a form without a little style!
.callout.is-important
header Angular forms do not require a style library
:marked
Angular makes no use of the `container`, `form-group`, `form-control`, and `btn` classes or
the styles of any external library. Angular apps can use any CSS library
... or none at all.
:marked
Let's add the stylesheet.
1. Download the Bootstrap stylesheet from
https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css,
and put it in the `web` directory.
2. Edit `web/index.html`, adding a link to `bootstrap.min.css`:
- var stylePattern = { otl: /(&lt;link rel.*$)/gm };
+makeExample('forms/dart/web/index.html', 'bootstrap-and-script', 'web/index.html (excerpt)', stylePattern)(format=".")
[PENDING: runnable now? Remind about pub get? remind them to look in the browser console]
.l-main-section
:marked
## Add powers with ***ngFor**
Our hero must choose one super power from a fixed list of Agency-approved powers.
We maintain that list internally (in `HeroFormComponent`).
We'll add a `select` to our
form and bind the options to the `powers` list using `ngFor`,
a technique used before in [Displaying Data](./displaying-data.html).
Add the following HTML *immediately below* the Alter Ego group.
+makeExample('forms/dart/lib/hero_form_component_ngmodel_ngfor.html', 'powers', 'lib/hero_form_component.html (excerpt)')(format=".")
:marked
This code repeats the `<option>` tag for each power in the list of powers.
The `p` template input variable is a different power in each iteration;
we display its name using the interpolation syntax with the double curly braces.
.l-main-section#ngModel
:marked
## Two-way data binding with ***ngModel**
Running the app right now would be disappointing.
figure.image-display
img(src="/resources/images/devguide/forms/hero-form-3.png" width="400px" alt="Early form with no binding")
:marked
We don't see hero data because we are not binding to the `Hero` yet.
We know how to do that from earlier chapters.
[Displaying Data](./displaying-data.html) taught us property binding.
[User Input](./user-input.html) showed us how to listen for DOM events with an
event binding and how to update a component property with the displayed value.
Now we need to display, listen, and extract at the same time.
We could use the techniques we already know, but
instead we'll introduce something new: the `NgModel` directive, which
makes binding the form to the model super easy.
Find the `<input>` tag for Name and update it like this:
+makeExample('forms/dart/lib/hero_form_component_ngmodel_ngfor.html', 'ngModel-1', 'lib/hero_form_component.html (excerpt)')(format=".")
.l-sub-section
:marked
We added a diagnostic interpolation after the input tag
so we can see what we're doing.
We left ourselves a note to throw it way when we're done.
:marked
Focus on the binding syntax: `[(ngModel)]="..."`.
If we ran the app right now and started typing in the Name input box,
adding and deleting characters, we'd see them appearing and disappearing
from the interpolated text.
At some point it might look like this.
figure.image-display
img(src="/resources/images/devguide/forms/ng-model-in-action.png" width="400px" alt="ngModel in action")
:marked
The diagnostic is evidence that values really are flowing from the input box to the model and
back again. **That's two-way data binding!**
Let's add similar `[(ngModel)]` bindings to Alter Ego and Hero Power.
We'll ditch the input box binding message
and add a new binding at the top to the component's `diagnostic` property.
Then we can confirm that two-way data binding works *for the entire Hero model*.
After revision, the core of the form should have three `[(ngModel)]` bindings that
look much like this:
+makeExample('forms/dart/lib/hero_form_component_ngmodel2.html', 'ngModel-2', 'lib/hero_form_component.html (excerpt)')(format=".")
:marked
If we ran the app right now and changed every Hero model property, the form might look like this:
figure.image-display
img(src="images/ng-model-in-action-2.png" width="500px" alt="ngModel in super action")
:marked
The diagnostic near the top of the form
confirms that our changes to the values are reflected in the model.
**Delete the diagnostic binding.** It has served its purpose.
.l-sub-section
h3 Inside [(ngModel)]
:marked
*This section is an optional deep dive into [(ngModel)]. Not interested? Skip ahead!*
The punctuation in the binding syntax, <span style="font-family:courier"><b>[()]</b></span>, is a good clue to what's going on.
A property binding makes data flow from the model to a target property on screen.
We identify that target property by surrounding its name in brackets, <span style="font-family:courier"><b>[]</b></span>.
This is a one-way data binding **from the model to the view**.
An event binding makes data flow from the target property onscreen to the model.
We identify that target property by surrounding its name in parentheses, <span style="font-family:courier"><b>()</b></span>.
This is a one-way data binding in the opposite direction **from the view to the model**.
No wonder Angular uses the combined punctuation, <span style="font-family:courier"><b>[()]</b></span>,
to signify a two-way data binding and a **flow of data in both directions**.
In fact, we can break the `NgModel` binding into its two separate modes
as in this rewrite of the Name `<input>` binding:
+makeExample('forms/dart/lib/hero_form_component_ngmodelchange.html', 'ngModel-3', 'lib/hero_form_component.html (excerpt)')(format=".")
:marked
<br>The property binding should feel familiar. The event binding might seem strange.
The name `ngModelChange` specifies an event property of the `NgModel` directive.
When Angular sees a binding target in the form <span style="font-family:courier">[(x)]</span>,
it expects the `x` directive to have an `x` input property and an `xChange` output property.
The other oddity is the template expression, `model.name = $event`.
We're used to seeing an `$event` object coming from a DOM event.
The `ngModelChange` property doesn't produce a DOM event; it's an Angular `EventEmitter`
property that returns the input box value when it fires—which is precisely what
we should assign to the model's `name` property.
Nice to know but is it practical? `[(ngModel)]` is usually what we want, but
we might split the binding when the event handling has to do something special
such as debounce or throttle the keystrokes.
<!-- TODO: Add the following once template-syntax.html exists.
Learn more about `NgModel` and other template syntax in the
[Template Syntax](./template-syntax.html) chapter.
-->
.l-main-section
:marked
## Track change-state and validity with **ngControl**
A form isn't just about data binding. We'd also like to know the state of the controls on our form.
The `NgControl` directive keeps track of control state for us.
.callout.is-helpful
header NgControl requires Form
:marked
The `NgControl` is one of a family of `NgForm` directives that can only be applied to
a control within a `<form`> tag.
:marked
Our application can ask an `NgControl` instance whether
the user touched the control, the value changed, or the value is valid.
`NgControl` doesn't just track state; it updates the control with special
Angular CSS classes, such as `ng-valid` or `ng-invalid`.
We can use those class names to change the appearance of the
control and make messages appear or disappear.
We'll explore those effects soon. Right now
let's **add `ngControl`to all three form controls**,
starting with the Name input box.
+makeExample('forms/dart/lib/hero_form_component_ngcontrol.html', 'ngControl-1', 'lib/hero_form_component.html (excerpt)')(format=".")
:marked
Be sure to assign a unique name to each `ngControl` directive.
.l-sub-section
:marked
Angular registers controls under their `ngControl` names
with the `NgForm` directive.
We didn't add the `NgForm` directive explicitly but it's here;
we'll talk about it [later in this chapter](#ng-form).
.l-main-section
h2 Add custom CSS for visual feedback
p.
<code>NgControl</code> doesn't just track state. It updates the control to
have three classes that describe the control's state.
table
tr
th State
th Class if true
th Class if false
tr
td Control has been visited
td <code>ng-touched</code>
td <code>ng-untouched</code>
tr
td Control's value has changed
td <code>ng-dirty</code>
td <code>ng-pristine</code>
tr
td Control's value is valid
td <code>ng-valid</code>
td <code>ng-invalid</code>
:marked
Let's add a temporary [template reference variable](./template-syntax.html#ref-vars) named **spy**
to the "Name" `<input>` tag and use the spy to display those classes.
+makeExample('forms/dart/lib/hero_form_component_spy.html', 'ngControl-2', 'lib/hero_form_component.html (excerpt)')(format=".")
:marked
Now run the app, and look at the Name input box.
Follow the next four steps *precisely*:
1. Look but don't touch.
1. Click inside the name box, then click outside it.
1. Add slashes to the end of the name.
1. Erase the name.
The classes are displayed as follows:
1. `form-control ng-untouched ng-valid ng-pristine` (initial state)
1. `form-control ng-valid ng-pristine ng-touched` (after clicking)
1. `form-control ng-valid ng-touched ng-dirty` (after changing)
1. `form-control ng-touched ng-dirty ng-invalid` (after erasing)
figure.image-display
img(src="/resources/images/devguide/forms/control-state-transitions-anim.gif" alt="Control State Transition")
:marked
The (`ng-valid` | `ng-invalid`) pair are the most interesting to us, because we want to send a
strong visual signal when the values are bad. We also want to mark required fields.
We can do both at the same time with a colored bar on the left of the input box:
figure.image-display
img(src="/resources/images/devguide/forms/validity-required-indicator.png" width="400px" alt="Invalid Form")
:marked
We achieve this effect by adding two styles to a new `forms.css` file
(under `web/`).
+makeExample('forms/dart/web/forms.css', null, 'web/forms.css')
:marked
These styles select for the two Angular validity classes and the HTML 5 "required" attribute.
To add the styles to the app,
update the `<head>` of `index.html` to link to `forms.css`.
- var stylePattern = { otl: /(.*forms.css.*$)/gm };
+makeExample('forms/dart/web/index.html', 'styles', 'web/index.html (excerpt)', stylePattern)(format=".")
:marked
## Show and hide validation error messages
We can do better.
The Name input box is required. Clearing it turns the bar red. That says *something* is wrong but we
don't know *what* is wrong or what to do about it.
We can leverage the `ng-invalid` class to reveal a helpful message.
Here's the way it should look when the user deletes the name:
figure.image-display
img(src="/resources/images/devguide/forms/name-required-error.png" width="400px" alt="Name required")
:marked
To achieve this effect we extend the `<input>` tag with
1. a [template reference variable](./template-syntax.html#ref-vars)
1. the "*is required*" message in a nearby `<div>` which we'll display only if the control is invalid.
Here's an example of adding an error message to the "name" input box:
- var stylePattern = { otl: /(#name=&quot;form&quot;)|(.*div.*$)|(Name is required)/gm };
+makeExample('forms/dart/lib/hero_form_component.html', 'name-with-error-msg', 'lib/hero_form_component.html (excerpt)', stylePattern)(format=".")
:marked
We need a template reference variable to access the input box's Angular control from within the template.
Here we created a variable called `name` and gave it the value "ngForm".
Angular recognizes that syntax and sets the `name` variable
to the `Control` object identified by the `ngControl` directive that,
not coincidentally, we called "name".
We bind the `Control` object's `valid` property to the element's `hidden` property.
While the control is valid, the message is hidden;
if it becomes invalid, the message is revealed.
<a id="ng-form"></a>
.l-sub-section
h3 The NgForm directive
:marked
Recall from the previous section that `ngControl` registered this input box with the
`NgForm` directive as "name".
We didn't add the [NgForm](../api/angular2.common/NgForm-class.html) directive
explicitly. Angular added it surreptitiously, wrapping it around the `<form>` element.
The `NgForm` directive supplements the `<form>` element with additional features.
It collects controls (elements identified by an `ngControl` directive)
and monitors their properties including their validity.
It has its own `valid` property, which is true only if every contained
control is valid.
In this example, we are pulling the "name" control out of its `controls` collection
and assigning it to a template reference variable so that we can
access the control's properties—such as the control's own `valid` property.
:marked
The Alter Ego is optional so we can leave that be.
Hero Power selection is required.
We can add the same kind of error handling to the `<select>` if we want,
but it's not imperative because the selection box already constrains the
power to valid values.
.l-main-section
:marked
## Submit the form with **ngSubmit**
The user should be able to submit this form after filling it in.
The Submit button at the bottom of the form
does nothing on its own, but it will
trigger a form submit because of its type (`type="submit"`).
A "form submit" is meaningless at the moment. To make it meaningful,
we'll update the `<form>` tag with another Angular directive, `NgSubmit`,
and bind it to the `HeroFormComponent.onSubmit()` method:
+makeExample('forms/dart/lib/hero_form_component.html', 'ngSubmit', 'lib/hero_form_component.html (excerpt)')(format=".")
:marked
We slipped in something extra there at the end! We defined a
template reference variable, **`#heroForm`**, and initialized it with the value "ngForm".
The variable `heroForm` is now a handle to the `NgForm` as we [discussed earlier](#ng-form)
with respect to `ngControl`, although this time we have a reference to the form
rather than a control.
We'll bind the form's overall validity via
the `heroForm` variable to the button's `disabled` property
using an event binding. Here's the code:
+makeExample('forms/dart/lib/hero_form_component.html', 'submit-button', 'lib/hero_form_component.html (excerpt)')(format=".")
:marked
If we run the application now, we find that the button is enabled.
It doesn't do anything useful yet but it's alive.
Now if we delete the Name, we violate the "required" rule, which
is duly noted in the error message.
The Submit button is also disabled.
Not impressed? Think about it for a moment. What would we have to do to
wire the button's enable/disabled state to the form's validity without Angular's help?
For us, it was as simple as:
1. Define a template reference variable on the (enhanced) form element.
2. Refer to that variable in a button many lines away.
.l-main-section
:marked
## Toggle two form regions (extra credit)
Submitting the form isn't terribly dramatic at the moment.
.l-sub-section
:marked
An unsurprising observation for a demo. To be honest,
jazzing it up won't teach us anything new about forms.
But this is an opportunity to exercise some of our newly won
binding skills.
If you aren't interested, go ahead and skip to this chapter's conclusion.
:marked
Let's do something more strikingly visual.
Let's hide the data entry area and display something else.
Start by wrapping the form in a `<div>` and binding
its `hidden` property to the `HeroFormComponent.submitted` property.
+makeExample('forms/dart/lib/hero_form_component.html', 'edit-div', 'lib/hero_form_component.html (excerpt)')(format=".")
:marked
The main form is visible from the start because the
`submitted` property is false until we submit the form,
as the following code from `hero_form_component.dart` shows:
+makeExample('forms/dart/lib/hero_form_component.dart', 'submitted')(format=".")
:marked
When we click the Submit button, the `submitted` flag becomes true and the form disappears
as planned.
Now the app needs to show something else while the form is in the submitted state.
Add the following block of HTML below the `<div>` wrapper we just wrote:
+makeExample('forms/dart/lib/hero_form_component.html', 'submitted', 'lib/hero_form_component.html (excerpt)')(format=".")
:marked
There's our hero again, displayed read-only with interpolation bindings.
This slug of HTML appears only while the component is in the submitted state.
The HTML includes an Edit button whose click event is bound to an expression
that clears the `submitted` flag.
When we click the Edit button, this block disappears and the editable form reappears.
That's as much drama as we can muster for now.
.l-main-section
:marked
## Conclusion
The Angular form discussed in this chapter takes advantage of the following framework features to provide support for data modification, validation, and more:
- An Angular HTML form template.
- A form component class with a `Component` decorator.
- The `ngSubmit` directive for handling the form submission.
- Template reference variables such as `#heroForm`, `#name`, `#p`, and `#spy`.
- The `ngModel` directive for two-way data binding.
- The `ngControl` directive for validation and form element change tracking.
- The reference variables `valid` property on input controls to check if a control is valid and show/hide error messages.
- Property binding to disable the submit button when the form is invalid.
- Custom CSS classes that provide visual feedback to users about required invalid controls.
Heres the code for the final version of the application:
+makeTabs(
`forms/dart/lib/hero_form_component.dart,
forms/dart/lib/hero_form_component.html,
forms/dart/lib/hero.dart,
forms/dart/pubspec.yaml,
forms/dart/web/index.html,
forms/dart/web/main.dart,
forms/dart/web/forms.css`,
'no-todo,,all,,,,',
`lib/hero_form_component.dart,
lib/hero_form_component.html,
lib/hero.dart,
pubspec.yaml,
web/index.html,
web/main.dart,
web/forms.css`)

View File

@ -1,4 +0,0 @@
extends ../glossary
block includes
include ../_util-fns

View File

@ -1,4 +0,0 @@
extends ../../../ts/_cache/guide/hierarchical-dependency-injection.jade
block includes
include ../_util-fns

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,5 +0,0 @@
extends ../../../ts/_cache/guide/index.jade
block includes
include ../_util-fns
- var _angular_io = 'website';

View File

@ -1,4 +0,0 @@
extends ../../../ts/_cache/guide/learning-angular.jade
block includes
include ../_util-fns

View File

@ -1,18 +0,0 @@
extends ../../../ts/_cache/guide/lifecycle-hooks.jade
block includes
include ../_util-fns
block other-angular-subsystems
:marked
The router, for instance, also has its own [router lifecycle
hooks](router.html#router-lifecycle-hooks) that allow us to tap into
specific moments in route navigation.
A parallel can be drawn between `ngOnInit` and `routerOnActivate`. Both are
prefixed so as to avoid collision, and both run right when a component is
being initialized.
block tick-methods
:marked
The `LoggerService.tick()` postpones the log update
for one turn of the browser's update cycle ... and that's just long enough.

View File

@ -1 +0,0 @@
include ../../../_includes/_ts-temp

View File

@ -1,8 +0,0 @@
:marked
## NPM and NPM packages
TypeScript and JavaScript developers rely on the Node Package Manager (*npm*) to install
angular and other libraries.
Dart applications do not use *npm* to load 3rd party modules so
the chapter describing that process is irrelevant to Dart developers.

View File

@ -1,11 +0,0 @@
extends ../../../ts/_cache/guide/pipes.jade
block includes
include ../_util-fns
block pure-change
:marked
Angular executes a *pure pipe* only when it detects a *pure change* to the input value.
In Angular Dart, a *pure change* results only from a change in object reference
(given that [everything is an object in Dart](https://www.dartlang.org/docs/dart-up-and-running/ch02.html#important-concepts)).

View File

@ -1,6 +0,0 @@
include ../_util-fns
:marked
This advanced guide hasn't yet been written.
To learn the basics of routing, read the Tour of Heroes tutorial:
[Routing Around the App](../tutorial/toh-pt5.html).

View File

@ -1 +0,0 @@
include ../../../_includes/_ts-temp

View File

@ -1,82 +0,0 @@
extends ../../../ts/_cache/guide/server-communication.jade
block includes
include ../_util-fns
- var _Http = 'BrowserClient';
- var _Angular_Http = 'Dart <code>BrowserClient</code>'
- var _httpUrl = 'https://pub.dartlang.org/packages/http'
- var _Angular_http_library = 'Dart <a href="' + _httpUrl + '"><b>http</b></a> library'
block demos-list
:marked
- [HTTP client: Tour of Heroes](#http-client)
- [JSONP client: Wikipedia to fetch data from a service that does not support CORS (**under development**)](#cors)
block http-providers
:marked
Actually, it is unnecessary to include `BrowserClient` in the list of providers.
***But*** as is mentioned in the *Angular Dart Transformer* [wiki page][ng2dtri],
the template compiler _generates_ dependency injection code, hence all the
identifiers used in DI have to be collected by the Angular transformer
so that the libraries containing these identifiers can be transformed.
Unless special steps are taken, Dart libraries like `http`
are not transformed. To ensure that the `BrowserClient` identifier is available
for DI, we must add a `resolved_identifiers` parameter to the `angular2`
transformer in `pubspec.yaml`:
[ng2dtri]: https://github.com/dart-lang/angular2/wiki/Transformer#resolved_identifiers
- var stylePattern = { pnk: /(resolved_identifiers:|Browser.*)/gm, otl: /(- angular2:)|(transformers:)/g };
+makeExcerpt('pubspec.yaml', 'transformers', null, stylePattern)
block getheroes-and-addhero
:marked
The hero service `getHeroes()` and `addHero()` asynchronous methods return the
[`Future`](https://api.dartlang.org/stable/dart-async/Future-class.html)
values of the current hero list and the newly added hero,
respectively. The hero list component methods of the same name specifying
the actions to be taken when the asynchronous method calls succeed or fail.
For more information about `Future`s, consult any one
of the [articles](https://www.dartlang.org/articles/) on asynchronous
programming in Dart, or the tutorial on
[_Asynchronous Programming: Futures_](https://www.dartlang.org/docs/tutorials/futures/).
block parse-json
:marked
The response data are in JSON string form.
We must parse that string into Objects which we do by calling
the `JSON.decode()` method from the `dart:convert` library.
block error-handling
//- TODO: describe `_handleError`?
block hlc-error-handling
:marked
Back in the `HeroListComponent`, we wrapped our call to
`!{_priv}heroService.getHeroes()` in a `try` clause. When an exception is
caught, the `errorMessage` variable &mdash; which we've bound conditionally in the
template &mdash; gets assigned to.
block hero-list-comp-add-hero
:marked
Back in the `HeroListComponent`, we see that *its* `addHero()`
awaits for the *service's* asynchronous `addHero()` to return, and when it does,
the new hero is added to the `heroes` list for presentation to the user.
block wikipedia-jsonp+
:marked
Wikipedia offers a modern `CORS` API and a legacy `JSONP` search API.
.alert.is-important
:marked
The remaining content of this section is coming soon.
In the meantime, consult the
[example sources](https://github.com/angular-examples/server-communication)
to see how to access Wikipedia via its `JSONP` API.
block redirect-to-web-api
:marked
To achieve this, we have Angular inject an in-memory web API server
instance as a provider for the `BrowserClient`. This is possible because
the in-memory web API server class extends `BrowserClient`.

View File

@ -1,161 +0,0 @@
include ../_util-fns
a#develop-locally
:marked
Setting up a new Angular project is quick and easy,
using everyday Dart tools.
.l-main-section#sdk
:marked
## Prerequisites: Dart SDK & Dartium
If you don't already have the **Dart SDK** and **Dartium**, get them.
We recommend using the WebStorm IDE, as well.
[Get Started](https://webdev.dartlang.org/guides/get-started) tells you how to get the tools.
a#webstorm
:marked
## Creating a project in WebStorm
Using WebStorm to create an app is simple, once you complete some one-time setup.
1. Launch WebStorm.
1. If you haven't already done so,
[configure Dart support in WebStorm](https://webdev.dartlang.org/tools/webstorm#configuring-dart-support).
1. Choose **Create New Project** from the welcome screen,
or **File > New > Project...** from the menu. A dialog appears.
1. Select **Dart** from the list on the left.
1. Set the location and template:
1. In the **Location** input field, check that
the project folder is where you want it.
1. Also in the **Location** field,
change the name of the project from `untitled` to whatever you choose, such as `angular_quickstart`.
1. Make sure that **Generate sample content** is checked.
1. Select **Angular QuickStart Example** from the list.
<br>
The form should look similar to the following:
<br>
<img src="images/create-ng2-project.png" alt="A screenshot of the New Project dialog, with the specified selections" style="border:1px solid">
1. Click **Create**.
WebStorm takes several seconds to analyze the sources and do other housekeeping.
This only happens once. After that, you'll be able to use WebStorm for the usual IDE tasks,
including running the app.
For more information on using WebStorm, see
[Installing and Using WebStorm](https://webdev.dartlang.org/tools/webstorm).
a#cli
:marked
## Using a template from the command line
[Stagehand](http://stagehand.pub/) gives you
command-line access to the same templates that WebStorm uses.
Assuming the Dart SDK and
[pub cache `bin` directory](https://www.dartlang.org/tools/pub/cmd/pub-global#running-a-script-from-your-path)
are in your path, here's how you use Stagehand to create an Angular project.
1. Install or update stagehand:
<br>
`pub global activate stagehand`
1. Create a directory for your project:
<br>
`mkdir angular_quickstart; cd angular_quickstart`
1. Use Stagehand with the appropriate template to create a skeleton app:
<br>
`stagehand web-angular-quickstart`
<br>
**Note:**
Examples in this guide and tutorial are based on the QuickStart example,
which is in the `web-angular-quickstart` template.
A more general-purpose template is `web-angular`.
1. Get the app's dependencies: <br>
`pub get`
1. Run the app: <br>
`pub serve`
.l-main-section#seed
:marked
## What's in the QuickStart example?
The <live-example name="quickstart">QuickStart example</live-example>
contains the following core files:
+makeTabs(`
quickstart/ts/app/app.component.ts,
quickstart/ts/app/main.ts,
quickstart/ts/index.html,
quickstart/ts/styles.css,
quickstart/dart/pubspec.yaml`,
',,,quickstart,',
`app/app.component.ts,
app/main.ts,
index.html,
styles.css (excerpt),
pubspec.yaml`)
:marked
These files are organized as follows:
.filetree
.file angular_quickstart
.children
.file lib
.children
.file app_component.dart
.file web
.children
.file main.dart
.file index.html
.file styles.css
.file pubspec.yaml
:marked
All guides and cookbooks have _at least these core files_. Each file has a distinct purpose and evolves independently as the application grows.
style td, th {vertical-align: top}
table(width="100%")
col(width="20%")
col(width="80%")
tr
th File
th Purpose
tr
td <code>lib/app_component.dart</code>
td
:marked
Defines `<my-app>`, the **root** component of what will become a tree of nested components
as the application evolves.
tr
td <code>web/main.dart</code>
td
:marked
Bootstraps the application to run in the browser.
tr
td <code>web/index.html</code>
td
:marked
Contains the `<my-app>` tag in its `<body>`.
This is where the app lives!
tr
td <code>web/styles.css</code>
td
:marked
A set of styles used throughout the app.
tr
td <code>pubspec.yaml</code>
td
:marked
The file that describes this Dart package (the app) and its dependencies.
For example, it specifies the **angular2** and **browser** packages as dependencies,
as well as the **angular2** transformer.
.l-sub-section
:marked
### Next step
If you're new to Angular, we recommend staying on the [learning path](learning-angular.html).

View File

@ -1,9 +0,0 @@
extends ../../../ts/_cache/guide/structural-directives.jade
block includes
include ../_util-fns
block unless-intro
:marked
Creating a directive is similar to creating a component.
Here is how we begin:

View File

@ -1 +0,0 @@
include ../../../_includes/_ts-temp

View File

@ -1,118 +0,0 @@
extends ../../../ts/_cache/guide/template-syntax.jade
block includes
include ../_util-fns
- var _JavaScript = 'Dart';
- var __chaining_op = '<code>;</code>';
- var __new_op = '<code>new</code> or <code>const</code>';
- var mapApiRef = 'https://api.dartlang.org/stable/dart-core/Map-class.html';
- var __objectAsMap = '<b><a href="' + mapApiRef + '">Map</a></b>'
block notable-differences
:marked
* no support for Dart string interpolation; for example,
instead of `"'The title is $title'"`, you must use
`"'The title is ' + title"`
* no support for the bitwise operators `|` and `&`
* new [template expression operators](#expression-operators), such as `|`
block template-expressions-cannot
:marked
Perhaps more surprising, template expressions cant refer to static
properties, nor to top-level variables or functions, such as `window` or
`document` from `dart:html`. They cant directly call `print` or functions
imported from `dart:math`. They are restricted to referencing members of
the expression context.
block statement-context
:marked
Template statements cant refer to static properties on the class, nor to
top-level variables or functions, such as `window` or `document` from
`dart:html`. They cant directly call `print` or functions imported from
`dart:math`.
block dart-type-exceptions
.callout.is-helpful
header Dart difference: Type exceptions
:marked
In checked mode, if the template expression result type and the target
property type are not assignment compatible, then a type exception will
be thrown.
For information on checked mode, see [Important concepts](https://www.dartlang.org/docs/dart-up-and-running/ch02.html#important-concepts)
in the Dart language tour.
block dart-type-exception-example
.callout.is-helpful
header Dart difference: Type exception example
:marked
In checked mode, the code above will result in a type exception:
`String` isn't a subtype of `Hero`.
block style-property-name-dart-diff
.callout.is-helpful
header Dart difference: Style property names
:marked
While [camelCase](glossary.html#camelcase) and
[dash-case](glossary.html#dash-case) style property naming schemes are
equivalent in Angular Dart, only dash-case names are recognized by the
`dart:html` [CssStyleDeclaration][CssSD] methods `getPropertyValue()`
and `setProperty()`. Hence, we recommend only using dash-case for style
property names.
[CssSD]: https://api.dartlang.org/stable/dart-html/CssStyleDeclaration-class.html
block dart-no-truthy-falsey
.callout.is-helpful
header Dart difference: No truthy/falsey values
:marked
In checked mode, Dart expects Boolean values
(those with type `bool`) to be either `true` or `false`.
Even in production mode, the only value Dart treats as `true` is
the value `true`; all other values are `false`.
TypeScript and JavaScript, on the other hand, treat
many values (including non-null objects) as true.
A TypeScript Angular program, for example, often has code like
`*ngIf="currentHero"` where a Dart program has code like
`*ngIf="currentHero != null"`.
When converting TypeScript code to Dart code, watch out for
true/false problems. For example, forgetting the `!= null`
can lead to exceptions in checked mode, such as
"EXCEPTION: type 'Hero' is not a subtype of type 'bool' of 'boolean expression'".
For more information, see
[Booleans](https://www.dartlang.org/docs/dart-up-and-running/ch02.html#booleans)
in the [Dart language tour](https://www.dartlang.org/docs/dart-up-and-running/ch02.html).
block remember-the-brackets
//- Changed from RED to ORANGE, since this isn't so dire a situation in Dart.
.callout.is-important
header Remember the brackets!
:marked
Dont make the mistake of writing `ngIf="currentHero"`!
That syntax assigns the *string* value `"currentHero"` to `ngIf`,
which won't work because `ngIf` expects a `bool`.
block dart-safe-nav-op
.callout.is-helpful
header Dart difference: ?. is a Dart operator
:marked
The safe navigation operator (`?.`) is part of the Dart language.
It's considered a template expression operator because
Angular supports `?.` even in TypeScript and JavaScript apps.
block json-pipe
//- TODO: explain alternative in Dart
//- {{ e | json }} --> {{ e }}
//- which causes the object's toString() method to be invoked.
//- Of course the `json` pipe can be used if the instance supports
//- JSON encoding.
block null-deref-example
:marked
Dart throws an exception, and so does Angular:
code-example(format="nocode").
EXCEPTION: The null object does not have a getter 'firstName'.
block safe-op-alt
//- N/A

View File

@ -1 +0,0 @@
include ../../../_includes/_ts-temp

View File

@ -1 +0,0 @@
include ../../../_includes/_ts-temp

View File

@ -1 +0,0 @@
include ../../../_includes/_ts-temp

View File

@ -1,254 +0,0 @@
include ../_util-fns
:marked
When the user clicks a link, pushes a button, or enters text
we want to know about it. These user actions all raise DOM events.
In this chapter we learn to bind to those events using the Angular
event binding syntax.
Run the <live-example></live-example>.
:marked
## Binding to user input events
We can use [Angular event bindings](./template-syntax.html#event-binding)
to respond to [any DOM event](https://developer.mozilla.org/en-US/docs/Web/Events).
The syntax is simple. We surround the DOM event name with
parentheses and assign a quoted template statement to it.
As an example, here's an event binding that implements a click handler:
+makeExample('user-input/dart/lib/click_me_component.dart', 'click-me-button')(format=".", language="html")
<a id="click"></a>
:marked
The `(click)` to the left of the equal sign identifies the button's click event as the **target of the binding**.
The text within quotes on the right is the **template statement** in which we
respond to the click event by calling the component's `onClickMe` method. A [template statement](./template-syntax.html#template-statements) is a subset
of Dart with restrictions and a few added tricks.
When writing a binding we must be aware of a template statement's **execution context**.
The identifiers appearing within a statement belong to a specific context object.
That object is usually the Angular component that controls the template ... which it definitely is
in this case because that snippet of HTML belongs to the following component:
+makeExample('user-input/dart/lib/click_me_component.dart', 'click-me-component', 'web/click_me_component.dart')(format=".")
:marked
When the user clicks the button, Angular calls the component's `onClickMe` method.
.l-main-section
:marked
## Get user input from the $event object
We can bind to all kinds of events. Let's bind to the keyup event of an input box and replay
what the user types back onto the screen.
This time we'll (1) listen to an event and (2) grab the user's input.
+makeExample('user-input/dart/lib/keyup_components.dart', 'key-up-component-1-template', 'web/keyup_components.dart (template v.1)')(format=".")
:marked
Angular makes an event object available in the **`$event`** variable,
which we pass to the component's `onKey()` method.
The user data we want is in that variable somewhere.
.callout.is-important
header $event vs. \$event
:marked
Templates in Dart files need a `\` in front of the `$`.
If the template is in an HTML file, use `$event` instead of `\$event`.
+makeExample('user-input/dart/lib/keyup_components.dart', 'key-up-component-1-class-no-type', 'web/keyup_components.dart (class v.1)')(format=".")
:marked
The shape of the `$event` object is determined by whatever raises the event.
The `keyup` event comes from the DOM, so `$event` must be a [standard DOM event object](https://developer.mozilla.org/en-US/docs/Web/API/Event).
The `$event.target` gives us an
[`HTMLInputElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement), which
has a `value` property that contains our user input data.
The `onKey()` component method is where we extract the user's input
from the event object, adding that input to the list of user data that we're accumulating in the component's `values` property.
We then use [interpolation](./template-syntax.html#interpolation)
to display the accumulating `values` property back on screen.
Enter the letters "abc", and then backspace to remove them.
Here's what the UI displays:
code-example().
a | ab | abc | ab | a | |
figure.image-display
img(src='/resources/images/devguide/user-input/keyup1-anim.gif' alt="key up 1")
<a id="keyup1"></a>
.l-sub-section
:marked
We cast the `$event` as an `any` type, which means we've abandoned strong typing
to simplify our code. We generally prefer the strong typing that Dart affords.
We can rewrite the method, casting to HTML DOM objects like this.
+makeExample('user-input/dart/lib/keyup_components.dart', 'key-up-component-1-class', 'web/keyup_components.dart (class v.1 - strongly typed )')(format=".")
:marked
<br>Strong typing reveals a serious problem with passing a DOM event into the method:
too much awareness of template details, too little separation of concerns.
We'll address this problem in our next try at processing user keystrokes.
:marked
.l-main-section
:marked
## Get user input from a template reference variable
There's another way to get the user data without the `$event` variable.
Angular has a syntax feature called [**template reference variables**](./template-syntax.html#ref-vars).
These variables grant us direct access to an element.
We declare a template reference variable by preceding an identifier with a hash/pound character (#).
Here's an example of using a template reference variable
to implement a clever keystroke loopback in an ultra-simple template.
+makeExample('user-input/dart/lib/loop_back_component.dart', 'loop-back-component', 'web/loop_back_component.dart')(format=".")
:marked
We've declared a template reference variable named `box` on the `<input>` element.
The `box` variable is a reference to the `<input>` element itself, which means we can
grab the input element's `value` and display it
with interpolation between `<p>` tags.
The template is completely self contained. It doesn't bind to the component,
and the component does nothing.
Type in the input box, and watch the display update with each keystroke. *Voila!*
figure.image-display
img(src='/resources/images/devguide/user-input/keyup-loop-back-anim.gif' alt="loop back")
.l-sub-section
:marked
**This won't work at all unless we bind to an event**.
Angular only updates the bindings (and therefore the screen)
if we do something in response to asynchronous events such as keystrokes.
That's why we bind the `keyup` event to a statement that does ... well, nothing.
We're binding to the number 0, the shortest statement we can think of.
That is all it takes to keep Angular happy. We said it would be clever!
:marked
That template reference variable is intriguing. It's clearly easier to get to the textbox with that
variable than to go through the `$event` object. Maybe we can rewrite our previous
keyup example so that it uses the variable to get the user's input. Let's give it a try.
+makeExample('user-input/dart/lib/keyup_components.dart', 'key-up-component-2' ,'web/keyup_components.dart (v2)')(format=".")
:marked
That sure seems easier.
An especially nice aspect of this approach is that our component code gets clean data values from the view.
It no longer requires knowledge of the `$event` and its structure.
<a id="key-event"></a>
.l-main-section
:marked
## Key event filtering (with `key.enter`)
Perhaps we don't care about every keystroke.
Maybe we're only interested in the input box value when the user presses Enter, and we'd like to ignore all other keys.
When we bind to the `(keyup)` event, our event handling statement hears *every keystroke*.
We could filter the keys first, examining every `$event.keyCode`, and update the `values` property only if the key is Enter.
Angular can filter the key events for us. Angular has a special syntax for keyboard events.
We can listen for just the Enter key by binding to Angular's `keyup.enter` pseudo-event.
Only then do we update the component's `values` property. (In this example,
the update happens inside the event binding statement. A better practice
would be to put the update code in the component.)
+makeExample('user-input/dart/lib/keyup_components.dart', 'key-up-component-3' ,'web/keyup_components.dart (v3)')(format=".")
:marked
Here's how it works.
figure.image-display
img(src='/resources/images/devguide/user-input/keyup3-anim.gif' alt="key up 3")
.l-main-section
:marked
## On blur
Our previous example won't transfer the current state of the input box if the user mouses away and clicks
elsewhere on the page. We update the component's `values` property only when the user presses Enter
while the focus is inside the input box.
Let's fix that by listening to the input box's blur event as well.
+makeExample('user-input/dart/lib/keyup_components.dart', 'key-up-component-4' ,'web/keyup_components.dart (v4)')(format=".")
.l-main-section
:marked
## Put it all together
We learned how to [display data](./displaying-data.html) in the previous chapter.
We've acquired a small arsenal of event binding techniques in this chapter.
Let's put it all together in a micro-app
that can display a list of heroes and add new heroes to that list.
The user can add a hero by first typing in the input box and then
pressing Enter, clicking the Add button, or clicking elsewhere on the page.
figure.image-display
img(src='/resources/images/devguide/user-input/little-tour-anim.gif' alt="Little Tour of Heroes")
:marked
Below is the "Little Tour of Heroes" component.
We'll call out the highlights after we bask briefly in its minimalist glory.
+makeExample('user-input/dart/lib/little_tour_component.dart', 'little-tour', 'web/little_tour_component.dart')(format=".")
:marked
We've seen almost everything here before. A few things are new or bear repeating.
### Use template variables to refer to elements
The `newHero` template variable refers to the `<input>` element.
We can use `newHero` from any sibling or child of the `<input>` element.
Getting the element from a template variable makes the button click handler
simpler. Without the variable, we'd have to use a fancy CSS selector
to find the input element.
### Pass values, not elements
We could have passed the `newHero` into the component's `addHero` method.
But that would require `addHero` to pick its way through the `<input>` DOM element,
something we learned to dislike in our first try at a [keyup component](#keyup1).
Instead, we grab the input box *value* and pass *that* to `addHero`.
The component knows nothing about HTML or the DOM, which is the way we like it.
### Keep template statements simple
We bound `(blur)` to *two* Dart statements.
We like the first one, which calls `addHero`.
We do not like the second one, which assigns an empty string to the input box value.
The second statement exists for a good reason. We have to clear the input box after adding the new hero to the list.
The component has no way to do that itself because it has no access to the
input box (our design choice).
Although the example *works*, we are rightly wary of Dart in HTML.
Template statements are powerful. We're supposed to use them responsibly.
Complex Dart in HTML is irresponsible.
Should we reconsider our reluctance to pass the input box into the component?
There should be a better third way. And there is, as we'll see when we learn about `NgModel` in the [Forms](forms.html) chapter.
.l-main-section
:marked
## Source code
Here is all the code we talked about in this chapter.
+makeTabs(`
user-input/dart/lib/click_me_component.dart,
user-input/dart/lib/keyup_components.dart,
user-input/dart/lib/loop_back_component.dart,
user-input/dart/lib/little_tour_component.dart
`,'',
`click_me_component.dart,
keyup_components.dart,
loop_back_component.dart,
little_tour_component.dart`)
.l-main-section
:marked
## Summary
We've mastered the basic primitives for responding to user input and gestures.
As powerful as these primitives are, they are a bit clumsy for handling
large amounts of user input. We're operating down at the low level of events when
we should be writing two-way bindings between data entry fields and model properties.
Angular has a two-way binding called `NgModel`, which we'll learn about
in the `Forms` chapter.

View File

@ -1 +0,0 @@
include ../../../_includes/_ts-temp

View File

@ -1,43 +0,0 @@
.l-main-section
h2 Get Help Using Angular
p.
We have an incredible community of developers who are passionate about solving problems.
We recommend some of the following methods to get help with Angular.
.l-sub-section
h3 Angular Google Group
ol
li.
Search the archive first.
It's likely that your question has already been answered.
li.
To avoid the spam moderation queue,
don't include code directly in your email. (See #3)
li.
Link to a live code example that demonstrates your problem or question,
so you'll get an answer faster.
<a href="http://plnkr.co/edit/jISF8yxbVGmarpBbqsYW?p=preview">Use this template</a>.
li.
If you get help, help others. Good karma rulez!
a(href="https://groups.google.com/forum/#!forum/angular" class="button button-primary" md-button) View the Google Group
.l-sub-section
h3 Angular Chat Room
p Talk in real time with other Angular developers on Slack.
a(href="http://dartlang-slack.herokuapp.com/" class="button button-primary" md-button) Join Dart on Slack
a(href="https://dartlang.slack.com/messages/angular2/" class="button button-primary" md-button) Go to the Angular channel
.l-sub-section
h3 Report an Issue
p If you run into an issue or have a feature request, you can create a new issue on our GitHub repository.
a(href="https://github.com/angular/angular/issues" class="button button-primary" md-button) Report an Issue

View File

@ -1,7 +0,0 @@
include ../../ts/latest/index
div.c12.l-space-top-3
.alert.is-helpful.
Not using Angular 2 yet? Perhaps you need the
<a href="https://www.dartdocs.org/documentation/angular/latest" target="_blank">API docs for the original AngularDart</a>.

View File

@ -1,5 +0,0 @@
extends ../../ts/_cache/quickstart.jade
block includes
include _util-fns
- var _on_Plunkr = '';

View File

@ -1,40 +0,0 @@
// TODO: don't duplicate text from /docs/ts/latest/resources.jade
.l-main-section
h2 Victor Savkin's Blog Posts
ul
li: a(href="http://victorsavkin.com/post/137821436516/managing-state-in-angular-2-applications") Managing State in Angular 2 Applications
li <a href="http://victorsavkin.com/post/114168430846/two-phases-of-angular-2-applications">Two Phases of Angular 2 Applications</a>
li <a href="http://angularjs.blogspot.com/2015/03/forms-in-angular-2.html">Forms in Angular 2</a>
li <a href="http://victorsavkin.com/post/110170125256/change-detection-in-angular-2">Change detection</a>
li <a href="http://victorsavkin.com/post/108837493941/better-support-for-functional-programming-in">Functional programming </a>
li <a href="http://victorsavkin.com/post/102965317996/angular-2-bits-unified-dependency-injection">Dependency injection</a>
.l-main-section
h2 <span class="icon-play-circle-outline"></span> Videos
h4 Intro Videos
ul
li <a href="https://www.youtube.com/watch?v=uD6Okha_Yj0">Building a Todo App</a> by David East
li <a href="https://www.youtube.com/watch?v=4C4bmDOV5hk">Angular 2 Forms</a> by David East
h4 ng-conf
ul
li Playlist <a href="https://www.youtube.com/watch?v=QHulaj5ZxbI&index=1&list=PLOETEcp3DkCoNnlhE-7fovYvqwVPrRiY7">of ng-conf 2015 videos</a>.
li <a href="https://www.youtube.com/watch?v=QHulaj5ZxbI&list=PLOETEcp3DkCoNnlhE-7fovYvqwVPrRiY7">Day 1 Keynote</a>: a broad overview of Angular 2, migration, and where we are headed.
li <a href="https://www.youtube.com/watch?v=-dMBcqwvYA0&index=21&list=PLOETEcp3DkCoNnlhE-7fovYvqwVPrRiY7">Day 2 Keynote</a>: Misko and Rado do a deep-dive on Angular 2 details.
li <a href="https://www.youtube.com/watch?v=AbunztfV5vU&index=6&list=PLOETEcp3DkCoNnlhE-7fovYvqwVPrRiY7">Creating Container Components with Web Components in Angular</a>: Kara Erickson &amp; Rachael L Moore.
li <a href="https://www.youtube.com/watch?v=jvKGQSFQf10&index=31&list=PLOETEcp3DkCoNnlhE-7fovYvqwVPrRiY7">Change Detection Reinvented</a>: Why Angular 2 change detection is fast out of the box and options for developers to make it even faster.
h4 ng-europe
ul
li Oct 2014 <a href="https://www.youtube.com/watch?v=lGdnh8QSPPk&list=PLhc_bKwZngxW_ZlY0NkaGkvKpiA_pzcZ-">playlist of ng-europe videos on Angular 2</a> and the future of Angular.
.l-main-section
h2 <span class="icon-content-copy"></span> API Design Docs &amp; Notes
ul
li <a href="https://drive.google.com/open?id=0B7GYXx6a6d8QR3lTT1J3MEpRSlE&authuser=0">Best Practices</a>
li <a href="https://drive.google.com/open?id=0BxgtL8yFJbacUnUxc3l5aTZrbVk&authuser=0">API Design Docs</a>
li <a href="https://drive.google.com/open?id=0BxgtL8yFJbacMEZDc2NtWS1VZ1k&authuser=0">Meeting Notes</a>
li <a href="https://drive.google.com/open?id=0BxgtL8yFJbaceGc2dlhGQnMzYXc&authuser=0">Presentations</a>
li <a href="http://goo.gl/sj0Nk1">More...</a>

View File

@ -1 +0,0 @@
include ../../_includes/styleguide/_styleguide

View File

@ -1,38 +0,0 @@
{
"index": {
"title": "Tutorial: Tour of Heroes",
"navTitle": "Introduction",
"intro": "The Tour of Heroes tutorial takes us through the steps of creating an Angular application in Dart.",
"nextable": true
},
"toh-pt1": {
"title": "The Hero Editor",
"intro": "We build a simple hero editor",
"nextable": true
},
"toh-pt2": {
"title": "Master/Detail",
"intro": "We build a master/detail page with a list of heroes",
"nextable": true
},
"toh-pt3": {
"title": "Multiple Components",
"intro": "We refactor the master/detail view into separate components",
"nextable": true
},
"toh-pt4": {
"title": "Services",
"intro": "We create a reusable service to manage our hero data calls",
"nextable": true
},
"toh-pt5": {
"title": "Routing",
"intro": "We add the Angular Component Router and learn to navigate among the views",
"nextable": true
},
"toh-pt6": {
"title": "HTTP",
"intro": "We convert our service and components to use Angular's HTTP service",
"nextable": true
}
}

View File

@ -1,5 +0,0 @@
extends ../../../ts/_cache/tutorial/index.jade
block includes
include ../_util-fns

View File

@ -1,185 +0,0 @@
include ../_util-fns
:marked
Every story starts somewhere. Our story starts where the [QuickStart](../quickstart.html) ends.
:marked
Follow the "QuickStart" steps. They provide the prerequisites, the folder structure,
and the core files for our Tour of Heroes.
<!--
TODO: Recommend stagehand?
-->
Copy the "QuickStart" code to a new folder and rename the folder `angular_tour_of_heroes`.
We should have the following structure:
.filetree
.file angular_tour_of_heroes
.children
.file lib
.children
.file app_component.dart
.file web
.children
.file index.html
.file main.dart
.file styles.css
.file pubspec.yaml
.p &nbsp;
.callout.is-helpful
header Source code
:marked
Run the <live-example></live-example> for this part.
:marked
## Keep the app compiling and running
We want to start the Dart compiler, have it watch for changes, and start our server. We'll do this by typing
code-example(language="sh" class="code-shell").
pub serve
:marked
This command runs the compiler in watch mode, starts the server,
and keeps the app running while we continue to build the Tour of Heroes.
.l-main-section
:marked
## Show our Hero
We want to display Hero data in our app.
Let's add two properties to our `AppComponent`, a `title` property for the application name and a `hero` property
for a hero named "Windstorm".
+makeExample('toh-1/dart-snippets/app_component_snippets_pt1.dart', 'app-component-1', 'app_component.dart (AppComponent class)')(format=".")
:marked
Now we update the template in the `@Component` annotation with data bindings to these new properties.
+makeExample('toh-1/dart-snippets/app_component_snippets_pt1.dart', 'show-hero')
:marked
The browser should refresh and display our title and hero.
The double curly braces tell our app to read the `title` and `hero` properties from the component and render them.
This is the "interpolation" form of one-way data binding.
.l-sub-section
:marked
Learn more about interpolation in the [Displaying Data chapter](../guide/displaying-data.html).
:marked
### Hero object
At the moment, our hero is just a name. Our hero needs more properties.
Let's convert the `hero` from a literal string to a class.
Create a `Hero` class with `id` and `name` properties.
Keep this near the top of the `app_component.dart` file for now.
+makeExample('toh-1/dart/lib/app_component.dart', 'hero-class-1', 'app_component.dart (Hero class)')(format=".")
:marked
Now that we have a `Hero` class, lets refactor our components `hero` property to be of type `Hero`.
Then initialize it with an id of `1` and the name, "Windstorm".
+makeExample('toh-1/dart/lib/app_component.dart', 'hero-property-1', 'app_component.dart (hero property)')(format=".")
:marked
Because we changed the hero from a string to an object,
we update the binding in the template to refer to the heros `name` property.
+makeExample('toh-1/dart-snippets/app_component_snippets_pt1.dart', 'show-hero-2')
:marked
The browser refreshes and continues to display our heros name.
### Adding more HTML
Displaying a name is good, but we want to see all of our heros properties.
Well add a `<div>` for our heros `id` property and another `<div>` for our heros `name`.
+makeExample('toh-1/dart-snippets/app_component_snippets_pt1.dart', 'show-hero-properties')
:marked
Uh oh, our template string is getting long. We better take care of that to avoid the risk of making a typo in the template.
### Multi-line template strings
We could make a more readable template with string concatenation
but that gets ugly fast, it is harder to read, and
it is easy to make a spelling error. Instead,
lets take advantage of the template strings feature
in Dart to maintain our sanity.
Change the quotes around the template to triple quotes and
put the `<h1>`, `<h2>` and `<div>` elements on their own lines.
+makeExample('toh-1/dart-snippets/app_component_snippets_pt1.dart', 'multi-line-strings', 'app_component.dart (AppComponent\'s template)')(format=".")
// omit back-tick warning
.l-main-section
:marked
## Editing Our Hero
We want to be able to edit the hero name in a textbox.
Refactor the hero name `<label>` with `<label>` and `<input>` elements as shown below:
+makeExample('toh-1/dart-snippets/app_component_snippets_pt1.dart', 'editing-Hero', 'app_component.dart (input element)')(format=".")
:marked
We see in the browser that the heros name does appear in the `<input>` textbox.
But something doesnt feel right.
When we change the name, we notice that our change
is not reflected in the `<h2>`. We won't get the desired behavior
with a one-way binding to `<input>`.
### Two-Way Binding
We intend to display the name of the hero in the `<input>`, change it,
and see those changes wherever we bind to the heros name.
In short, we want two-way data binding.
Lets update the template to use the **`ngModel`** built-in directive for two-way binding.
.l-sub-section
:marked
Learn more about `ngModel` in the
[Forms](../guide/forms.html#ngModel) and
[Template Syntax](../guide/template-syntax.html#ngModel) chapters.
:marked
Replace the `<input>` with the following HTML
code-example(language="html").
&lt;input [(ngModel)]="hero.name" placeholder="name">
:marked
The browser refreshes. We see our hero again. We can edit the heros name and
see the changes reflected immediately in the `<h2>`.
.l-main-section
:marked
## The Road Weve Travelled
Lets take stock of what weve built.
* Our Tour of Heroes uses the double curly braces of interpolation (a form of one-way data binding)
to display the application title and properties of a `Hero` object.
* We wrote a multi-line template using Dart's template strings to make our template readable.
* We can both display and change the heros name after adding a two-way data binding to the `<input>` element
using the built-in `ngModel` directive.
* The `ngModel` directive also propagates changes to every other binding of the `hero.name`.
<!-- TODO:
add [Run the live example for part 1](https://tour-of-heroes.firebaseapp.com/toh1/)
-->
Here's the complete `app_component.dart` as it stands now:
+makeExample('toh-1/dart/lib/app_component.dart', 'pt1', 'app_component.dart')
.l-main-section
:marked
## The Road Ahead
Our Tour of Heroes only displays one hero and we really want to display a list of heroes.
We also want to allow the user to select a hero and display their details.
Well learn more about how to retrieve lists, bind them to the
template, and allow a user to select it in the
[next tutorial chapter](./toh-pt2.html).

View File

@ -1,302 +0,0 @@
include ../_util-fns
:marked
Our story needs more heroes.
Well expand our Tour of Heroes app to display a list of heroes,
allow the user to select a hero, and display the heros details.
Run the <live-example></live-example> for this part.
Lets take stock of what well need to display a list of heroes.
First, we need a list of heroes. We want to display those heroes in the views template,
so well need a way to do that.
.l-main-section
:marked
## Where We Left Off
Before we continue with Part 2 of the Tour of Heroes,
lets verify we have the following structure after [Part 1](./toh-pt1.html).
If not, well need to go back to Part 1 and figure out what we missed.
.filetree
.file angular_tour_of_heroes
.children
.file lib
.children
.file app_component.dart
.file web
.children
.file index.html
.file main.dart
.file styles.css
.file pubspec.yaml
:marked
### Keep the app compiling and running
We want to start the Dart compiler, have it watch for changes, and start our server. We'll do this by typing
code-example(language="sh" class="code-shell").
pub serve
:marked
This will keep the application running while we continue to build the Tour of Heroes.
.l-main-section
:marked
## Displaying Our Heroes
### Creating heroes
Lets create a list of ten heroes at the bottom of `app_component.dart`.
+makeExample('toh-2/dart/lib/app_component.dart', 'hero-array', 'app_component.dart (hero list)')(format=".")
:marked
The `mockHeroes` list is of type `Hero`, the class defined in part one,
to create a list of heroes.
We aspire to fetch this list of heroes from a web service, but lets take small steps
first and display mock heroes.
### Exposing heroes
Lets create a public property in `AppComponent` that exposes the heroes for binding.
+makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'hero-array-1', 'app_component.dart (hero list property)')
.l-sub-section
:marked
We could have defined the heroes list here in this component class.
But we know that ultimately well get the heroes from a data service.
Because we know where we are heading, it makes sense to separate the hero data
from the class implementation from the start.
:marked
### Displaying heroes in a template
Our component has `heroes`. Lets create an unordered list in our template to display them.
Well insert the following chunk of HTML below the title and above the hero details.
+makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'heroes-template-1', 'app_component.dart (heroes template)')(format=".")
:marked
Now we have a template that we can fill with our heroes.
### Listing heroes with ngFor
We want to bind the list of `heroes` in our component to our template, iterate over them,
and display them individually.
Well need some help from Angular to do this. Lets do this step by step.
First modify the `<li>` tag by adding the built-in directive `*ngFor`.
+makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'heroes-ngfor-1', 'app_component.dart (ngFor)')
.alert.is-critical
:marked
The leading asterisk (`*`) in front of `ngFor` is a critical part of this syntax.
.l-sub-section
:marked
The (`*`) prefix to `ngFor` indicates that the `<li>` element and its children
constitute a master template.
The `ngFor` directive iterates over the `heroes` list returned by the `AppComponent.heroes` property
and stamps out instances of this template.
The quoted text assigned to `ngFor` means
“*take each hero in the `heroes` list, store it in the local `hero` variable,
and make it available to the corresponding template instance*”.
The `let` keyword before "hero" identifies `hero` as a template input variable.
We can reference this variable within the template to access a heros properties.
Learn more about `ngFor` and template input variables in the
[Displaying Data](../guide/displaying-data.html#ngFor) and
[Template Syntax](../guide/template-syntax.html#ngFor) chapters.
:marked
Now we insert some content between the `<li>` tags
that uses the `hero` template variable to display the heros properties.
+makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'ng-for', 'app_component.dart (ngFor template)')(format=".")
:marked
When the browser refreshes, we see a list of heroes!
### Styling our heroes
Our list of heroes looks pretty bland.
We want to make it visually obvious to a user which hero we are hovering over and which hero is selected.
Lets add some styles to our component by setting the `styles` argument of the `@Component` annotation
to the following CSS classes:
+makeExample('toh-2/dart/lib/app_component.dart', 'styles', 'app_component.dart (styles)')(format=".")
:marked
Notice that we again use the triple-quote notation for multi-line strings.
That's a lot of styles! We can put them inline as shown here, or we can move them out to their own file which will make it easier to code our component.
We'll do this in a later chapter. For now let's keep rolling.
When we assign styles to a component they are scoped to that specific component.
Our styles will only apply to our `AppComponent` and won't "leak" to the outer HTML.
Our template for displaying the heroes should now look like this:
+makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'heroes-styled', 'app_component.dart (styled heroes)')
.l-main-section
:marked
## Selecting a Hero
We have a list of heroes and we have a single hero displayed in our app.
The list and the single hero are not connected in any way.
We want the user to select a hero from our list, and have the selected hero appear in the details view.
This UI pattern is widely known as "master-detail".
In our case, the master is the heroes list and the detail is the selected hero.
Lets connect the master to the detail through a `selectedHero` component property bound to a click event.
### Click event
We modify the `<li>` by inserting an Angular event binding to its click event.
+makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'selectedHero-click', 'app_component.dart (template excerpt)')(format=".")
:marked
Focus on the event binding
code-example.
(click)="onSelect(hero)"
:marked
The parentheses identify the `<li>` elements `click` event as the target.
The expression to the right of the equal sign calls the `AppComponent` method, `onSelect()`,
passing the template input variable `hero` as an argument.
Thats the same `hero` variable we defined previously in the `ngFor`.
.l-sub-section
:marked
Learn more about Event Binding in the
[User Input](../guide/user-input.html) and
[Templating Syntax](../guide/template-syntax.html#event-binding) chapters.
:marked
### Add the click handler
Our event binding refers to an `onSelect` method that doesnt exist yet.
Well add that method to our component now.
What should that method do? It should set the components selected hero to the hero that the user clicked.
Our component doesnt have a “selected hero” yet either. Well start there.
### Expose the selected hero
We no longer need the static `hero` property of the `AppComponent`.
**Replace** it with this simple `selectedHero` property:
+makeExample('toh-2/dart/lib/app_component.dart', 'selected-hero', 'app_component.dart (selectedHero)')
:marked
Weve decided that none of the heroes should be selected before the user picks a hero so
we wont initialize the `selectedHero` as we were doing with `hero`.
Now **add an `onSelect` method** that sets the `selectedHero` property to the `hero` the user clicked.
+makeExample('toh-2/dart/lib/app_component.dart', 'on-select', 'app_component.dart (onSelect)')(format=".")
:marked
We will be showing the selected hero's details in our template.
At the moment, it is still referring to the old `hero` property.
Lets fix the template to bind to the new `selectedHero` property.
+makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'selectedHero-details', 'app_component.dart (template excerpt)')(format=".")
:marked
### Hide the empty detail with ngIf
When our app loads we see a list of heroes, but a hero is not selected.
The `selectedHero` is `undefined`.
Thats why we'll see the following error in the browsers console:
code-example(format="nocode").
EXCEPTION: TypeError: Cannot read property 'name' of undefined in [null]
:marked
Remember that we are displaying `selectedHero.name` in the template.
This name property does not exist because `selectedHero` itself is undefined.
We'll address this problem by keeping the hero detail out of the DOM until there is a selected hero.
We wrap the HTML hero detail content of our template with a `<div>`.
Then we add the `ngIf` built-in directive and set it to the `selectedHero` property of our component.
+makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'ng-if', 'app_component.dart (ngIf)')(format=".")
.alert.is-critical
:marked
Remember that the leading asterisk (`*`) in front of `ngIf` is
a critical part of this syntax.
:marked
When there is no `selectedHero`, the `ngIf` directive removes the hero detail HTML from the DOM.
There will be no hero detail elements and no bindings to worry about.
When the user picks a hero, `selectedHero` isn't `null` anymore and
`ngIf` puts the hero detail content into the DOM and evaluates the nested bindings.
.l-sub-section
:marked
`ngIf` and `ngFor` are called “structural directives” because they can change the
structure of portions of the DOM.
In other words, they give structure to the way Angular displays content in the DOM.
Learn more about `ngIf`, `ngFor` and other structural directives in the
[Structural Directives](../guide/structural-directives.html) and
[Template Syntax](../guide/template-syntax.html#directives) chapters.
:marked
The browser refreshes and we see the list of heroes but not the selected hero detail.
The `ngIf` keeps it out of the DOM as long as the `selectedHero` is undefined.
When we click on a hero in the list, the selected hero displays in the hero details.
Everything is working as we expect.
### Styling the selection
We see the selected hero in the details area below but we cant quickly locate that hero in the list above.
We can fix that by applying the `selected` CSS class to the appropriate `<li>` in the master list.
For example, when we select Magneta from the heroes list,
we can make it pop out visually by giving it a subtle background color as shown here.
figure.image-display
img(src='/resources/images/devguide/toh/heroes-list-selected.png' alt="Selected hero")
:marked
Well add a property binding on `class` for the `selected` class to the template. We'll set this to an expression that compares the current `selectedHero` to the `hero`.
The key is the name of the CSS class (`selected`). The value is `true` if the two heroes match and `false` otherwise.
Were saying “*apply the `selected` class if the heroes match, remove it if they dont*”.
+makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'class-selected-1', 'app_component.dart (setting the CSS class)')(format=".")
:marked
Notice in the template that the `class.selected` is surrounded in square brackets (`[]`).
This is the syntax for a **property binding**, a binding in which data flows one way
from the data source (the expression `hero == selectedHero`) to a property of `class`.
+makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'class-selected-2', 'app_component.dart (styling each hero)')(format=".")
.l-sub-section
:marked
Learn more about [property bindings](../guide/template-syntax.html#property-binding)
in the Template Syntax chapter.
:marked
The browser reloads our app.
We select the hero Magneta and the selection is clearly identified by the background color.
figure.image-display
img(src='/resources/images/devguide/toh/heroes-list-1.png' alt="Output of heroes list app")
:marked
We select a different hero and the tell-tale color switches to that hero.
Here's the complete `app_component.dart` as it stands now:
+makeExample('toh-2/dart/lib/app_component.dart', '', 'app_component.dart')
.l-main-section
:marked
## The Road Weve Travelled
Heres what we achieved in this chapter:
* Our Tour of Heroes now displays a list of selectable heroes
* We added the ability to select a hero and show the heros details
* We learned how to use the built-in directives `ngIf` and `ngFor` in a components template
Run the <live-example></live-example> for this part.
### The Road Ahead
Our Tour of Heroes has grown, but its far from complete.
We can't put the entire app into a single component.
We need to break it up into sub-components and teach them to work together
as we learn in the [next chapter](toh-pt3.html).

View File

@ -1,255 +0,0 @@
include ../_util-fns
:marked
Our app is growing.
Use cases are flowing in for reusing components, passing data to components, and creating more reusable assets. Let's separate the heroes list from the hero details and make the details component reusable.
Run the <live-example></live-example> for this part.
.l-main-section
:marked
## Where We Left Off
Before we continue with our Tour of Heroes, lets verify we have the following structure. If not, well need to go back and follow the previous chapters.
.filetree
.file angular_tour_of_heroes
.children
.file lib
.children
.file app_component.dart
.file web
.children
.file index.html
.file main.dart
.file styles.css
.file pubspec.yaml
:marked
### Keep the app compiling and running
We want to start the Dart compiler, have it watch for changes, and start our server. We'll do this by typing
code-example(language="sh" class="code-shell").
pub serve
:marked
This will keep the application running while we continue to build the Tour of Heroes.
## Making a Hero Detail Component
Our heroes list and our hero details are in the same component in the same file.
They're small now but each could grow.
We are sure to receive new requirements for one and not the other.
Yet every change puts both components at risk and doubles the testing burden without benefit.
If we had to reuse the hero details elsewhere in our app,
the heroes list would tag along for the ride.
Our current component violates the
[Single Responsibility Principle](https://blog.8thlight.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html).
It's only a tutorial but we can still do things right &mdash;
especially if doing them right is easy and we learn how to build Angular apps in the process.
Lets break the hero details out into its own component.
### Separating the Hero Detail Component
Add a new file named `hero_detail_component.dart` to the `lib` folder and create `HeroDetailComponent` as follows.
+makeExample('toh-3/dart/lib/hero_detail_component.dart', 'v1', 'lib/hero_detail_component.dart (initial version)')(format=".")
.l-sub-section
:marked
### Naming conventions
We like to identify at a glance which classes are components and which files contain components.
Notice that we have an `AppComponent` in a file named `app_component.dart` and our new
`HeroDetailComponent` is in a file named `hero_detail_component.dart`.
All of our component names end in "Component". All of our component file names end in "_component".
We spell our filenames in lower **underscore case**
(AKA **[snake_case](../guide/glossary.html#snake_case)**) so we don't worry about
case sensitivity on the server or in source control.
<!-- TODO
.l-sub-section
:marked
Learn more about naming conventions in the chapter [Naming Conventions]
:marked
-->
:marked
We begin by importing the Angular `core.dart` file,
so that we can use common types like `@Component` when we create
our component.
We create metadata with the `@Component` annotation where we
specify the selector name that identifies this component's element.
When we finish here, we'll import it into `AppComponent` and create a corresponding `<my-hero-detail>` element.
:marked
#### Hero Detail Template
At the moment, the *Heroes* and *Hero Detail* views are combined in one template in `AppComponent`.
Lets **cut** the *Hero Detail* content from `AppComponent` and **paste** it into the new template property of `HeroDetailComponent`.
We previously bound to the `selectedHero.name` property of the `AppComponent`.
Our `HeroDetailComponent` will have a `hero` property, not a `selectedHero` property.
So we replace `selectedHero` with `hero` everywhere in our new template. That's our only change.
The result looks like this:
+makeExample('toh-3/dart/lib/hero_detail_component.dart', 'template', 'lib/hero_detail_component.dart (template)')(format=".")
:marked
Now our hero detail layout exists only in the `HeroDetailComponent`.
#### Add the *hero* property
Lets add that `hero` property we were talking about to the component class.
+makeExample('toh-3/dart/lib/hero_detail_component.dart', 'hero')
:marked
Uh oh. We declared the `hero` property as type `Hero` but our `Hero` class is over in the `app_component.dart` file.
We have two components, each in their own file, that need to reference the `Hero` class.
We solve the problem by relocating the `Hero` class from `app_component.dart` to its own `hero.dart` file.
+makeExample('toh-3/dart/lib/hero.dart', '', 'lib/hero.dart')(format=".")
:marked
Add the following import statement near the top of **both `app_component.dart` and `hero_detail_component.dart`**.
+makeExample('toh-3/dart/lib/hero_detail_component.dart', 'hero-import')
:marked
#### The *hero* property is an ***input***
The `HeroDetailComponent` must be told what hero to display. Who will tell it? The parent `AppComponent`!
The `AppComponent` knows which hero to show: the hero that the user selected from the list.
The user's selection is in its `selectedHero` property.
We will soon update the `AppComponent` template so that it binds its `selectedHero` property
to the `hero` property of our `HeroDetailComponent`. The binding *might* look like this:
code-example(language="html").
&lt;my-hero-detail [hero]="selectedHero">&lt;/my-hero-detail>
:marked
Notice that the `hero` property is the ***target*** of a property binding &mdash; it's in square brackets to the left of the (=).
Angular insists that we declare a ***target*** property to be an ***input*** property.
If we don't, Angular rejects the binding and throws an error.
.l-sub-section
:marked
We explain input properties in more detail [here](../guide/attribute-directives.html#why-input)
where we also explain why *target* properties require this special treatment and
*source* properties do not.
:marked
There are a couple of ways we can declare that `hero` is an *input*.
We'll do it the way we *prefer*, by annotating the `hero` property with `@Input()`.
+makeExample('toh-3/dart/lib/hero_detail_component.dart', 'inputs')(format=".")
.l-sub-section
:marked
Learn more about `@Input()` in the
[Attribute Directives](../guide/attribute-directives.html#input) chapter.
.l-main-section
:marked
## Refresh the AppComponent
We return to the `AppComponent` and teach it to use the `HeroDetailComponent`.
We begin by importing the `HeroDetailComponent` so we can refer to it.
+makeExample('toh-3/dart/lib/app_component.dart', 'hero-detail-import')(format=".")
:marked
Find the location in the template where we removed the *Hero Detail* content
and add an element tag that represents the `HeroDetailComponent`.
code-example(language="html").
&lt;my-hero-detail>&lt;/my-hero-detail>
.l-sub-section
:marked
*my-hero-detail* is the name we set as the `selector` in the `HeroDetailComponent` metadata.
:marked
The two components won't coordinate until we bind the `selectedHero` property of the `AppComponent`
to the `HeroDetailComponent` element's `hero` property like this:
code-example(language="html").
&lt;my-hero-detail [hero]="selectedHero">&lt;/my-hero-detail>
:marked
The `AppComponent`s template should now look like this
+makeExample('toh-3/dart/lib/app_component.dart', 'hero-detail-template', 'app_component.dart (template)')(format=".")
:marked
Thanks to the binding, the `HeroDetailComponent` should receive the hero from the `AppComponent` and display that hero's detail beneath the list.
The detail should update every time the user picks a new hero.
It's not happening yet!
We click among the heroes. No details. We look for an error in the console of the browser development tools. No error.
It is as if Angular were ignoring the new tag. That's because *it is ignoring the new tag*.
### The *directives* list
A browser ignores HTML tags and attributes that it doesn't recognize. So does Angular.
We've imported `HeroDetailComponent`, we've used it in the template, but we haven't told Angular about it.
We tell Angular about it by listing it in the metadata `directives` list. Let's add that list property to the bottom of the
`@Component` configuration object, immediately after the `template` and `styles` properties.
+makeExample('toh-3/dart/lib/app_component.dart', 'directives')(format=".")
:marked
### It works!
When we view our app in the browser we see the list of heroes.
When we select a hero we can see the selected heros details.
What's fundamentally new is that we can use this `HeroDetailComponent`
to show hero details anywhere in the app.
Weve created our first reusable component!
### Reviewing the App Structure
Lets verify that we have the following structure after all of our good refactoring in this chapter:
.filetree
.file angular_tour_of_heroes
.children
.file lib
.children
.file app_component.dart
.file hero.dart
.file hero_detail_component.dart
.file web
.children
.file index.html
.file main.dart
.file styles.css
.file pubspec.yaml
:marked
Here are the code files we discussed in this chapter.
+makeTabs(`
toh-3/dart/lib/hero_detail_component.dart,
toh-3/dart/lib/app_component.dart,
toh-3/dart/lib/hero.dart
`,'',`
lib/hero_detail_component.dart,
lib/app_component.dart,
lib/hero.dart
`)
.l-main-section
:marked
## The Road Weve Travelled
Lets take stock of what weve built.
* We created a reusable component
* We learned how to make a component accept input
* We learned to bind a parent component to a child component.
* We learned to declare the application directives we need in a `directives` list.
Run the <live-example></live-example> for this part.
.l-main-section
:marked
## The Road Ahead
Our Tour of Heroes has become more reusable with shared components.
We're still getting our (mock) data within the `AppComponent`.
That's not sustainable.
We should refactor data access to a separate service
and share it among the components that need data.
Well learn to create services in the [next tutorial](toh-pt4.html) chapter.

View File

@ -1,400 +0,0 @@
include ../_util-fns
:marked
The Tour of Heroes is evolving and we anticipate adding more components in the near future.
Multiple components will need access to hero data and we don't want to copy and
paste the same code over and over.
Instead, we'll create a single reusable data service and learn to
inject it in the components that need it.
Refactoring data access to a separate service keeps the component lean and focused on supporting the view.
It also makes it easier to unit test the component with a mock service.
Because data services are invariably asynchronous,
we'll finish the chapter with a **!{_Promise}**-based version of the data service.
Run the <live-example></live-example> for this part.
.l-main-section
:marked
## Where We Left Off
Before we continue with our Tour of Heroes, lets verify we have the following structure.
If not, well need to go back and follow the previous chapters.
.filetree
.file angular_tour_of_heroes
.children
.file lib
.children
.file app_component.dart
.file hero.dart
.file hero_detail_component.dart
.file web
.children
.file index.html
.file main.dart
.file styles.css
.file pubspec.yaml
:marked
### Keep the app compiling and running
Open a terminal/console window.
Start the Dart compiler, watch for changes, and start our server by entering the command:
code-example(language="sh" class="code-shell").
pub serve
:marked
The application runs and updates automatically as we continue to build the Tour of Heroes.
## Creating a Hero Service
Our stakeholders have shared their larger vision for our app.
They tell us they want to show the heroes in various ways on different pages.
We already can select a hero from a list.
Soon we'll add a dashboard with the top performing heroes and create a separate view for editing hero details.
All three views need hero data.
At the moment the `AppComponent` defines mock heroes for display.
We have at least two objections.
First, defining heroes is not the component's job.
Second, we can't easily share that list of heroes with other components and views.
We can refactor this hero data acquisition business to a single service that provides heroes, and
share that service with all components that need heroes.
### Create the HeroService
Create a file in the `lib` folder called `hero_service.dart`.
.l-sub-section
:marked
We've adopted a convention in which we spell the name of a service in lowercase followed by `_service`.
If the service name were multi-word, we'd spell the base filename in lower underscore case (also called [snake_case](../guide/glossary.html#snake_case)).
The `SpecialSuperHeroService` would be defined in the `special_super_hero_service.dart` file.
:marked
We name the class `HeroService`.
+makeExample('toh-4/dart/lib/hero_service_1.dart', 'empty-class', 'lib/hero_service.dart (starting point)')(format=".")
:marked
### Injectable Services
Notice that we used an `@Injectable()` annotation.
.callout.is-helpful
:marked
**Don't forget the parentheses!** Neglecting them leads to an error that's difficult to diagnose.
:marked
Dart sees the `@Injectable()` annotation and emits metadata about our service,
metadata that Angular may need to inject other dependencies into this service.
The `HeroService` doesn't have any dependencies *at the moment*. Add the annotation anyway.
It is a "best practice" to apply the `@Injectable()` annotation *from the start*
both for consistency and for future-proofing.
:marked
### Getting Heroes
Add a `getHeroes` method stub.
+makeExample('toh-4/dart/lib/hero_service_1.dart', 'getHeroes-stub', 'lib/hero_service.dart (getHeroes stub)')(format=".")
:marked
We're holding back on the implementation for a moment to make an important point.
The consumer of our service doesn't know how the service gets the data.
Our `HeroService` could get `Hero` data from anywhere.
It could get the data from a web service or local storage
or from a mock data source.
That's the beauty of removing data access from the component.
We can change our minds about the implementation as often as we like,
for whatever reason, without touching any of the components that need heroes.
### Mock Heroes
We already have mock `Hero` data sitting in the `AppComponent`. It doesn't belong there. It doesn't belong *here* either.
We'll move the mock data to its own file.
Cut the `mockHeroes` list from `app_component.dart` and paste it to a new file in the `lib` folder named `mock_heroes.dart`.
We copy the `import 'hero.dart'` statement as well because the heroes list uses the `Hero` class.
+makeExample('toh-4/dart/lib/mock_heroes.dart', null, 'lib/mock_heroes.dart')
:marked
Meanwhile, back in `app_component.dart` where we cut away the `mockHeroes` list,
we leave behind an uninitialized `heroes` property:
+makeExample('toh-4/dart/lib/app_component_1.dart', 'heroes-prop', 'lib/app_component.dart (heroes property)')(format=".")
:marked
### Return Mocked Heroes
Back in the `HeroService` we import the mock `mockHeroes` and return it from the `getHeroes` method.
Our `HeroService` looks like this:
+makeExample('toh-4/dart/lib/hero_service_1.dart', 'final', 'lib/hero_service.dart')(format=".")
:marked
### Use the Hero Service
We're ready to use the `HeroService` in other components starting with our `AppComponent`.
We begin, as usual, by importing the thing we want to use, the `HeroService`.
+makeExcerpt('toh-4/dart/lib/app_component.dart', 'hero-service-import')
:marked
Importing the service allows us to *reference* it in our code.
How should the `AppComponent` acquire a runtime concrete `HeroService` instance?
### Do we *new* the *HeroService*? No way!
We could create a new instance of the `HeroService` with `new` like this:
+makeExample('toh-4/dart/lib/app_component_1.dart', 'new-service')(format=".")
:marked
That's a bad idea for several reasons including
* Our component has to know how to create a `HeroService`.
If we ever change the `HeroService` constructor,
we'll have to find every place we create the service and fix it.
Running around patching code is error prone and adds to the test burden.
* We create a new service each time we use `new`.
What if the service should cache heroes and share that cache with others?
We couldn't do that.
* We're locking the `AppComponent` into a specific implementation of the `HeroService`.
It will be hard to switch implementations for different scenarios.
Can we operate offline?
Will we need different mocked versions under test?
Not easy.
*What if ... what if ... Hey, we've got work to do!*
We get it. Really we do.
But it is so ridiculously easy to avoid these problems that there is no excuse for doing it wrong.
### Inject the *HeroService*
Three lines replace the one line of *new*:
1. We add a property.
1. We add a constructor that sets the property.
1. We add to the component's `providers` metadata.
Here are the property and the constructor:
+makeExample('toh-4/dart/lib/app_component_1.dart', 'ctor', 'lib/app_component.dart (constructor)')(format='.')
:marked
The constructor does nothing except set the `_heroService`
property. The `HeroService` type of `_heroService`
identifies the constructor's parameter as
a `HeroService` injection site.
Now Angular will know to supply an instance of the `HeroService` when it creates a new `AppComponent`.
Angular has to get that instance from somewhere. That's the role of the Angular *Dependency Injector*.
The **Injector** has a **container** of previously created services.
Either it finds and returns a pre-existing `HeroService` from its container or it creates a new instance, adds
it to the container, and returns it to Angular.
.l-sub-section
:marked
Learn more about Dependency Injection in the [Dependency Injection](../guide/dependency-injection.html) chapter.
:marked
The *injector* does not know yet how to create a `HeroService`.
If we ran our code now, Angular would fail with an error:
code-example(format="nocode").
EXCEPTION: No provider for HeroService! (AppComponent -> HeroService)
:marked
We have to teach the *injector* how to make a `HeroService` by registering a `HeroService` **provider**.
Do that by adding the following `providers` parameter to the bottom of the component metadata
in the `@Component` annotation.
+makeExcerpt('toh-4/dart/lib/app_component_1.dart', 'providers')
:marked
The `providers` parameter tells Angular to create a fresh instance of the `HeroService` when it creates a new `AppComponent`.
The `AppComponent` can use that service to get heroes and so can every child component of its component tree.
<a id="child-component"></a>
.l-sub-section
:marked
### Services and the component tree
Recall that the `AppComponent` creates an instance of `HeroDetail` by virtue of the
`<my-hero-detail>` tag at the bottom of its template. That `HeroDetail` is a child of the `AppComponent`.
If the `HeroDetailComponent` needed its parent component's `HeroService`,
it would ask Angular to inject the service into its constructor which would look just like the one for `AppComponent`:
+makeExample('toh-4/dart/lib/app_component_1.dart', 'ctor', 'lib/hero_detail_component.dart (constructor)')(format=".")
:marked
The `HeroDetailComponent` must *not* repeat its parent's `providers` list! Guess [why](#shadow-provider).
The `AppComponent` is the top level component of our application.
There should be only one instance of that component and only one instance of the `HeroService` in our entire app.
:marked
### *getHeroes* in the *AppComponent*
We've got the service in a `_heroService` private variable. Let's use it.
We pause to think. We can call the service and get the data in one line.
+makeExample('toh-4/dart/lib/app_component_1.dart', 'get-heroes')(format=".")
:marked
We don't really need a dedicated method to wrap one line. We write it anyway:
+makeExcerpt('toh-4/dart/lib/app_component_1.dart', 'getHeroes')
<a id="oninit"></a>
:marked
### The *ngOnInit* Lifecycle Hook
`AppComponent` should fetch and display heroes without a fuss.
Where do we call the `getHeroes` method? In a constructor? We do *not*!
Years of experience and bitter tears have taught us to keep complex logic out of the constructor,
especially anything that might call a server as a data access method is sure to do.
The constructor is for simple initializations like wiring constructor parameters to properties.
It's not for heavy lifting. We should be able to create a component in a test and not worry that it
might do real work &mdash; like calling a server! &mdash; before we tell it to do so.
If not the constructor, something has to call `getHeroes`.
Angular will call it if we implement the Angular **ngOnInit** *Lifecycle Hook*.
Angular offers a number of interfaces for tapping into critical moments in the component lifecycle:
at creation, after each change, and at its eventual destruction.
Each interface has a single method. When the component implements that method, Angular calls it at the appropriate time.
.l-sub-section
:marked
Learn more about lifecycle hooks in the [Lifecycle Hooks](../guide/lifecycle-hooks.html) chapter.
:marked
Here's the essential outline for the `OnInit` interface:
+makeExample('toh-4/dart/lib/app_component_1.dart', 'on-init', 'lib/app_component.dart (ngOnInit stub)')(format=".")
:marked
We write an `ngOnInit` method with our initialization logic inside and leave it to Angular to call it
at the right time. In our case, we initialize by calling `getHeroes`.
+makeExcerpt('toh-4/dart/lib/app_component_1.dart', 'ng-on-init')
:marked
Our application should be running as expected, showing a list of heroes and a hero detail view
when we click on a hero name.
We're getting closer. But something isn't quite right.
<a id="async"></a>
## Async Services and !{_Promise}
Our `HeroService` returns a list of mock heroes immediately.
Its `getHeroes` signature is synchronous
+makeExample('toh-4/dart/lib/app_component_1.dart', 'get-heroes')(format=".")
:marked
Ask for heroes and they are there in the returned result.
Someday we're going to get heroes from a remote server. We dont call http yet, but we aspire to in later chapters.
When we do, we'll have to wait for the server to respond and we won't be able to block the UI while we wait,
even if we want to (which we shouldn't) because the browser won't block.
We'll have to use some kind of asynchronous technique and that will change the signature of our `getHeroes` method.
We'll use *!{_Promise}s*.
### The Hero Service returns a !{_Promise}
We ask an asynchronous service to do some work and give us the result in the !{_Promise}.
The service does that work (somewhere) and eventually it updates the !{_Promise} with the results of the work or an error.
.l-sub-section
:marked
We are simplifying. Learn about !{_Promise}s in the tutorial
[Asynchronous Programming: Futures](https://www.dartlang.org/docs/tutorials/futures/).
:marked
Update the `HeroService` with this !{_Promise}-returning `getHeroes` method:
+makeExample('toh-4/dart/lib/hero_service.dart', 'get-heroes', 'lib/hero_service.dart (excerpt)')(format=".")
:marked
We're still mocking the data. We're simulating the behavior of an ultra-fast, zero-latency server,
by returning a !{_Promise} that will quickly resolve with our mock heroes as the result.
.l-sub-section
:marked
Marking the method's body with `async` makes the method immediately return a `Future` object.
That !{_Promise} later completes with the method's return value.
For more information on async functions, see
[Declaring async functions](https://www.dartlang.org/docs/dart-up-and-running/ch02.html#async) in the Dart language tour.
:marked
### Act on the !{_Promise}
Returning to the `AppComponent` and its `getHeroes` method, we see that it still looks like this:
+makeExample('toh-4/dart/lib/app_component_1.dart', 'getHeroes', 'lib/app_component.dart (getHeroes - old)')(format=".")
:marked
As a result of our change to `HeroService`, we're now setting `heroes` to a !{_Promise} rather than a list of heroes.
We have to change our implementation to *act on the !{_Promise} when it resolves*.
We can *await* for the !{_Promise} to resolve, and then display the heroes:
+makeExample('toh-4/dart/lib/app_component.dart', 'get-heroes', 'lib/app_component.dart (getHeroes - revised)')(format=".")
:marked
Our code waits until the !{_Promise} completes, and then
sets the component's `heroes` property to the list of heroes returned by the service. That's all there is to it!
Our app should still be running, still showing a list of heroes, and still
responding to a name selection with a detail view.
.l-sub-section
:marked
Check out the "[Take it slow](#slow)" appendix to see what the app might be like with a poor connection.
:marked
### Review the App Structure
Lets verify that we have the following structure after all of our good refactoring in this chapter:
.filetree
.file angular_tour_of_heroes
.children
.file lib
.children
.file app_component.dart
.file hero.dart
.file hero_detail_component.dart
.file hero_service.dart
.file mock_heroes.dart
.file web
.children
.file index.html
.file main.dart
.file styles.css
.file pubspec.yaml
:marked
Here are the code files we discussed in this chapter.
+makeTabs(`
toh-4/dart/lib/hero_service.dart,
toh-4/dart/lib/app_component.dart,
toh-4/dart/lib/mock_heroes.dart
`,'',`
lib/hero_service.dart,
lib/app_component.dart,
lib/mock_heroes.dart
`)
:marked
## The Road Weve Travelled
Lets take stock of what weve built.
* We created a service class that can be shared by many components.
* We used the `ngOnInit` Lifecycle Hook to get our heroes when our `AppComponent` activates.
* We defined our `HeroService` as a provider for our `AppComponent`.
* We created mock hero data and imported them into our service.
* We designed our service to return a !{_Promise} and our component to get our data from the !{_Promise}.
### The Road Ahead
Our Tour of Heroes has become more reusable using shared components and services.
We want to create a dashboard, add menu links that route between the views, and format data in a template.
As our app evolves, well learn how to design it to make it easier to grow and maintain.
We learn about Angular Component Router and navigation among the views in the [next tutorial](toh-pt5.html) chapter.
.l-main-section
<a id="slow"></a>
:marked
### Appendix: Take it slow
We can simulate a slow connection.
Add the following `getHeroesSlowly` method to the `HeroService`:
+makeExample('toh-4/dart/lib/hero_service.dart', 'get-heroes-slowly', 'lib/hero_service.dart (getHeroesSlowly)')(format=".")
:marked
Like `getHeroes`, it also returns a !{_Promise}.
But this !{_Promise} waits 2 seconds before resolving the !{_Promise} with mock heroes.
Back in the `AppComponent`, replace
`_heroService.getHeroes` with `_heroService.getHeroesSlowly`
and see how the app behaves.
.l-main-section
<a id="shadow-provider"></a>
:marked
### Appendix: Shadowing the parent's service
We stated [earlier](#child-component) that if we injected the parent `AppComponent` `HeroService`
into the `HeroDetailComponent`, *we must not add a providers list* to the `HeroDetailComponent` metadata.
Why? Because that tells Angular to create a new instance of the `HeroService` at the `HeroDetailComponent` level.
The `HeroDetailComponent` doesn't want its *own* service instance; it wants its *parent's* service instance.
Adding the `providers` list creates a new service instance that shadows the parent instance.
Think carefully about where and when to register a provider.
Understand the scope of that registration. Be careful not to create a new service instance at the wrong level.

View File

@ -1,168 +0,0 @@
extends ../../../ts/_cache/tutorial/toh-pt5
block includes
include ../_util-fns
- var _appRoutingTsVsAppComp = 'AppComponent'
- var _RoutesVsAtRouteConfig = '@RouteConfig'
- var _RouterModuleVsRouterDirectives = 'ROUTER_DIRECTIVES'
- var _redirectTo = 'useAsDefault'
block intro-file-tree
.filetree
.file angular_tour_of_heroes
.children
.file lib
.children
.file app_component.dart
.file hero.dart
.file hero_detail_component.dart
.file hero_service.dart
.file mock_heroes.dart
.file web
.children
.file index.html
.file main.dart
.file styles.css
.file pubspec.yaml
block keep-app-running
:marked
### Keep the app compiling and running
Open a terminal/console window.
Start the Dart compiler, watch for changes, and start our server by entering the command:
code-example(language="sh" class="code-shell").
pub serve
block app-comp-v1
+makeExcerpt('lib/app_component_1.dart (v1)', '')
block angular-router
:marked
The Angular router is a combination of multiple services
(`ROUTER_PROVIDERS`), multiple directives (`ROUTER_DIRECTIVES`), and a
configuration annotation (`RouteConfig`). You get them all by importing
the router library:
+makeExcerpt('app/app.component.ts (router imports)', 'import-router')
:marked
### Make the router available
Not all apps need routing, which is why the Angular *Component Router* is
in a separate, optional library module.
Like for any service, you make router services available to the application
by adding them to the `providers` list. Update the `directives` and
`providers` lists to include the router assets:
+makeExcerpt('app/app.component.ts (excerpt)', 'directives-and-providers')
:marked
`AppComponent` no longer shows heroes, that will be the router's job,
so you can remove the `HeroesComponent` from the `directives` list.
You'll soon remove `<my-heroes>` from the template too.
block router-config-intro
:marked
### Configure routes and add the router
The `AppComponent` doesn't have a router yet. You'll use the `@RouteConfig`
annotation to simultaneously:
- Assign a router to the component
- Configure that router with *routes*
block routerLink
:marked
Notice the `[routerLink]` binding in the anchor tag.
You bind the `RouterLink` directive (another of the `ROUTER_DIRECTIVES`) to a list
that tells the router where to navigate when the user clicks the link.
You define a *routing instruction* with a *link parameters list*.
The list only has one element in our little sample, the quoted ***name* of the route** to follow.
Looking back at the route configuration, confirm that `'Heroes'` is the name of the route to the `HeroesComponent`.
.l-sub-section
:marked
Learn about the *link parameters list*
in the [Routing](../guide/router.html#link-parameters-array) chapter.
block redirect-vs-use-as-default
:marked
You don't need a route definition for that. Instead,
add `useAsDefault: true` to the dashboard *route definition* and the
router will display the dashboard when the browser URL doesn't match an existing route.
block templateUrl-path-resolution
.l-sub-section
:marked
The value of `templateUrl` can be an [asset][] in this package or another
package. To use an asset in another package, use a full package reference,
such as `'package:some_other_package/dashboard_component.html'`.
[asset]: https://www.dartlang.org/tools/pub/glossary#asset
block route-params
:marked
You will no longer receive the hero in a parent component property binding.
The new `HeroDetailComponent` should take the `id` parameter from the router's
`RouteParams` service and use the `HeroService` to fetch the hero with that `id`.
block ngOnInit
:marked
Inside the `ngOnInit` lifecycle hook, extract the `id` parameter value from the `RouteParams` service
and use the `HeroService` to fetch the hero with that `id`.
block extract-id
:marked
Notice how you can extract the `id` by calling the `RouteParams.get` method.
block heroes-component-cleanup
:marked
Because the template for `HeroesComponent` no longer uses `HeroDetailComponent`
directly &mdash; instead using the router to _navigate_ to it &mdash; you can
drop the `directives` argument from `@Component` and remove the unused hero detail
import. The revised `@Component` looks like this:
block css-files
+makeTabs(
`toh-5/dart/lib/hero_detail_component.css,
toh-5/dart/lib/dashboard_component.css`,
null,
`lib/hero_detail_component.css,
lib/dashboard_component.css`)
block router-link-active
:marked
**The *router-link-active* class**
The Angular Router adds the `router-link-active` class to the HTML navigation element
whose route matches the active route. All you have to do is define the style for it. Sweet!
block file-tree-end
.filetree
.file angular_tour_of_heroes
.children
.file lib
.children
.file app_component.css
.file app_component.dart
.file dashboard_component.css
.file dashboard_component.dart
.file dashboard_component.html
.file hero.dart
.file hero_detail_component.css
.file hero_detail_component.dart
.file hero_detail_component.html
.file hero_service.dart
.file heroes_component.css
.file heroes_component.dart
.file heroes_component.html
.file mock_heroes.dart
.file web
.children
.file index.html
.file main.dart
.file styles.css
.file pubspec.yaml

View File

@ -1,191 +0,0 @@
extends ../../../ts/_cache/tutorial/toh-pt6.jade
block includes
include ../_util-fns
- var _Http = 'BrowserClient';
- var _Angular_Http = 'Dart <code>BrowserClient</code>'
- var _httpUrl = 'https://pub.dartlang.org/packages/http'
- var _Angular_http_library = 'Dart <a href="' + _httpUrl + '"><b>http</b></a> package'
- var _HttpModule = 'BrowserClient'
- var _JSON_stringify = 'JSON.encode'
block start-server-and-watch
:marked
### Keep the app compiling and running
Open a terminal/console window.
Start the Dart compiler, watch for changes, and start our server by entering the command:
code-example(language="sh" class="code-shell").
pub serve
block http-library
:marked
We'll be using the !{_Angular_http_library}'s
`BrowserClient` class to communicate with a server.
### Pubspec updates
Update package dependencies by adding the
`stream_transformers` and !{_Angular_http_library}s.
We also need to add a `resolved_identifiers` entry, to inform the [angular2
transformer][ng2x] that we'll be using `BrowserClient`. (For an explanation of why
this extra configuration is needed, see the [HTTP client chapter][guide-http].) We'll
also need to use `Client` from http, so let's add that now as well.
Update `pubspec.yaml` to look like this (additions are highlighted):
[guide-http]: ../guide/server-communication.html#http-providers
[ng2x]: https://github.com/angular/angular/wiki/Angular-2-Dart-Transformer
- var stylePattern = { pnk: /(http.*|stream.*|resolved_identifiers:|Browser.*|Client.*)/gm };
+makeExcerpt('pubspec.yaml', 'additions', null, stylePattern)
block http-providers
:marked
Before our app can use `#{_Http}`, we have to register it as a service provider.
We should be able to access `!{_Http}` services from anywhere in the application.
So we register it in the `bootstrap` call where we
launch the application and its root `AppComponent`.
+makeExcerpt('app/main.ts','v1')
:marked
Notice that we supply `!{_HttpModule}` in a list, as the second parameter to
the `bootstrap` method. This has the same effect as the `providers` list in
`@Component` annotation.
block backend
:marked
We want to replace `BrowserClient`, the service that talks to the remote server,
with the in-memory web API service.
Our in-memory web API service, shown below, is implemented using the
`http` library `MockClient` class.
All `http` client implementations share a common `Client` interface, so
we'll have our app use the `Client` type so that we can freely switch between
implementations.
block dont-be-distracted-by-backend-subst
//- No backend substitution but we do need to comment on the changes to Hero.
:marked
As is common for web API services, our mock in-memory service will be
encoding and decoding heroes in JSON format, so we enhance the `Hero`
class with these capabilities:
+makeExample('lib/hero.dart')
block get-heroes-details
:marked
To get the list of heroes, we first make an asynchronous call to
`http.get()`. Then we use the `_extractData` helper method to decode the
response body.
block observables-section-intro
:marked
Recall that `HeroService.getHeroes()` awaits for an `http.get()`
response and yields a _Future_ `List<Hero>`, which is fine when we are only
interested in a single result.
block search-criteria-intro
:marked
A [StreamController][], as its name implies, is a controller for a [Stream][] that allows us to
manipulate the underlying stream by adding data to it, for example.
In our sample, the underlying stream of strings (`_searchTerms.stream`) represents the hero
name search patterns entered by the user. Each call to `search` puts a new string into
the stream by calling `add` over the controller.
[Stream]: https://api.dartlang.org/stable/dart-async/Stream-class.html
[StreamController]: https://api.dartlang.org/stable/dart-async/StreamController-class.html
block observable-transformers
:marked
Fortunately, there are stream transformers that will help us reduce the request flow.
We'll make fewer calls to the `HeroSearchService` and still get timely results. Here's how:
* `transform(new Debounce(... 300)))` waits until the flow of search terms pauses for 300
milliseconds before passing along the latest string. We'll never make requests more frequently
than 300ms.
* `distinct()` ensures that we only send a request if a search term has changed.
There's no point in repeating a request for the same search term.
* `transform(new FlatMapLatest(...))` applies a map-like transformer that (1) calls our search
service for each search term that makes it through the debounce and distinct gauntlet and (2)
returns only the most recent search service result, discarding any previous results.
* `handleError()` handles errors. Our simple example prints the error to the console; a real
life application should do better.
block filetree
.filetree
.file angular_tour_of_heroes
.children
.file lib
.children
.file app_component.css
.file app_component.dart
.file dashboard_component.css
.file dashboard_component.dart
.file dashboard_component.html
.file hero.dart
.file hero_detail_component.css
.file hero_detail_component.dart
.file hero_detail_component.html
.file hero_search_component.css (new)
.file hero_search_component.dart (new)
.file hero_search_component.html (new)
.file hero_search_service.dart (new)
.file hero_service.dart
.file heroes_component.css
.file heroes_component.dart
.file heroes_component.html
.file in_memory_data_service.dart (new)
.file web
.children
.file main.dart
.file index.html
.file styles.css
.file pubspec.yaml
block file-summary
+makeTabs(
`toh-6/dart/lib/dashboard_component.dart,
toh-6/dart/lib/dashboard_component.html,
toh-6/dart/lib/hero.dart,
toh-6/dart/lib/hero_detail_component.dart,
toh-6/dart/lib/hero_detail_component.html,
toh-6/dart/lib/hero_service.dart,
toh-6/dart/lib/heroes_component.css,
toh-6/dart/lib/heroes_component.dart,
toh-6/dart/lib/in_memory_data_service.dart`,
',,,,,,,,',
`lib/dashboard_component.dart,
lib/dashboard_component.html,
lib/hero.dart,
lib/hero_detail_component.dart,
lib/hero_detail_component.html,
lib/hero_service.dart,
lib/heroes_component.css,
lib/heroes_component.dart,
lib/in_memory_data_service.dart`)
+makeTabs(
`toh-6/dart/lib/hero_search_component.css,
toh-6/dart/lib/hero_search_component.dart,
toh-6/dart/lib/hero_search_component.html,
toh-6/dart/lib/hero_search_service.dart`,
null,
`lib/hero_search_component.css,
lib/hero_search_component.dart,
lib/hero_search_component.html,
lib/hero_search_service.dart`)
+makeTabs(
`toh-6/dart/pubspec.yaml,
toh-6/dart/web/main.dart`,
null,
`pubspec.yaml,
web/main.dart`)