chore(jade): upgrade to use `marked` filter
The `Transformers.markdown` (`:markdown`) filter is deprecated. This commit updates the entire docs project to use the `jstransformer-marked` (`:marked') filter.
This commit is contained in:
parent
0f61ea288f
commit
e86fde8dc9
|
@ -60,5 +60,8 @@
|
||||||
"q": "^1.4.1",
|
"q": "^1.4.1",
|
||||||
"typescript": "~1.5.3",
|
"typescript": "~1.5.3",
|
||||||
"yargs": "^3.23.0"
|
"yargs": "^3.23.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"jstransformer-marked": "^1.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ include ../../../_includes/_util-fns
|
||||||
.showcase-content
|
.showcase-content
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
### Including a code example from the `_examples` folder
|
### Including a code example from the `_examples` folder
|
||||||
|
|
||||||
One of the design goals for this documention was that any code samples that appear within the documentation be 'testable'.
|
One of the design goals for this documention was that any code samples that appear within the documentation be 'testable'.
|
||||||
|
@ -35,7 +35,7 @@ include ../../../_includes/_util-fns
|
||||||
code-example(language="js").
|
code-example(language="js").
|
||||||
include ../../../../_includes/_util-fns
|
include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
The syntax for the `makeExample` mixin is:
|
The syntax for the `makeExample` mixin is:
|
||||||
|
|
||||||
#### +makeExample(filePath, region, title, stylePattern)
|
#### +makeExample(filePath, region, title, stylePattern)
|
||||||
|
@ -49,14 +49,14 @@ include ../../../_includes/_util-fns
|
||||||
code-example(format="linenums" language="js").
|
code-example(format="linenums" language="js").
|
||||||
+makeExample('styleguide/js/index.html', null, 'index.html')
|
+makeExample('styleguide/js/index.html', null, 'index.html')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
This will read the *_examples/styleguide/js/index.html* file and include it
|
This will read the *_examples/styleguide/js/index.html* file and include it
|
||||||
with the heading 'index.html'. Note that the file will be properly escaped and
|
with the heading 'index.html'. Note that the file will be properly escaped and
|
||||||
color coded according to the extension on the file ( html in this case).
|
color coded according to the extension on the file ( html in this case).
|
||||||
|
|
||||||
+makeExample('styleguide/js/index.html', null, 'index.html')
|
+makeExample('styleguide/js/index.html', null, 'index.html')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
The second parameter with a value of 'null' will be described later in this document.
|
The second parameter with a value of 'null' will be described later in this document.
|
||||||
|
|
||||||
There is a similar `makeTabs` mixin that provides the same service but for multiple examples
|
There is a similar `makeTabs` mixin that provides the same service but for multiple examples
|
||||||
|
@ -73,13 +73,13 @@ include ../../../_includes/_util-fns
|
||||||
code-example(format="linenums" language="js").
|
code-example(format="linenums" language="js").
|
||||||
+makeTabs('styleguide/js/index.html, styleguide/js/spec.js', null, 'index.html,unit test')
|
+makeTabs('styleguide/js/index.html, styleguide/js/spec.js', null, 'index.html,unit test')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
This will create two tabs, each with its own title and appropriately color coded.
|
This will create two tabs, each with its own title and appropriately color coded.
|
||||||
|
|
||||||
+makeTabs('styleguide/js/index.html, styleguide/js/spec.js', null, 'index.html,unit test')
|
+makeTabs('styleguide/js/index.html, styleguide/js/spec.js', null, 'index.html,unit test')
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
### Marking up an example file for use by the `makeExample` and `makeTabs` mixins
|
### Marking up an example file for use by the `makeExample` and `makeTabs` mixins
|
||||||
|
|
||||||
At a minimum, marking up an example file simply consists of adding a single comment line to the top of the file
|
At a minimum, marking up an example file simply consists of adding a single comment line to the top of the file
|
||||||
|
@ -98,7 +98,7 @@ include ../../../_includes/_util-fns
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
If a file only has a single `#docregion` then the entire file AFTER the `#docregion` comment is available for inclusion
|
If a file only has a single `#docregion` then the entire file AFTER the `#docregion` comment is available for inclusion
|
||||||
via mixin. Portions of the file can be indicated by surrounding an area of the file with
|
via mixin. Portions of the file can be indicated by surrounding an area of the file with
|
||||||
`#docregion` and an `#enddocregion` tags. These regions, each with its own name, may be nested to any level and any regions that are not 'ended' explicitly
|
`#docregion` and an `#enddocregion` tags. These regions, each with its own name, may be nested to any level and any regions that are not 'ended' explicitly
|
||||||
|
@ -130,7 +130,7 @@ include ../../../_includes/_util-fns
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Multiple `#docregion` tags may be defined on a single line as shown below. In addition, anytime a file contains multiple
|
Multiple `#docregion` tags may be defined on a single line as shown below. In addition, anytime a file contains multiple
|
||||||
`#docregion` tags with the same name they will automatically be combined. Each of the individually tagged sections of the combined document
|
`#docregion` tags with the same name they will automatically be combined. Each of the individually tagged sections of the combined document
|
||||||
will be separated from one another by a comment consisting of '. . .'. This default separator, known
|
will be separated from one another by a comment consisting of '. . .'. This default separator, known
|
||||||
|
@ -158,7 +158,7 @@ include ../../../_includes/_util-fns
|
||||||
doSomethingInteresting();
|
doSomethingInteresting();
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
HTML files can also contain #docregion comments:
|
HTML files can also contain #docregion comments:
|
||||||
|
|
||||||
code-example(format="linenums" language="html" escape="html").
|
code-example(format="linenums" language="html" escape="html").
|
||||||
|
@ -168,7 +168,7 @@ include ../../../_includes/_util-fns
|
||||||
<script src="app.js"></script>
|
<script src="app.js"></script>
|
||||||
...
|
...
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
as can CSS files:
|
as can CSS files:
|
||||||
|
|
||||||
code-example(format="linenums" language="css").
|
code-example(format="linenums" language="css").
|
||||||
|
@ -179,7 +179,7 @@ include ../../../_includes/_util-fns
|
||||||
}
|
}
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
### Including a named #docregion via the makeExample or makeTabs mixins.
|
### Including a named #docregion via the makeExample or makeTabs mixins.
|
||||||
|
|
||||||
In order to include just a portion of an example file that has been marked up with a 'named' `#docregion`
|
In order to include just a portion of an example file that has been marked up with a 'named' `#docregion`
|
||||||
|
@ -189,14 +189,14 @@ include ../../../_includes/_util-fns
|
||||||
code-example(format="linenums" language="js").
|
code-example(format="linenums" language="js").
|
||||||
+makeExample('styleguide/js/app.js', 'class-w-annotations', "Extracted region")
|
+makeExample('styleguide/js/app.js', 'class-w-annotations', "Extracted region")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
is a request to include just the `class-w-annotations` region from the `app.js` file in the `_examples/styleguide`
|
is a request to include just the `class-w-annotations` region from the `app.js` file in the `_examples/styleguide`
|
||||||
folder and results in the following:
|
folder and results in the following:
|
||||||
|
|
||||||
+makeExample('styleguide/js/app.js', 'class-w-annotations', "Extracted region")
|
+makeExample('styleguide/js/app.js', 'class-w-annotations', "Extracted region")
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
### Additional styling
|
### Additional styling
|
||||||
|
|
||||||
In some cases you may want to add additional styling to an external file after it had been included in the documentation.
|
In some cases you may want to add additional styling to an external file after it had been included in the documentation.
|
||||||
|
@ -215,7 +215,7 @@ include ../../../_includes/_util-fns
|
||||||
code-example(format="linenums" language="js" escape="none").
|
code-example(format="linenums" language="js" escape="none").
|
||||||
+makeExample('styleguide/js/index.html', null, 'index.html', {pnk: /script (src=.*&quot;)/g})
|
+makeExample('styleguide/js/index.html', null, 'index.html', {pnk: /script (src=.*&quot;)/g})
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Which will mark all of the quoted contents of each `script` tag within the index.html file in pink.
|
Which will mark all of the quoted contents of each `script` tag within the index.html file in pink.
|
||||||
|
|
||||||
.alert.is-important.
|
.alert.is-important.
|
||||||
|
@ -224,20 +224,20 @@ include ../../../_includes/_util-fns
|
||||||
|
|
||||||
+makeExample('styleguide/js/index.html', null, 'index.html', {pnk: /script (src=.*")/g})
|
+makeExample('styleguide/js/index.html', null, 'index.html', {pnk: /script (src=.*")/g})
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
A more complicated example might be:
|
A more complicated example might be:
|
||||||
|
|
||||||
code-example(format="linenums" language="js").
|
code-example(format="linenums" language="js").
|
||||||
- var stylePattern = { pnk: /script (src=.*&quot;)/g, otl: /(\S*my-app.*$)/m };
|
- var stylePattern = { pnk: /script (src=.*&quot;)/g, otl: /(\S*my-app.*$)/m };
|
||||||
+makeExample('styleguide/js/index.html', null, 'index.html', stylePattern )
|
+makeExample('styleguide/js/index.html', null, 'index.html', stylePattern )
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Which applies multiple styles and uses an intermediate javascript object as opposed to a literal.
|
Which applies multiple styles and uses an intermediate javascript object as opposed to a literal.
|
||||||
|
|
||||||
- var stylePattern = { pnk: /script (src=.*")/g, otl: /(\S*my-app.*$)/m };
|
- var stylePattern = { pnk: /script (src=.*")/g, otl: /(\S*my-app.*$)/m };
|
||||||
+makeExample('styleguide/js/index.html', null, 'index.html', stylePattern )
|
+makeExample('styleguide/js/index.html', null, 'index.html', stylePattern )
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
`makeTabs` support for `stylePatterns` is slightly different from the `makeExample` mixin in that you can also
|
`makeTabs` support for `stylePatterns` is slightly different from the `makeExample` mixin in that you can also
|
||||||
pass in an array of stylePattern objects where each is paired with its corresponding 'tab'. If only a single stylePattern
|
pass in an array of stylePattern objects where each is paired with its corresponding 'tab'. If only a single stylePattern
|
||||||
object is passed in then it is assumed to apply to all of the tabs.
|
object is passed in then it is assumed to apply to all of the tabs.
|
||||||
|
@ -250,7 +250,7 @@ include ../../../_includes/_util-fns
|
||||||
+makeTabs('styleguide/js/index.html, styleguide/js/spec.js', null, 'index.html,unit test', stylePatterns)
|
+makeTabs('styleguide/js/index.html, styleguide/js/spec.js', null, 'index.html,unit test', stylePatterns)
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
### Including a JSON file or just parts of one
|
### Including a JSON file or just parts of one
|
||||||
|
|
||||||
To include an '.json' file from somewhere in the `doc\_examples` folder you can use the `makeJson` mixin. The `makeExample`
|
To include an '.json' file from somewhere in the `doc\_examples` folder you can use the `makeJson` mixin. The `makeExample`
|
||||||
|
@ -277,7 +277,7 @@ include ../../../_includes/_util-fns
|
||||||
|
|
||||||
+makeJson('styleguide/package.json', null, "Entire package.json file")
|
+makeJson('styleguide/package.json', null, "Entire package.json file")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
A subset of the '.json' file can also be selected.
|
A subset of the '.json' file can also be selected.
|
||||||
|
|
||||||
code-example(format="linenums" language="js").
|
code-example(format="linenums" language="js").
|
||||||
|
@ -285,7 +285,7 @@ include ../../../_includes/_util-fns
|
||||||
|
|
||||||
+makeJson('styleguide/package.json', { paths: 'version, scripts.tsc, scripts.start '}, "Selected parts of the package.json file" )
|
+makeJson('styleguide/package.json', { paths: 'version, scripts.tsc, scripts.start '}, "Selected parts of the package.json file" )
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Styling selected portions of the json is also supported.
|
Styling selected portions of the json is also supported.
|
||||||
|
|
||||||
code-example(format="linenums" language="js").
|
code-example(format="linenums" language="js").
|
||||||
|
@ -293,7 +293,7 @@ include ../../../_includes/_util-fns
|
||||||
|
|
||||||
+makeJson('styleguide/package.json', {paths: 'dependencies'}, "package.json dependencies", { pnk: [/(\S*traceur.*)/, /(\Sangular2.*)/, /(\Ssystem.*)/ ]})
|
+makeJson('styleguide/package.json', {paths: 'dependencies'}, "package.json dependencies", { pnk: [/(\S*traceur.*)/, /(\Sangular2.*)/, /(\Ssystem.*)/ ]})
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
As well as styling across multiple lines.
|
As well as styling across multiple lines.
|
||||||
|
|
||||||
code-example(format="linenums" language="js").
|
code-example(format="linenums" language="js").
|
||||||
|
@ -304,7 +304,7 @@ include ../../../_includes/_util-fns
|
||||||
+makeJson('styleguide/package.json', {paths: 'name, version, dependencies '}, "Foo", styles )
|
+makeJson('styleguide/package.json', {paths: 'name, version, dependencies '}, "Foo", styles )
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
### Inline code and code examples provided directly i.e. not from an example file.
|
### Inline code and code examples provided directly i.e. not from an example file.
|
||||||
|
|
||||||
The `makeExample` and `makeTabs` mixins are both both built on top of a custom jade 'style'; `code-example`.
|
The `makeExample` and `makeTabs` mixins are both both built on top of a custom jade 'style'; `code-example`.
|
||||||
|
@ -391,7 +391,7 @@ include ../../../_includes/_util-fns
|
||||||
// TAB 2 CONTENT
|
// TAB 2 CONTENT
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
### Combining makeExample, makeTabs mixins with code-example style attributes
|
### Combining makeExample, makeTabs mixins with code-example style attributes
|
||||||
As mentioned above the `makeExample` and `makeTabs` mixins are built on top of the `code-example` style. By default
|
As mentioned above the `makeExample` and `makeTabs` mixins are built on top of the `code-example` style. By default
|
||||||
the mixins automatically determine a language based on the example file's extensions and always include line numbers.
|
the mixins automatically determine a language based on the example file's extensions and always include line numbers.
|
||||||
|
@ -403,12 +403,12 @@ include ../../../_includes/_util-fns
|
||||||
code-example().
|
code-example().
|
||||||
+makeExample('styleguide/js/app.js', "class-w-annotations")(format="linenums:15")
|
+makeExample('styleguide/js/app.js', "class-w-annotations")(format="linenums:15")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Starts the numbering of the example at line 15.
|
Starts the numbering of the example at line 15.
|
||||||
|
|
||||||
+makeExample('styleguide/js/app.js', "class-w-annotations")(format="linenums:15")
|
+makeExample('styleguide/js/app.js', "class-w-annotations")(format="linenums:15")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Or to suppress line numbering completely you can use
|
Or to suppress line numbering completely you can use
|
||||||
|
|
||||||
code-example().
|
code-example().
|
||||||
|
@ -417,7 +417,7 @@ include ../../../_includes/_util-fns
|
||||||
+makeExample('styleguide/js/app.js', 'class-w-annotations')(format=".")
|
+makeExample('styleguide/js/app.js', 'class-w-annotations')(format=".")
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
### Code examples in angular/angular source code
|
### Code examples in angular/angular source code
|
||||||
|
|
||||||
References to embedded example code in the angular/angular source make use of the same mixins as defined above, but with a slightly different
|
References to embedded example code in the angular/angular source make use of the same mixins as defined above, but with a slightly different
|
||||||
|
@ -431,7 +431,7 @@ include ../../../_includes/_util-fns
|
||||||
|
|
||||||
Example files referenced by inline tags are all assumed to be in the 'modules/angular2' folder in the angular/angular repo.
|
Example files referenced by inline tags are all assumed to be in the 'modules/angular2' folder in the angular/angular repo.
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
#### @example inline tag parameters
|
#### @example inline tag parameters
|
||||||
- *filePath:* path to the example file under the '_examples' folder
|
- *filePath:* path to the example file under the '_examples' folder
|
||||||
- *region:* (optional or null) region from the example file to display
|
- *region:* (optional or null) region from the example file to display
|
||||||
|
@ -452,7 +452,7 @@ include ../../../_includes/_util-fns
|
||||||
* {@example core/directives/ng_if_spec.ts foo title='Foo' }
|
* {@example core/directives/ng_if_spec.ts foo title='Foo' }
|
||||||
**/
|
**/
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
#### @exampleTabs inline tag parameters
|
#### @exampleTabs inline tag parameters
|
||||||
- *filePaths:* a comma delimited string of filePaths to example files under the '_examples' folder
|
- *filePaths:* a comma delimited string of filePaths to example files under the '_examples' folder
|
||||||
- *regions:* (optional or null) region from the example file to display
|
- *regions:* (optional or null) region from the example file to display
|
||||||
|
@ -469,7 +469,7 @@ include ../../../_includes/_util-fns
|
||||||
**/
|
**/
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
### Cross references to Developer guide pages in angular/angular source comments.
|
### Cross references to Developer guide pages in angular/angular source comments.
|
||||||
|
|
||||||
The '{@linkDevGuide ... }' inline tag is intended to be used to create links from api documentation to dev guide
|
The '{@linkDevGuide ... }' inline tag is intended to be used to create links from api documentation to dev guide
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
include ../../../_includes/_util-fns
|
include ../../../_includes/_util-fns
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Let's start from zero and build a super simple Angular 2 application in Dart.
|
Let's start from zero and build a super simple Angular 2 application in Dart.
|
||||||
|
|
||||||
.callout.is-helpful
|
.callout.is-helpful
|
||||||
header Don't want Dart?
|
header Don't want Dart?
|
||||||
:markdown
|
:marked
|
||||||
Although we're getting started in Dart, you can also write Angular 2 apps
|
Although we're getting started in Dart, you can also write Angular 2 apps
|
||||||
in TypeScript and JavaScript.
|
in TypeScript and JavaScript.
|
||||||
Just select either of those languages from the combo-box in the banner.
|
Just select either of those languages from the combo-box in the banner.
|
||||||
|
@ -27,7 +27,7 @@ p.
|
||||||
.l-main-section
|
.l-main-section
|
||||||
h2#section-install-angular Set up a new app directory
|
h2#section-install-angular Set up a new app directory
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Create a new directory,
|
Create a new directory,
|
||||||
and put a file named `pubspec.yaml` in it.
|
and put a file named `pubspec.yaml` in it.
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ p.
|
||||||
|
|
||||||
+makeExample('quickstart/dart/ex1/web/main.dart', null, 'web/main.dart')
|
+makeExample('quickstart/dart/ex1/web/main.dart', null, 'web/main.dart')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
You've just defined an Angular 2 **component**,
|
You've just defined an Angular 2 **component**,
|
||||||
one of the most important Angular 2 features.
|
one of the most important Angular 2 features.
|
||||||
Components are the primary way to create application views
|
Components are the primary way to create application views
|
||||||
|
@ -121,7 +121,7 @@ p.
|
||||||
|
|
||||||
+makeExample('quickstart/dart/ex1/web/index.html', null, 'web/index.html')
|
+makeExample('quickstart/dart/ex1/web/index.html', null, 'web/index.html')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
The `<my-app>` tag in the `<body>` is
|
The `<my-app>` tag in the `<body>` is
|
||||||
the custom HTML element defined in the Dart file.
|
the custom HTML element defined in the Dart file.
|
||||||
|
|
||||||
|
@ -149,7 +149,7 @@ p.
|
||||||
Once the app is running,
|
Once the app is running,
|
||||||
you should see <b>My First Angular 2 App</b> in your browser window.
|
you should see <b>My First Angular 2 App</b> in your browser window.
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
If you don't see that, make sure you've entered all the code correctly
|
If you don't see that, make sure you've entered all the code correctly
|
||||||
and run `pub get`.
|
and run `pub get`.
|
||||||
|
|
||||||
|
@ -158,14 +158,14 @@ p.
|
||||||
|
|
||||||
h2#section-angular-run-app Generate JavaScript
|
h2#section-angular-run-app Generate JavaScript
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Before you can deploy your app, you need to generate JavaScript files.
|
Before you can deploy your app, you need to generate JavaScript files.
|
||||||
Pub build makes that easy.
|
Pub build makes that easy.
|
||||||
To improve your app's performance, convert the
|
To improve your app's performance, convert the
|
||||||
HTML file to directly include the generated JavaScript;
|
HTML file to directly include the generated JavaScript;
|
||||||
one way to do that is with dart_to_js_script_rewriter.
|
one way to do that is with dart_to_js_script_rewriter.
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Add the dart_to_js_script_rewriter package to your pubspec,
|
Add the dart_to_js_script_rewriter package to your pubspec,
|
||||||
in both the `dependencies` and `transformers` sections.
|
in both the `dependencies` and `transformers` sections.
|
||||||
|
|
||||||
|
|
|
@ -1,54 +1,54 @@
|
||||||
include ../../../_includes/_util-fns
|
include ../../../_includes/_util-fns
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Let's start from zero and build a simple Angular 2 application in JavaScript.
|
Let's start from zero and build a simple Angular 2 application in JavaScript.
|
||||||
|
|
||||||
.callout.is-helpful
|
.callout.is-helpful
|
||||||
header Don't want JavaScript?
|
header Don't want JavaScript?
|
||||||
:markdown
|
:marked
|
||||||
Although we're getting started in JavaScript, you can also write Angular 2 apps
|
Although we're getting started in JavaScript, you can also write Angular 2 apps
|
||||||
in TypeScript and Dart by selecting either of those languages from the combo-box in the banner.
|
in TypeScript and Dart by selecting either of those languages from the combo-box in the banner.
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
We'll do it in six short steps
|
We'll do it in six short steps
|
||||||
1. Create a project folder
|
1. Create a project folder
|
||||||
1. Install essential libraries
|
1. Install essential libraries
|
||||||
1. Write the root component for our application in *app.js*
|
1. Write the root component for our application in *app.js*
|
||||||
1. Bootstrap the app
|
1. Bootstrap the app
|
||||||
1. Create an *index.html*
|
1. Create an *index.html*
|
||||||
1. Run it
|
1. Run it
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Create a project folder
|
## Create a project folder
|
||||||
|
|
||||||
**Create a new folder** to hold our application project, perhaps like this:
|
**Create a new folder** to hold our application project, perhaps like this:
|
||||||
```
|
```
|
||||||
mkdir angular2-quickstart
|
mkdir angular2-quickstart
|
||||||
cd angular2-quickstart
|
cd angular2-quickstart
|
||||||
```
|
```
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Install essential libraries
|
## Install essential libraries
|
||||||
|
|
||||||
We'll use the **npm package manager** to install packages for
|
We'll use the **npm package manager** to install packages for
|
||||||
the libraries and development tools we need:
|
the libraries and development tools we need:
|
||||||
|
|
||||||
>angular2 - the Angular 2 library.
|
>angular2 - the Angular 2 library.
|
||||||
|
|
||||||
>[live-server](https://www.npmjs.com/package/live-server "Live-server")
|
>[live-server](https://www.npmjs.com/package/live-server "Live-server")
|
||||||
a static file server that reloads the browser when files change.
|
a static file server that reloads the browser when files change.
|
||||||
|
|
||||||
We could reference the libraries on the web or download them to our project.
|
We could reference the libraries on the web or download them to our project.
|
||||||
That isn't a sustainable development process and package loading with npm is really
|
That isn't a sustainable development process and package loading with npm is really
|
||||||
easy once we have it installed.
|
easy once we have it installed.
|
||||||
|
|
||||||
.alert.is-helpful
|
.alert.is-helpful
|
||||||
:markdown
|
:marked
|
||||||
Don't have npm? [Get it now](https://docs.npmjs.com/getting-started/installing-node "Installing Node.js and updating npm")
|
Don't have npm? [Get it now](https://docs.npmjs.com/getting-started/installing-node "Installing Node.js and updating npm")
|
||||||
because we're going to use it now and repeatedly throughout this documentation.
|
because we're going to use it now and repeatedly throughout this documentation.
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
**Open** a terminal window and enter these commands:
|
**Open** a terminal window and enter these commands:
|
||||||
```
|
```
|
||||||
npm init -y
|
npm init -y
|
||||||
|
@ -61,24 +61,24 @@ include ../../../_includes/_util-fns
|
||||||
|
|
||||||
+makeJson('quickstart/js/package.json', { paths: 'name, version, dependencies, devDependencies'})
|
+makeJson('quickstart/js/package.json', { paths: 'name, version, dependencies, devDependencies'})
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
There is also a `scripts` section. **Find and replace** it with the following:
|
There is also a `scripts` section. **Find and replace** it with the following:
|
||||||
|
|
||||||
+makeJson('quickstart/js/package.json', { paths: 'scripts'})
|
+makeJson('quickstart/js/package.json', { paths: 'scripts'})
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
We've just extended our project world with a script command that we'll be running very soon.
|
We've just extended our project world with a script command that we'll be running very soon.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Our first Angular component
|
## Our first Angular component
|
||||||
|
|
||||||
Add a new file called *app.js* and paste the following lines:
|
Add a new file called *app.js* and paste the following lines:
|
||||||
|
|
||||||
+makeExample('quickstart/js/app.js', 'class-w-annotations')
|
+makeExample('quickstart/js/app.js', 'class-w-annotations')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
We're creating a visual component named **`AppComponent`** by chaining the
|
We're creating a visual component named **`AppComponent`** by chaining the
|
||||||
`Component` and `Class` methods that belong to the **global Angular namespace, `ng`**.
|
`Component` and `Class` methods that belong to the **global Angular namespace, `ng`**.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -88,12 +88,12 @@ include ../../../_includes/_util-fns
|
||||||
```
|
```
|
||||||
The **`Component`** method takes a configuration object with two
|
The **`Component`** method takes a configuration object with two
|
||||||
properties. The `selector` property tells Angular that this is a component
|
properties. The `selector` property tells Angular that this is a component
|
||||||
controlling a host element named "my-app".
|
controlling a host element named "my-app".
|
||||||
Angular creates and displays an instance of our `AppComponent`
|
Angular creates and displays an instance of our `AppComponent`
|
||||||
wherever it encounters a `my-app` element.
|
wherever it encounters a `my-app` element.
|
||||||
|
|
||||||
The `template` property defines the visual appearance of the component.
|
The `template` property defines the visual appearance of the component.
|
||||||
We're writing the HTML template inline in this example.
|
We're writing the HTML template inline in this example.
|
||||||
Later we'll move the HTML to a view template file and
|
Later we'll move the HTML to a view template file and
|
||||||
assign the template's filename to the `templateUrl` property.
|
assign the template's filename to the `templateUrl` property.
|
||||||
We'll prefer that practice for all but the most trivial templates.
|
We'll prefer that practice for all but the most trivial templates.
|
||||||
|
@ -101,24 +101,24 @@ include ../../../_includes/_util-fns
|
||||||
The **`Class`** method is where we implement the component itself,
|
The **`Class`** method is where we implement the component itself,
|
||||||
giving it properties and methods that bind to the view and whatever
|
giving it properties and methods that bind to the view and whatever
|
||||||
behavior is appropriate for this part of the UI.
|
behavior is appropriate for this part of the UI.
|
||||||
|
|
||||||
This component class has the bare minimum implementation:
|
This component class has the bare minimum implementation:
|
||||||
a *no-op* constructor function that does nothing because there is nothing to do.
|
a *no-op* constructor function that does nothing because there is nothing to do.
|
||||||
We'll see more interesting component classes in future examples.
|
We'll see more interesting component classes in future examples.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Bootstrap the app
|
## Bootstrap the app
|
||||||
We need to do something to put our application in motion.
|
We need to do something to put our application in motion.
|
||||||
Add the following to the bottom of the `app.js` file:
|
Add the following to the bottom of the `app.js` file:
|
||||||
|
|
||||||
+makeExample('quickstart/js/app.js', 'bootstrap')
|
+makeExample('quickstart/js/app.js', 'bootstrap')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
We'll wait for the browser to tell us that it has finished loading
|
We'll wait for the browser to tell us that it has finished loading
|
||||||
all content and then we'll call the Angular `bootstrap` method.
|
all content and then we'll call the Angular `bootstrap` method.
|
||||||
|
|
||||||
The `bootstrap` method tells Angular to start the application with our
|
The `bootstrap` method tells Angular to start the application with our
|
||||||
`AppComponent` at the application root.
|
`AppComponent` at the application root.
|
||||||
We'd be correct to guess that someday our application will
|
We'd be correct to guess that someday our application will
|
||||||
consist of more components arising in tree-like fashion from this root.
|
consist of more components arising in tree-like fashion from this root.
|
||||||
|
@ -126,22 +126,22 @@ include ../../../_includes/_util-fns
|
||||||
### Wrapped in an IIFE
|
### Wrapped in an IIFE
|
||||||
We don't want to pollute the global namespace.
|
We don't want to pollute the global namespace.
|
||||||
We don't need an application namespace yet.
|
We don't need an application namespace yet.
|
||||||
So we'll surround the code in a simple IIFE
|
So we'll surround the code in a simple IIFE
|
||||||
("Immediately Invoked Function Execution")
|
("Immediately Invoked Function Execution")
|
||||||
wrapper.
|
wrapper.
|
||||||
|
|
||||||
Here is the entire file:
|
Here is the entire file:
|
||||||
+makeExample('quickstart/js/app.js', 'dsl')
|
+makeExample('quickstart/js/app.js', 'dsl')
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Create an *index.html*
|
## Create an *index.html*
|
||||||
|
|
||||||
**Add a new `index.html`** file to the project folder and enter the following HTML
|
**Add a new `index.html`** file to the project folder and enter the following HTML
|
||||||
|
|
||||||
+makeExample('quickstart/js/index.html', null, 'index.html')(format="")
|
+makeExample('quickstart/js/index.html', null, 'index.html')(format="")
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
Our app loads two script files in the `<head>` element:
|
Our app loads two script files in the `<head>` element:
|
||||||
|
|
||||||
>***angular2.sfx.dev.js***, the Angular 2 development library that puts
|
>***angular2.sfx.dev.js***, the Angular 2 development library that puts
|
||||||
|
@ -149,15 +149,15 @@ include ../../../_includes/_util-fns
|
||||||
|
|
||||||
>***app.js***, the application JavaScript we just wrote.
|
>***app.js***, the application JavaScript we just wrote.
|
||||||
|
|
||||||
In the `<body>`, there's an element called `<my-app>`.
|
In the `<body>`, there's an element called `<my-app>`.
|
||||||
This is the placeholder for the *root* of the
|
This is the placeholder for the *root* of the
|
||||||
application. Angular displays our application here.
|
application. Angular displays our application here.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Run it!
|
## Run it!
|
||||||
|
|
||||||
We need a file server to serve the static assets of our application
|
We need a file server to serve the static assets of our application
|
||||||
(*index.html* and *app.js*).
|
(*index.html* and *app.js*).
|
||||||
|
|
||||||
For this example we'll use the **live-server** that we installed with `npm`
|
For this example we'll use the **live-server** that we installed with `npm`
|
||||||
|
@ -170,27 +170,27 @@ include ../../../_includes/_util-fns
|
||||||
code npm start
|
code npm start
|
||||||
|
|
||||||
.alert.is-helpful
|
.alert.is-helpful
|
||||||
:markdown
|
:marked
|
||||||
That's the `npm` command we added earlier to the `scripts` section of `package.json`
|
That's the `npm` command we added earlier to the `scripts` section of `package.json`
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
**live-server** loads the browser for us and refreshes the page as we make
|
**live-server** loads the browser for us and refreshes the page as we make
|
||||||
changes to the application.
|
changes to the application.
|
||||||
|
|
||||||
In a few moments, a browser tab should open and display
|
In a few moments, a browser tab should open and display
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/quickstart/my-first-app.png' alt="Output of quickstart app")
|
img(src='/resources/images/devguide/quickstart/my-first-app.png' alt="Output of quickstart app")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
### Make some changes
|
### Make some changes
|
||||||
The `live-server` detects changes to our files and refreshes the browser page for us automatically.
|
The `live-server` detects changes to our files and refreshes the browser page for us automatically.
|
||||||
|
|
||||||
Try changing the message to "My SECOND Angular 2 app".
|
Try changing the message to "My SECOND Angular 2 app".
|
||||||
The `live-server` sees that change and reloads the browser.
|
The `live-server` sees that change and reloads the browser.
|
||||||
|
|
||||||
Keep `live-server` running in this terminal window and keep trying changes.
|
Keep `live-server` running in this terminal window and keep trying changes.
|
||||||
You can stop it anytime with `Ctrl-C`.
|
You can stop it anytime with `Ctrl-C`.
|
||||||
|
|
||||||
**Congratulations! We are in business** ... and ready to take
|
**Congratulations! We are in business** ... and ready to take
|
||||||
our app to the next level.
|
our app to the next level.
|
||||||
|
|
|
@ -1,26 +1,26 @@
|
||||||
include ../../../../_includes/_util-fns
|
include ../../../../_includes/_util-fns
|
||||||
:markdown
|
:marked
|
||||||
Dependency Injection is an important application design pattern.
|
Dependency Injection is an important application design pattern.
|
||||||
Angular has its own Dependency Injection framework and
|
Angular has its own Dependency Injection framework and
|
||||||
we really can't build an Angular application without it.
|
we really can't build an Angular application without it.
|
||||||
|
|
||||||
In this chapter we'll learn what Dependency Injection is, why we want it, and how to use it.
|
In this chapter we'll learn what Dependency Injection is, why we want it, and how to use it.
|
||||||
<a name="why-di"></a>
|
<a name="why-di"></a>
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Why Dependency Injection?
|
## Why Dependency Injection?
|
||||||
|
|
||||||
Let's start with the following code.
|
Let's start with the following code.
|
||||||
|
|
||||||
```
|
```
|
||||||
class Engine {}
|
class Engine {}
|
||||||
|
|
||||||
class Tires {}
|
class Tires {}
|
||||||
|
|
||||||
class Car {
|
class Car {
|
||||||
private engine: Engine;
|
private engine: Engine;
|
||||||
private tires: Tires;
|
private tires: Tires;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.engine = new Engine();
|
this.engine = new Engine();
|
||||||
this.tires = new Tires();
|
this.tires = new Tires();
|
||||||
|
@ -29,49 +29,49 @@ include ../../../../_includes/_util-fns
|
||||||
drive() {}
|
drive() {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Our `Car` creates everything it needs inside its constructor.
|
Our `Car` creates everything it needs inside its constructor.
|
||||||
What's the problem?
|
What's the problem?
|
||||||
|
|
||||||
The problem is that our `Car` class is brittle, inflexible, and hard to test.
|
The problem is that our `Car` class is brittle, inflexible, and hard to test.
|
||||||
|
|
||||||
Our `Car` needs an engine and tires. Instead of asking for them,
|
Our `Car` needs an engine and tires. Instead of asking for them,
|
||||||
the `Car` constructor creates its own copies by "new-ing" them from
|
the `Car` constructor creates its own copies by "new-ing" them from
|
||||||
the very specific classes, `Engine` and `Tires`.
|
the very specific classes, `Engine` and `Tires`.
|
||||||
|
|
||||||
What if the `Engine` class evolves and its constructor requires a parameter?
|
What if the `Engine` class evolves and its constructor requires a parameter?
|
||||||
Our `Car` is broken and stays broken until we rewrite it along the lines of
|
Our `Car` is broken and stays broken until we rewrite it along the lines of
|
||||||
`this.engine = new Engine(theNewParameter)`.
|
`this.engine = new Engine(theNewParameter)`.
|
||||||
We didn't care about `Engine` constructor parameters when we first wrote `Car`.
|
We didn't care about `Engine` constructor parameters when we first wrote `Car`.
|
||||||
We don't really care about them now.
|
We don't really care about them now.
|
||||||
But we'll *have* to start caring because
|
But we'll *have* to start caring because
|
||||||
when the definion of `Engine` changes, our `Car` class must change.
|
when the definion of `Engine` changes, our `Car` class must change.
|
||||||
That makes `Car` brittle.
|
That makes `Car` brittle.
|
||||||
|
|
||||||
What if we want to put a different brand of tires on our `Car`. Too bad.
|
What if we want to put a different brand of tires on our `Car`. Too bad.
|
||||||
We're locked into whatever brand the `Tires` class creates. That makes our `Car` inflexible.
|
We're locked into whatever brand the `Tires` class creates. That makes our `Car` inflexible.
|
||||||
|
|
||||||
Right now each new car gets its own engine. It can't share an engine with other cars.
|
Right now each new car gets its own engine. It can't share an engine with other cars.
|
||||||
While that makes sense for an automobile engine,
|
While that makes sense for an automobile engine,
|
||||||
we can think of other dependencies that should be shared ... like the onboard
|
we can think of other dependencies that should be shared ... like the onboard
|
||||||
wireless connection to the manufacturer's service center. Our `Car` lacks the flexibility
|
wireless connection to the manufacturer's service center. Our `Car` lacks the flexibility
|
||||||
to share services that have been created previously for other consumers.
|
to share services that have been created previously for other consumers.
|
||||||
|
|
||||||
When we write tests for our `Car` we're at the mercy of its hidden dependencies.
|
When we write tests for our `Car` we're at the mercy of its hidden dependencies.
|
||||||
Is it even possible to create a new `Engine` in a test environment?
|
Is it even possible to create a new `Engine` in a test environment?
|
||||||
What does `Engine`itself depend upon? What does that dependency depend on?
|
What does `Engine`itself depend upon? What does that dependency depend on?
|
||||||
Will a new instance of `Engine` make an asynchronous call to the server?
|
Will a new instance of `Engine` make an asynchronous call to the server?
|
||||||
We certainly don't want that going on during our tests.
|
We certainly don't want that going on during our tests.
|
||||||
|
|
||||||
What if our `Car` should flash a warning signal when tire pressure is low.
|
What if our `Car` should flash a warning signal when tire pressure is low.
|
||||||
How do we confirm that if actually does flash a warning
|
How do we confirm that if actually does flash a warning
|
||||||
if we can't swap in low-pressure tires during the test?
|
if we can't swap in low-pressure tires during the test?
|
||||||
|
|
||||||
We have no control over the car's hidden dependencies.
|
We have no control over the car's hidden dependencies.
|
||||||
When we can't control the dependencies, a class become difficult to test.
|
When we can't control the dependencies, a class become difficult to test.
|
||||||
|
|
||||||
How can we make `Car` more robust, more flexible, and more testable?
|
How can we make `Car` more robust, more flexible, and more testable?
|
||||||
|
|
||||||
That's super easy. We probably already know what to do. We change our `Car` constructor to this:
|
That's super easy. We probably already know what to do. We change our `Car` constructor to this:
|
||||||
<a name="ctor-injection"></a>
|
<a name="ctor-injection"></a>
|
||||||
```
|
```
|
||||||
|
@ -80,50 +80,50 @@ include ../../../../_includes/_util-fns
|
||||||
this.tires = tires;
|
this.tires = tires;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
See what happened? We moved the definition of the dependencies to the constructor.
|
See what happened? We moved the definition of the dependencies to the constructor.
|
||||||
Our `Car` class no longer creates an engine or tires.
|
Our `Car` class no longer creates an engine or tires.
|
||||||
It just consumes them.
|
It just consumes them.
|
||||||
|
|
||||||
Now we create a car by passing the engine and tires to the constructor.
|
Now we create a car by passing the engine and tires to the constructor.
|
||||||
```
|
```
|
||||||
var car = new Car(new Engine(), new Tires());
|
var car = new Car(new Engine(), new Tires());
|
||||||
```
|
```
|
||||||
How cool is that?
|
How cool is that?
|
||||||
The definition of the engine and tire dependencies are decoupled from the `Car` class itself.
|
The definition of the engine and tire dependencies are decoupled from the `Car` class itself.
|
||||||
We can pass in any kind of engine or tires we like, as long as they
|
We can pass in any kind of engine or tires we like, as long as they
|
||||||
conform to the general API requirements of an engine or tires.
|
conform to the general API requirements of an engine or tires.
|
||||||
|
|
||||||
If someone extends the `Engine` class, that is not `Car`'s problem.
|
If someone extends the `Engine` class, that is not `Car`'s problem.
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
The consumer of `Car` has the problem. The consumer must update the car creation code to
|
The consumer of `Car` has the problem. The consumer must update the car creation code to
|
||||||
something like:
|
something like:
|
||||||
```
|
```
|
||||||
var car = new Car(new Engine(theNewParameter), new Tires());
|
var car = new Car(new Engine(theNewParameter), new Tires());
|
||||||
```
|
```
|
||||||
The critical point is this: `Car` itself did not have to change.
|
The critical point is this: `Car` itself did not have to change.
|
||||||
We'll take care of the consumer's problem soon enough.
|
We'll take care of the consumer's problem soon enough.
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
The `Car` class is much easier to test because we are in complete control
|
The `Car` class is much easier to test because we are in complete control
|
||||||
of its dependencies.
|
of its dependencies.
|
||||||
We can pass mocks to the constructor that do exactly what we want them to do
|
We can pass mocks to the constructor that do exactly what we want them to do
|
||||||
during each test:
|
during each test:
|
||||||
```
|
```
|
||||||
var car = new Car(new MockEngine(), new MockLowPressureTires());
|
var car = new Car(new MockEngine(), new MockLowPressureTires());
|
||||||
```
|
```
|
||||||
|
|
||||||
**We just learned what Dependency Injection is**.
|
**We just learned what Dependency Injection is**.
|
||||||
|
|
||||||
It's a coding pattern in which a class receives its dependencies from external
|
It's a coding pattern in which a class receives its dependencies from external
|
||||||
sources rather than creating them itself.
|
sources rather than creating them itself.
|
||||||
|
|
||||||
Cool! But what about that poor consumer?
|
Cool! But what about that poor consumer?
|
||||||
Anyone who wants a `Car` must now
|
Anyone who wants a `Car` must now
|
||||||
create all three parts: the `Car`, `Engine`, and `Tires`.
|
create all three parts: the `Car`, `Engine`, and `Tires`.
|
||||||
The `Car` class shed its problems at the consumer's expense.
|
The `Car` class shed its problems at the consumer's expense.
|
||||||
We need something that takes care of assembling these parts for us.
|
We need something that takes care of assembling these parts for us.
|
||||||
|
|
||||||
We could write a giant class to do that:
|
We could write a giant class to do that:
|
||||||
```
|
```
|
||||||
class SuperFactory {
|
class SuperFactory {
|
||||||
|
@ -136,14 +136,14 @@ include ../../../../_includes/_util-fns
|
||||||
But maintaining it will be hairy as the application grows.
|
But maintaining it will be hairy as the application grows.
|
||||||
This `SuperFactory` is going to become a huge spider web of
|
This `SuperFactory` is going to become a huge spider web of
|
||||||
interdependent factory methods!
|
interdependent factory methods!
|
||||||
|
|
||||||
Wouldn't it be nice if we could simply list the things we want to build without
|
Wouldn't it be nice if we could simply list the things we want to build without
|
||||||
having to define which dependency gets injected into what?
|
having to define which dependency gets injected into what?
|
||||||
|
|
||||||
This is where the Dependency Injection Framework comes into play.
|
This is where the Dependency Injection Framework comes into play.
|
||||||
Imagine the framework had something called an `Injector`.
|
Imagine the framework had something called an `Injector`.
|
||||||
We register some classes with this `Injector` and it figures out how to create them.
|
We register some classes with this `Injector` and it figures out how to create them.
|
||||||
|
|
||||||
When we need a `Car`, we simply ask the `Injector` to get it for us and we're good to go.
|
When we need a `Car`, we simply ask the `Injector` to get it for us and we're good to go.
|
||||||
```
|
```
|
||||||
function main() {
|
function main() {
|
||||||
|
@ -153,103 +153,103 @@ include ../../../../_includes/_util-fns
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Everyone wins. The `Car` knows nothing about creating an `Engine` or `Tires`.
|
Everyone wins. The `Car` knows nothing about creating an `Engine` or `Tires`.
|
||||||
The consumer knows nothing about creating a `Car`.
|
The consumer knows nothing about creating a `Car`.
|
||||||
We don't have a gigantic factory class to maintain.
|
We don't have a gigantic factory class to maintain.
|
||||||
Both `Car` and consumer simply ask for what they need and the `Injector` delivers.
|
Both `Car` and consumer simply ask for what they need and the `Injector` delivers.
|
||||||
|
|
||||||
This is what a **Dependency InjectionFramework** is all about.
|
This is what a **Dependency InjectionFramework** is all about.
|
||||||
|
|
||||||
Now that we know what Dependency Injection is and appreciate its benefits,
|
Now that we know what Dependency Injection is and appreciate its benefits,
|
||||||
let's see how it is implemented in Angular.
|
let's see how it is implemented in Angular.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Angular Dependency Injection
|
## Angular Dependency Injection
|
||||||
|
|
||||||
Angular ships with its own Dependency Injection framework. This framework can also be used
|
Angular ships with its own Dependency Injection framework. This framework can also be used
|
||||||
as a standalone module by other applications and frameworks.
|
as a standalone module by other applications and frameworks.
|
||||||
|
|
||||||
That sounds nice. What does it do for us when building components in Angular?
|
That sounds nice. What does it do for us when building components in Angular?
|
||||||
Let's see, one step at a time.
|
Let's see, one step at a time.
|
||||||
|
|
||||||
We'll begin with a simplified version of the `HeroesComponent`
|
We'll begin with a simplified version of the `HeroesComponent`
|
||||||
that we built in the [The Tour of Heroes](../tutorial/).
|
that we built in the [The Tour of Heroes](../tutorial/).
|
||||||
```
|
```
|
||||||
import {Component} from 'angular2/angular2';
|
import {Component} from 'angular2/angular2';
|
||||||
import {Hero} from './hero';
|
import {Hero} from './hero';
|
||||||
import {HEROES} from './mock-heroes';
|
import {HEROES} from './mock-heroes';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-heroes'
|
selector: 'my-heroes'
|
||||||
templateUrl: 'app/heroes.component.html'
|
templateUrl: 'app/heroes.component.html'
|
||||||
})
|
})
|
||||||
export class HeroesComponent {
|
export class HeroesComponent {
|
||||||
|
|
||||||
heroes: Hero[] = HEROES;
|
heroes: Hero[] = HEROES;
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
It assigns a list of mocked heroes to its `heroes` property for binding within the template.
|
It assigns a list of mocked heroes to its `heroes` property for binding within the template.
|
||||||
Pretty straight forward.
|
Pretty straight forward.
|
||||||
|
|
||||||
Those heroes are currently a fixed, in-memory collection, defined in another file and imported by the component.
|
Those heroes are currently a fixed, in-memory collection, defined in another file and imported by the component.
|
||||||
That works in the early stages of development but it's far from ideal.
|
That works in the early stages of development but it's far from ideal.
|
||||||
As soon as we try to test this component or want to get our heroes data from a remote server,
|
As soon as we try to test this component or want to get our heroes data from a remote server,
|
||||||
we'll have to change this component's implementation of `heroes` and
|
we'll have to change this component's implementation of `heroes` and
|
||||||
fix every other use of the `HEROES` mock data.
|
fix every other use of the `HEROES` mock data.
|
||||||
|
|
||||||
Let's make a service that hides how we get Hero data.
|
Let's make a service that hides how we get Hero data.
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
Write this service in its own file. See [this note](#forward-ref) to understand why.
|
Write this service in its own file. See [this note](#forward-ref) to understand why.
|
||||||
:markdown
|
:marked
|
||||||
```
|
```
|
||||||
import {Hero} from './hero';
|
import {Hero} from './hero';
|
||||||
import {HEROES} from './mock-heroes';
|
import {HEROES} from './mock-heroes';
|
||||||
|
|
||||||
class HeroService {
|
class HeroService {
|
||||||
|
|
||||||
heroes: Hero[];
|
heroes: Hero[];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.heroes = HEROES;
|
this.heroes = HEROES;
|
||||||
}
|
}
|
||||||
|
|
||||||
getHeroes() {
|
getHeroes() {
|
||||||
return this.heroes;
|
return this.heroes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Our `HeroService` exposes a `getHeroes()` method that returns
|
Our `HeroService` exposes a `getHeroes()` method that returns
|
||||||
the same mock data as before but none of its consumers need to know that.
|
the same mock data as before but none of its consumers need to know that.
|
||||||
|
|
||||||
A service is nothing more than a class in Angular 2.
|
A service is nothing more than a class in Angular 2.
|
||||||
It remains nothing more than a class until we register it with
|
It remains nothing more than a class until we register it with
|
||||||
the Angular injector.
|
the Angular injector.
|
||||||
|
|
||||||
### Configuring the Injector
|
### Configuring the Injector
|
||||||
|
|
||||||
We don't have to create the injector.
|
We don't have to create the injector.
|
||||||
<a name="bootstrap"></a>
|
<a name="bootstrap"></a>
|
||||||
Angular creates an application-wide injector for us during the bootstrap process.
|
Angular creates an application-wide injector for us during the bootstrap process.
|
||||||
```
|
```
|
||||||
bootstrap(HeroesComponent);
|
bootstrap(HeroesComponent);
|
||||||
```
|
```
|
||||||
|
|
||||||
Let’s configure the injector at the same time that we bootstrap by adding
|
Let’s configure the injector at the same time that we bootstrap by adding
|
||||||
our `HeroService` to an array in the second argument.
|
our `HeroService` to an array in the second argument.
|
||||||
We'll explain that array when we talk about [providers](#providers) later in this chapter.
|
We'll explain that array when we talk about [providers](#providers) later in this chapter.
|
||||||
```
|
```
|
||||||
bootstrap(AppComponent, [HeroService]);
|
bootstrap(AppComponent, [HeroService]);
|
||||||
```
|
```
|
||||||
That’s it! The injector now knows about the `HeroService` which is available for injection across our entire application.
|
That’s it! The injector now knows about the `HeroService` which is available for injection across our entire application.
|
||||||
|
|
||||||
### Preparing the `HeroesComponent` for injection
|
### Preparing the `HeroesComponent` for injection
|
||||||
|
|
||||||
The `HeroesComponent` should get its heroes from this service.
|
The `HeroesComponent` should get its heroes from this service.
|
||||||
Per the dependency injection pattern, the component must "ask for" the service in its constructor [as we explained
|
Per the dependency injection pattern, the component must "ask for" the service in its constructor [as we explained
|
||||||
earlier](#ctor-injection)".
|
earlier](#ctor-injection)".
|
||||||
|
|
||||||
```
|
```
|
||||||
constructor(heroService: HeroService) {
|
constructor(heroService: HeroService) {
|
||||||
this.heroes = heroService.getHeroes();
|
this.heroes = heroService.getHeroes();
|
||||||
|
@ -257,19 +257,19 @@ include ../../../../_includes/_util-fns
|
||||||
```
|
```
|
||||||
<a name="di-metadata"></a>
|
<a name="di-metadata"></a>
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
Adding a parameter to the constructor isn't all that's happening here.
|
Adding a parameter to the constructor isn't all that's happening here.
|
||||||
|
|
||||||
We are writing the app in TypeScript and have followed the parameter name with a type notation, `:HeroService`.
|
We are writing the app in TypeScript and have followed the parameter name with a type notation, `:HeroService`.
|
||||||
The class is also decorated with the `@Component` decorator (scroll up to confirm that fact).
|
The class is also decorated with the `@Component` decorator (scroll up to confirm that fact).
|
||||||
|
|
||||||
When the TypeScript compiler evaluates this class, it sees the decorator and adds class metadata
|
When the TypeScript compiler evaluates this class, it sees the decorator and adds class metadata
|
||||||
into the generated JavaScript code. Within that metadata lurks the information that
|
into the generated JavaScript code. Within that metadata lurks the information that
|
||||||
associates the `heroService` parameter with the `HeroService` class.
|
associates the `heroService` parameter with the `HeroService` class.
|
||||||
|
|
||||||
That's how the Angular injector will know to inject an instance of the `HeroService` when it
|
That's how the Angular injector will know to inject an instance of the `HeroService` when it
|
||||||
creates a new `HeroesComponent`.
|
creates a new `HeroesComponent`.
|
||||||
:markdown
|
:marked
|
||||||
### Creating the `HeroesComponent` with the injector (implicitly)
|
### Creating the `HeroesComponent` with the injector (implicitly)
|
||||||
When we introduced the idea of an injector above, we showed how to create
|
When we introduced the idea of an injector above, we showed how to create
|
||||||
a new `Car` with that injector.
|
a new `Car` with that injector.
|
||||||
|
@ -281,24 +281,24 @@ include ../../../../_includes/_util-fns
|
||||||
var hc = injector.get(HeroesComponent);
|
var hc = injector.get(HeroesComponent);
|
||||||
```
|
```
|
||||||
We *could* write code like that if we wanted to. We just don't have to.
|
We *could* write code like that if we wanted to. We just don't have to.
|
||||||
Angular does that for us when it renders a `HeroesComponent`
|
Angular does that for us when it renders a `HeroesComponent`
|
||||||
whether we ask for it in an HTML template ...
|
whether we ask for it in an HTML template ...
|
||||||
```
|
```
|
||||||
<my-heroes></heroes>
|
<my-heroes></heroes>
|
||||||
```
|
```
|
||||||
... or navigate to a `HeroesComponent` view with the [router](./router.html).
|
... or navigate to a `HeroesComponent` view with the [router](./router.html).
|
||||||
|
|
||||||
### Singleton services
|
### Singleton services
|
||||||
We might wonder what happens when we inject the `HeroService` into other components.
|
We might wonder what happens when we inject the `HeroService` into other components.
|
||||||
Do we get the same instance every time?
|
Do we get the same instance every time?
|
||||||
|
|
||||||
Yes we do. Dependencies are singletons.
|
Yes we do. Dependencies are singletons.
|
||||||
We’ll discuss that later in our chapter about
|
We’ll discuss that later in our chapter about
|
||||||
[Hierarchical Injectors](./hierarchical-dependency-injection.html).
|
[Hierarchical Injectors](./hierarchical-dependency-injection.html).
|
||||||
|
|
||||||
### Testing the component
|
### Testing the component
|
||||||
We emphasized earlier that designing a class for dependency injection makes it easier to test.
|
We emphasized earlier that designing a class for dependency injection makes it easier to test.
|
||||||
|
|
||||||
Mission accomplished! We don't even need the Angular Dependency Injection system to test the `HeroesComponent`.
|
Mission accomplished! We don't even need the Angular Dependency Injection system to test the `HeroesComponent`.
|
||||||
We simply create a bew `HeroesComponent` with a mock service and poke at it:
|
We simply create a bew `HeroesComponent` with a mock service and poke at it:
|
||||||
```
|
```
|
||||||
|
@ -307,28 +307,28 @@ include ../../../../_includes/_util-fns
|
||||||
expect(hc.heroes.length).toEqual(mockService.getHeroes().length);
|
expect(hc.heroes.length).toEqual(mockService.getHeroes().length);
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
### When the service needs a service
|
### When the service needs a service
|
||||||
Our `HeroService` is very simple. It doesn't have any dependencies of its own.
|
Our `HeroService` is very simple. It doesn't have any dependencies of its own.
|
||||||
|
|
||||||
|
|
||||||
What if it had a dependency? What if it reported its activities through a logging service?
|
What if it had a dependency? What if it reported its activities through a logging service?
|
||||||
We'd apply the same "constructor injection" pattern.
|
We'd apply the same "constructor injection" pattern.
|
||||||
|
|
||||||
Here's a rewrite of `HeroService` with a new constructor that takes a `logger` parameter.
|
Here's a rewrite of `HeroService` with a new constructor that takes a `logger` parameter.
|
||||||
```
|
```
|
||||||
import {Hero} from './hero';
|
import {Hero} from './hero';
|
||||||
import {HEROES} from './mock-heroes';
|
import {HEROES} from './mock-heroes';
|
||||||
import {Logger} from './logger';
|
import {Logger} from './logger';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
class HeroService {
|
class HeroService {
|
||||||
|
|
||||||
heroes: Hero[];
|
heroes: Hero[];
|
||||||
|
|
||||||
constructor(private logger: Logger) {
|
constructor(private logger: Logger) {
|
||||||
this.heroes = HEROES;
|
this.heroes = HEROES;
|
||||||
}
|
}
|
||||||
|
|
||||||
getHeroes() {
|
getHeroes() {
|
||||||
this.logger.log('Getting heroes ...')
|
this.logger.log('Getting heroes ...')
|
||||||
return this.heroes;
|
return this.heroes;
|
||||||
|
@ -337,74 +337,74 @@ include ../../../../_includes/_util-fns
|
||||||
```
|
```
|
||||||
The constructor now asks for an injected instance of a `Logger` and stores it in a private property called `logger`.
|
The constructor now asks for an injected instance of a `Logger` and stores it in a private property called `logger`.
|
||||||
We call that property within our `getHeroes()` method when anyone asks for heroes.
|
We call that property within our `getHeroes()` method when anyone asks for heroes.
|
||||||
|
|
||||||
**The `@Injectable()` decoration catches our eye!**
|
**The `@Injectable()` decoration catches our eye!**
|
||||||
|
|
||||||
.alert.is-critical
|
.alert.is-critical
|
||||||
:markdown
|
:marked
|
||||||
**Always include the parentheses!** Always call `@Injectable()`. It's easy to forget the parentheses.
|
**Always include the parentheses!** Always call `@Injectable()`. It's easy to forget the parentheses.
|
||||||
Our application will fail mysteriously if we do. It bears repeating: **always include the parentheses.**
|
Our application will fail mysteriously if we do. It bears repeating: **always include the parentheses.**
|
||||||
:markdown
|
:marked
|
||||||
We haven't seen `@Injectable()` before.
|
We haven't seen `@Injectable()` before.
|
||||||
As it happens, we could have added it to `HeroService`. We didn't bother because we didn't need it then.
|
As it happens, we could have added it to `HeroService`. We didn't bother because we didn't need it then.
|
||||||
|
|
||||||
We need it now ... now that our service has an injected dependency.
|
We need it now ... now that our service has an injected dependency.
|
||||||
We need it because Angular requires constructor parameter metadata in order to inject a `Logger`.
|
We need it because Angular requires constructor parameter metadata in order to inject a `Logger`.
|
||||||
As [we mentioned earlier](#di-metadata), TypeScript *only generates metadata for classes that have a decorator*. .
|
As [we mentioned earlier](#di-metadata), TypeScript *only generates metadata for classes that have a decorator*. .
|
||||||
|
|
||||||
The `HeroesComponent` has an injected dependency too. Why don't we add `@Injectable()` to the `HeroesComponent`?
|
The `HeroesComponent` has an injected dependency too. Why don't we add `@Injectable()` to the `HeroesComponent`?
|
||||||
We *can* add it if we really want to. It isn't necessary because the `HeroesComponent` is already decorated with `@Component`.
|
We *can* add it if we really want to. It isn't necessary because the `HeroesComponent` is already decorated with `@Component`.
|
||||||
TypeScript generates metadata for *any* class with a decorator and *any* decorator will do.
|
TypeScript generates metadata for *any* class with a decorator and *any* decorator will do.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
<a name="providers"></a>
|
<a name="providers"></a>
|
||||||
## Injector Providers
|
## Injector Providers
|
||||||
|
|
||||||
Remember when we added the `HeroService` to an array in the [bootstrap](#bootstrap) process?
|
Remember when we added the `HeroService` to an array in the [bootstrap](#bootstrap) process?
|
||||||
```
|
```
|
||||||
bootstrap(AppComponent, [HeroService]);
|
bootstrap(AppComponent, [HeroService]);
|
||||||
```
|
```
|
||||||
That list of classes is actually a list of **providers**.
|
That list of classes is actually a list of **providers**.
|
||||||
|
|
||||||
"Providers" create the instances of the things that we ask the injector to inject.
|
"Providers" create the instances of the things that we ask the injector to inject.
|
||||||
There are many ways ways to "provide" a thing that has the necessary shape and behavior to serve as a `HeroService`.
|
There are many ways ways to "provide" a thing that has the necessary shape and behavior to serve as a `HeroService`.
|
||||||
A class is a natural provider - it's meant to be created. But it's not the only way
|
A class is a natural provider - it's meant to be created. But it's not the only way
|
||||||
to produce something injectable. We could hand the injector an object to return. We could give it a factory function to call.
|
to produce something injectable. We could hand the injector an object to return. We could give it a factory function to call.
|
||||||
Any of these approaches might be a good choice under the right circumstances.
|
Any of these approaches might be a good choice under the right circumstances.
|
||||||
|
|
||||||
What matters is that the injector knows what to do when something asks for a `HeroService`.
|
What matters is that the injector knows what to do when something asks for a `HeroService`.
|
||||||
|
|
||||||
### The Provider Class
|
### The Provider Class
|
||||||
|
|
||||||
When we wrote ...
|
When we wrote ...
|
||||||
```
|
```
|
||||||
[HeroService];
|
[HeroService];
|
||||||
```
|
```
|
||||||
we used a short-hand expression for provider registration.
|
we used a short-hand expression for provider registration.
|
||||||
Angular expanded that short-hand into a call to the Angular `provide` method
|
Angular expanded that short-hand into a call to the Angular `provide` method
|
||||||
```
|
```
|
||||||
[provide(HeroService, {useClass:HeroService})];
|
[provide(HeroService, {useClass:HeroService})];
|
||||||
```
|
```
|
||||||
and the `provide` method in turn creates a new instance of the Angular
|
and the `provide` method in turn creates a new instance of the Angular
|
||||||
[Provider class](http://localhost:3000/docs/ts/latest/api/core/Provider-class.html):
|
[Provider class](http://localhost:3000/docs/ts/latest/api/core/Provider-class.html):
|
||||||
```
|
```
|
||||||
[new Provider(HeroService, {useClass:HeroService})]
|
[new Provider(HeroService, {useClass:HeroService})]
|
||||||
```
|
```
|
||||||
This provider instance associates a `HeroService` *token*
|
This provider instance associates a `HeroService` *token*
|
||||||
with code that can create an *instance* of a `HeroService`.
|
with code that can create an *instance* of a `HeroService`.
|
||||||
|
|
||||||
The first parameter is the [token](#token) that serves as the key for both locating a dependency value
|
The first parameter is the [token](#token) that serves as the key for both locating a dependency value
|
||||||
and registering the provider.
|
and registering the provider.
|
||||||
|
|
||||||
The second parameter is a provider definition object
|
The second parameter is a provider definition object
|
||||||
which we think of as a "recipe" for creating the dependency value.
|
which we think of as a "recipe" for creating the dependency value.
|
||||||
There are many ways to create dependency values ... and many ways to write a recipe.
|
There are many ways to create dependency values ... and many ways to write a recipe.
|
||||||
|
|
||||||
### Alternative Class Providers
|
### Alternative Class Providers
|
||||||
|
|
||||||
Occasionally we'll ask a different class to provide the service.
|
Occasionally we'll ask a different class to provide the service.
|
||||||
|
|
||||||
We do that regularly when testing a component that we're creating with dependency injection.
|
We do that regularly when testing a component that we're creating with dependency injection.
|
||||||
In this example, we tell the injector
|
In this example, we tell the injector
|
||||||
to return a `MockHeroService` when something asks for the `HeroService`.
|
to return a `MockHeroService` when something asks for the `HeroService`.
|
||||||
|
@ -414,42 +414,42 @@ include ../../../../_includes/_util-fns
|
||||||
]);
|
]);
|
||||||
```
|
```
|
||||||
### Value Providers
|
### Value Providers
|
||||||
|
|
||||||
Sometimes it's easier to provide a ready-made object rather than ask the injector to create it from a class.
|
Sometimes it's easier to provide a ready-made object rather than ask the injector to create it from a class.
|
||||||
|
|
||||||
We do that a lot when we write tests. We might write the following test setup
|
We do that a lot when we write tests. We might write the following test setup
|
||||||
for tests that explore how the `HeroComponent` behaves when the `HeroService`
|
for tests that explore how the `HeroComponent` behaves when the `HeroService`
|
||||||
returns an empty hero list.
|
returns an empty hero list.
|
||||||
```
|
```
|
||||||
beforeEachProviders(() => {
|
beforeEachProviders(() => {
|
||||||
|
|
||||||
let emptyHeroService = { getHeroes: () => [] };
|
let emptyHeroService = { getHeroes: () => [] };
|
||||||
|
|
||||||
return [ provide(HeroService, {useValue: emptyHeroService}) ];
|
return [ provide(HeroService, {useValue: emptyHeroService}) ];
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
Notice we defined the recipe with `useValue` instead of `useClass`.
|
Notice we defined the recipe with `useValue` instead of `useClass`.
|
||||||
|
|
||||||
### Factory Providers
|
### Factory Providers
|
||||||
|
|
||||||
Sometimes the best choice for a provider is neither a class nor a value.
|
Sometimes the best choice for a provider is neither a class nor a value.
|
||||||
|
|
||||||
Suppose our HeroService has some cool new feature that we're only offering to "special" users.
|
Suppose our HeroService has some cool new feature that we're only offering to "special" users.
|
||||||
The HeroService shouldn't know about users and
|
The HeroService shouldn't know about users and
|
||||||
we won't know if the current user is special until runtime anyway.
|
we won't know if the current user is special until runtime anyway.
|
||||||
We decide to extend our `HeroService` constructor to accept a `useCoolFeature` flag
|
We decide to extend our `HeroService` constructor to accept a `useCoolFeature` flag
|
||||||
that toggles the feature on or off.
|
that toggles the feature on or off.
|
||||||
We rewrite the `HeroService` again as follows.
|
We rewrite the `HeroService` again as follows.
|
||||||
```
|
```
|
||||||
@Injectable()
|
@Injectable()
|
||||||
class HeroService {
|
class HeroService {
|
||||||
|
|
||||||
heroes: Hero[];
|
heroes: Hero[];
|
||||||
|
|
||||||
constructor(private logger: Logger, private useCoolFeature: boolean) {
|
constructor(private logger: Logger, private useCoolFeature: boolean) {
|
||||||
this.heroes = HEROES;
|
this.heroes = HEROES;
|
||||||
}
|
}
|
||||||
|
|
||||||
getHeroes() {
|
getHeroes() {
|
||||||
let msg = this.useCoolFeature ? 'the cool new way' : 'the old way';
|
let msg = this.useCoolFeature ? 'the cool new way' : 'the old way';
|
||||||
this.logger.log('Getting heroes ...' + msg)
|
this.logger.log('Getting heroes ...' + msg)
|
||||||
|
@ -457,9 +457,9 @@ include ../../../../_includes/_util-fns
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
The feature flag is a simple boolean value. We'd like to inject the flag but it seems silly to write an entire class for a
|
The feature flag is a simple boolean value. We'd like to inject the flag but it seems silly to write an entire class for a
|
||||||
simple flag.
|
simple flag.
|
||||||
|
|
||||||
We can replace the `HeroService` provider with a factory function that creates a properly configured `HeroService` for the current user.
|
We can replace the `HeroService` provider with a factory function that creates a properly configured `HeroService` for the current user.
|
||||||
We'll' build up to that result, beginning with our definition of the factory function:
|
We'll' build up to that result, beginning with our definition of the factory function:
|
||||||
```
|
```
|
||||||
|
@ -468,59 +468,59 @@ include ../../../../_includes/_util-fns
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
The factory takes two parameters: the logger service and a user service.
|
The factory takes two parameters: the logger service and a user service.
|
||||||
The logger we pass straight to the constructor as we did before.
|
The logger we pass straight to the constructor as we did before.
|
||||||
|
|
||||||
We'll know to use the cool new feature if the `userService.user.isSpecial` flag is true,
|
We'll know to use the cool new feature if the `userService.user.isSpecial` flag is true,
|
||||||
a fact we can't know until runtime.
|
a fact we can't know until runtime.
|
||||||
:markdown
|
:marked
|
||||||
We use dependency injection everywhere so of course the factory function depends on
|
We use dependency injection everywhere so of course the factory function depends on
|
||||||
two injected services: `Logger` and `UserService`.
|
two injected services: `Logger` and `UserService`.
|
||||||
We declare those requirements in our provider definition object:
|
We declare those requirements in our provider definition object:
|
||||||
```
|
```
|
||||||
let heroServiceDefinition = {
|
let heroServiceDefinition = {
|
||||||
useFactory: heroServiceFactory,
|
useFactory: heroServiceFactory,
|
||||||
deps: [Logger, UserService]
|
deps: [Logger, UserService]
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
The `useFactory` field tells Angular that the provider is a factory function and that its implementation is the `heroServiceFactory`.
|
The `useFactory` field tells Angular that the provider is a factory function and that its implementation is the `heroServiceFactory`.
|
||||||
|
|
||||||
The `deps` property is an array of [provider tokens](#token).
|
The `deps` property is an array of [provider tokens](#token).
|
||||||
The `Logger` and `UserService` classes serve as tokens for their own class providers.
|
The `Logger` and `UserService` classes serve as tokens for their own class providers.
|
||||||
:markdown
|
:marked
|
||||||
Finally, we create the provider and adjust the bootstrapping to include that provider
|
Finally, we create the provider and adjust the bootstrapping to include that provider
|
||||||
among its provider registrations.
|
among its provider registrations.
|
||||||
```
|
```
|
||||||
let heroServiceProvider = provide(HeroService, heroServiceDefinition);
|
let heroServiceProvider = provide(HeroService, heroServiceDefinition);
|
||||||
|
|
||||||
bootstrap(AppComponent, [heroServiceProvider, Logger, UserService]);
|
bootstrap(AppComponent, [heroServiceProvider, Logger, UserService]);
|
||||||
```
|
```
|
||||||
|
|
||||||
### String tokens
|
### String tokens
|
||||||
|
|
||||||
Sometimes we have an object dependency rather than a class dependency.
|
Sometimes we have an object dependency rather than a class dependency.
|
||||||
|
|
||||||
Applications often define configuration objects with lots of small facts like the title of the application or the address of a web api endpoint.
|
Applications often define configuration objects with lots of small facts like the title of the application or the address of a web api endpoint.
|
||||||
These configuration objects aren't always instances of a class. They're just objects ... like this one:
|
These configuration objects aren't always instances of a class. They're just objects ... like this one:
|
||||||
```
|
```
|
||||||
let config = {
|
let config = {
|
||||||
apiEndpoint: 'api.heroes.com',
|
apiEndpoint: 'api.heroes.com',
|
||||||
title: 'The Hero Employment Agency'
|
title: 'The Hero Employment Agency'
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
We'd like to make this `config` object available for injection.
|
We'd like to make this `config` object available for injection.
|
||||||
We know we can register an object with a "Value Provider". But what do we use for the token?
|
We know we can register an object with a "Value Provider". But what do we use for the token?
|
||||||
|
|
||||||
<a id="token"></a>
|
<a id="token"></a>
|
||||||
Until now, we've always asked the class to play the token role
|
Until now, we've always asked the class to play the token role
|
||||||
whether we wrote a provider with a class, value, or factory recipe.
|
whether we wrote a provider with a class, value, or factory recipe.
|
||||||
This time we don't have a class to serve as a token. There is no `Config` class.
|
This time we don't have a class to serve as a token. There is no `Config` class.
|
||||||
|
|
||||||
Fortunately, the token can be a string, a class type, or an
|
Fortunately, the token can be a string, a class type, or an
|
||||||
[OpaqueToken](http://localhost:3000/docs/ts/latest/api/core/OpaqueToken-class.html).
|
[OpaqueToken](http://localhost:3000/docs/ts/latest/api/core/OpaqueToken-class.html).
|
||||||
Internally, the `Provider` turns the string and class parameter into an `OpaqueToken`;
|
Internally, the `Provider` turns the string and class parameter into an `OpaqueToken`;
|
||||||
the injector locates dependency values and providers by this token.
|
the injector locates dependency values and providers by this token.
|
||||||
|
|
||||||
|
@ -532,7 +532,7 @@ include ../../../../_includes/_util-fns
|
||||||
]);
|
]);
|
||||||
```
|
```
|
||||||
|
|
||||||
Let's apply what we've learned and update the `HeroesComponent` constructor so it can display the configured title.
|
Let's apply what we've learned and update the `HeroesComponent` constructor so it can display the configured title.
|
||||||
Right now the constructor signature is
|
Right now the constructor signature is
|
||||||
```
|
```
|
||||||
constructor(heroService: HeroService)
|
constructor(heroService: HeroService)
|
||||||
|
@ -546,38 +546,38 @@ include ../../../../_includes/_util-fns
|
||||||
We'll need a little help from another Angular decorator called `@Inject`.
|
We'll need a little help from another Angular decorator called `@Inject`.
|
||||||
```
|
```
|
||||||
import {Inject} from 'angular2/angular2'
|
import {Inject} from 'angular2/angular2'
|
||||||
|
|
||||||
constructor(heroService: HeroService, @Inject('app.config') config)
|
constructor(heroService: HeroService, @Inject('app.config') config)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
# Next Steps
|
# Next Steps
|
||||||
We learned the basics of Angular Dependency Injection in this chapter.
|
We learned the basics of Angular Dependency Injection in this chapter.
|
||||||
|
|
||||||
The Angular Dependency Injection is more capable than we've described.
|
The Angular Dependency Injection is more capable than we've described.
|
||||||
We can learn more about its advanced features, beginning with its support for
|
We can learn more about its advanced features, beginning with its support for
|
||||||
a hierarchy of nested injectors in the next
|
a hierarchy of nested injectors in the next
|
||||||
[Dependency Injection chapter](./hierarchical-dependency-injection.html)
|
[Dependency Injection chapter](./hierarchical-dependency-injection.html)
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
<a name="forward-ref"></a>
|
<a name="forward-ref"></a>
|
||||||
:markdown
|
:marked
|
||||||
### Appendix: Why we recommend one class per file
|
### Appendix: Why we recommend one class per file
|
||||||
Developers expect one class per file. Multiple classes per file is confusing and is best avoided.
|
Developers expect one class per file. Multiple classes per file is confusing and is best avoided.
|
||||||
If we define every class in its own file, there is nothing in this note to worry about.
|
If we define every class in its own file, there is nothing in this note to worry about.
|
||||||
Move along!
|
Move along!
|
||||||
|
|
||||||
If we scorn this advice
|
If we scorn this advice
|
||||||
and we add our `HeroService` class to the `HeroesComponent` file anyway,
|
and we add our `HeroService` class to the `HeroesComponent` file anyway,
|
||||||
**define the `HeroesComponent` last!**
|
**define the `HeroesComponent` last!**
|
||||||
If we put it define component before the service,
|
If we put it define component before the service,
|
||||||
we'll get a runtime null reference error.
|
we'll get a runtime null reference error.
|
||||||
|
|
||||||
To understand why, paste the following incorrect, ultra-simplified rendition of these two
|
To understand why, paste the following incorrect, ultra-simplified rendition of these two
|
||||||
classes into the [TypeScript playground](http://www.typescriptlang.org/Playground).
|
classes into the [TypeScript playground](http://www.typescriptlang.org/Playground).
|
||||||
|
|
||||||
```
|
```
|
||||||
class HeroesComponent {
|
class HeroesComponent {
|
||||||
static $providers=[HeroService]
|
static $providers=[HeroService]
|
||||||
|
@ -588,18 +588,18 @@ include ../../../../_includes/_util-fns
|
||||||
alert(HeroesComponent.$providers)
|
alert(HeroesComponent.$providers)
|
||||||
```
|
```
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
The `HeroService` is incorrectly defined below the `HeroComponent`.
|
The `HeroService` is incorrectly defined below the `HeroComponent`.
|
||||||
|
|
||||||
The `$providers` static property represents the metadata about the injected `HeroService`
|
The `$providers` static property represents the metadata about the injected `HeroService`
|
||||||
that TypeScript compiler would add to the component class.
|
that TypeScript compiler would add to the component class.
|
||||||
|
|
||||||
The `alert` simulates the action of the Dependency Injector at runtime
|
The `alert` simulates the action of the Dependency Injector at runtime
|
||||||
when it attempts to create a `HeroesComponent`.
|
when it attempts to create a `HeroesComponent`.
|
||||||
:markdown
|
:marked
|
||||||
Run it. The alert appears but displays nothing.
|
Run it. The alert appears but displays nothing.
|
||||||
This is the equivalent of the null reference error thrown at runtime.
|
This is the equivalent of the null reference error thrown at runtime.
|
||||||
|
|
||||||
We understand why when we review the generated JavaScript which looks like this:
|
We understand why when we review the generated JavaScript which looks like this:
|
||||||
```
|
```
|
||||||
var HeroesComponent = (function () {
|
var HeroesComponent = (function () {
|
||||||
|
@ -608,28 +608,28 @@ include ../../../../_includes/_util-fns
|
||||||
HeroesComponent.$providers = [HeroService];
|
HeroesComponent.$providers = [HeroService];
|
||||||
return HeroesComponent;
|
return HeroesComponent;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
var HeroService = (function () {
|
var HeroService = (function () {
|
||||||
function HeroService() {
|
function HeroService() {
|
||||||
}
|
}
|
||||||
return HeroService;
|
return HeroService;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
alert(HeroesComponent.$providers);
|
alert(HeroesComponent.$providers);
|
||||||
```
|
```
|
||||||
|
|
||||||
Notice that the TypeScript compiler turns classes into function expressions
|
Notice that the TypeScript compiler turns classes into function expressions
|
||||||
assigned to variables. The value of the captured `HeroService` variable is undefined
|
assigned to variables. The value of the captured `HeroService` variable is undefined
|
||||||
when the `$providers` array is assigned. The `HeroService` variable gets its value too late
|
when the `$providers` array is assigned. The `HeroService` variable gets its value too late
|
||||||
to be captured.
|
to be captured.
|
||||||
|
|
||||||
Reverse the order of class definition so that the `HeroService`
|
Reverse the order of class definition so that the `HeroService`
|
||||||
appears before the `HeroesComponent` that requires it.
|
appears before the `HeroesComponent` that requires it.
|
||||||
Run again. This time the alert displays the `HeroService` function definition.
|
Run again. This time the alert displays the `HeroService` function definition.
|
||||||
|
|
||||||
If we insist on defining the `HeroService` in the same file and insist on
|
If we insist on defining the `HeroService` in the same file and insist on
|
||||||
defining the component first, Angular offers a way to make that work.
|
defining the component first, Angular offers a way to make that work.
|
||||||
The `forwardRef()` method let's us reference a class
|
The `forwardRef()` method let's us reference a class
|
||||||
before it has been defined.
|
before it has been defined.
|
||||||
Learn more about this problem and the `forwardRef()`
|
Learn more about this problem and the `forwardRef()`
|
||||||
in this [blog post](http://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html).
|
in this [blog post](http://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html).
|
||||||
|
|
|
@ -2,7 +2,7 @@ include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
<!-- http://plnkr.co/edit/x9JYbC -->
|
<!-- http://plnkr.co/edit/x9JYbC -->
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
## Displaying Component Properties
|
## Displaying Component Properties
|
||||||
|
|
||||||
We typically display data in Angular by binding controls in an HTML template
|
We typically display data in Angular by binding controls in an HTML template
|
||||||
|
@ -17,7 +17,7 @@ figure.image-display
|
||||||
img(src="/resources/images/devguide/displaying-data/final.png" alt="Final UI")
|
img(src="/resources/images/devguide/displaying-data/final.png" alt="Final UI")
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Showing component properties with interpolation
|
## Showing component properties with interpolation
|
||||||
The easiest way to display a component property
|
The easiest way to display a component property
|
||||||
is to bind the property name through interpolation.
|
is to bind the property name through interpolation.
|
||||||
|
@ -32,7 +32,7 @@ figure.image-display
|
||||||
|
|
||||||
+makeExample('displaying-data/ts/src/app/app.1.ts')
|
+makeExample('displaying-data/ts/src/app/app.1.ts')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
We added two properties to the formerly empty component: `title` and `myHero`.
|
We added two properties to the formerly empty component: `title` and `myHero`.
|
||||||
|
|
||||||
Our revised template displays the two component properties using the double curly brace
|
Our revised template displays the two component properties using the double curly brace
|
||||||
|
@ -40,24 +40,24 @@ figure.image-display
|
||||||
|
|
||||||
+makeExample('displaying-data/ts/src/app/app.1.ts', 'template')
|
+makeExample('displaying-data/ts/src/app/app.1.ts', 'template')
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
The template is a multi-line string within ECMAScript 2015 back-tics (\`).
|
The template is a multi-line string within ECMAScript 2015 back-tics (\`).
|
||||||
The back-tick (\`) is not the same character as a single quote (').
|
The back-tick (\`) is not the same character as a single quote (').
|
||||||
It has many nice features. The feature we're exploiting is
|
It has many nice features. The feature we're exploiting is
|
||||||
the ability to compose the string over several lines which
|
the ability to compose the string over several lines which
|
||||||
makes for much more readable HTML.
|
makes for much more readable HTML.
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Angular automatically pulls the value of the `title` and `myHero` properties from the component and
|
Angular automatically pulls the value of the `title` and `myHero` properties from the component and
|
||||||
inserts those values into the browser. Angular will update the display
|
inserts those values into the browser. Angular will update the display
|
||||||
when these properties change.
|
when these properties change.
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
More precisely, the re-display occurs after some kind of asynchronous event related to
|
More precisely, the re-display occurs after some kind of asynchronous event related to
|
||||||
the view such as a keystroke, a timer completion, or an asynch `XHR` response.
|
the view such as a keystroke, a timer completion, or an asynch `XHR` response.
|
||||||
We don't have those in this sample.
|
We don't have those in this sample.
|
||||||
But then the properties aren't changing on their own either. For the moment we must operate on faith.
|
But then the properties aren't changing on their own either. For the moment we must operate on faith.
|
||||||
:markdown
|
:marked
|
||||||
Notice that we haven't called **new** to create an instance of the `AppComponent` class.
|
Notice that we haven't called **new** to create an instance of the `AppComponent` class.
|
||||||
Angular is creating an instance for us. How?
|
Angular is creating an instance for us. How?
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ figure.image-display
|
||||||
Remember back in QuickStart that we added the `<my-app>` element to the body of our `index.html`
|
Remember back in QuickStart that we added the `<my-app>` element to the body of our `index.html`
|
||||||
+makeExample('displaying-data/ts/src/index.html', 'my-app')
|
+makeExample('displaying-data/ts/src/index.html', 'my-app')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
When we bootstrap with the `AppComponent` class (see the bottom of `app.ts`), Angular looks for a `<my-app>`
|
When we bootstrap with the `AppComponent` class (see the bottom of `app.ts`), Angular looks for a `<my-app>`
|
||||||
in the `index.html`, finds it, instantiates an instance of `AppComponent`, and renders it
|
in the `index.html`, finds it, instantiates an instance of `AppComponent`, and renders it
|
||||||
inside the `<my-app>` tag.
|
inside the `<my-app>` tag.
|
||||||
|
@ -90,40 +90,40 @@ figure.image-display
|
||||||
Some folks prefer to declare the properties and initialize them within a constructor like this:
|
Some folks prefer to declare the properties and initialize them within a constructor like this:
|
||||||
+makeExample('displaying-data/ts/src/app/app-ctor.ts', 'app-ctor')
|
+makeExample('displaying-data/ts/src/app/app-ctor.ts', 'app-ctor')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
That's fine too. The choice between them is a matter of taste and organization policy.
|
That's fine too. The choice between them is a matter of taste and organization policy.
|
||||||
We'll adopt the more terse "variable assignment" style in this chapter simply because
|
We'll adopt the more terse "variable assignment" style in this chapter simply because
|
||||||
there will be less code to read.
|
there will be less code to read.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Showing an array property with NgFor
|
## Showing an array property with NgFor
|
||||||
|
|
||||||
We want to display a list of heroes. We begin by adding a mock heroes name array to the component,
|
We want to display a list of heroes. We begin by adding a mock heroes name array to the component,
|
||||||
just above `myHero` and redefine `myHero` to be the first name in the array.
|
just above `myHero` and redefine `myHero` to be the first name in the array.
|
||||||
+makeExample('displaying-data/ts/src/app/app.2.ts', 'mock-heroes')
|
+makeExample('displaying-data/ts/src/app/app.2.ts', 'mock-heroes')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Now we use the Angular `NgFor` "repeater" Directive in the template to display
|
Now we use the Angular `NgFor` "repeater" Directive in the template to display
|
||||||
each item in the `heroes` list.
|
each item in the `heroes` list.
|
||||||
|
|
||||||
+makeExample('displaying-data/ts/src/app/app.2.ts', 'template')
|
+makeExample('displaying-data/ts/src/app/app.2.ts', 'template')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Our presentation is the familiar HTML unordered list with `<ul>` and `<li>` tags. Let's focus on the `<li>` tag.
|
Our presentation is the familiar HTML unordered list with `<ul>` and `<li>` tags. Let's focus on the `<li>` tag.
|
||||||
+makeExample('displaying-data/ts/src/app/app.2.ts', 'li-repeater')
|
+makeExample('displaying-data/ts/src/app/app.2.ts', 'li-repeater')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
We added a somewhat mysterious `*ng-for` to the `<li>` element.
|
We added a somewhat mysterious `*ng-for` to the `<li>` element.
|
||||||
That's the Angular "repeater" directive.
|
That's the Angular "repeater" directive.
|
||||||
It's presence on the `<li>` tag marks that `<li>` element (and its children) as the "repeater template".
|
It's presence on the `<li>` tag marks that `<li>` element (and its children) as the "repeater template".
|
||||||
|
|
||||||
.alert.is-important
|
.alert.is-important
|
||||||
:markdown
|
:marked
|
||||||
Don't forget the leading asterisk (\*) in front of `*ng-for`. It is an essential part of the syntax.
|
Don't forget the leading asterisk (\*) in front of `*ng-for`. It is an essential part of the syntax.
|
||||||
Learn more about this and `NgFor` in the [Template Syntax](./template-syntax.html#ng-for) chapter.
|
Learn more about this and `NgFor` in the [Template Syntax](./template-syntax.html#ng-for) chapter.
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Notice the `#hero` in the `NgFor` double-quoted instruction.
|
Notice the `#hero` in the `NgFor` double-quoted instruction.
|
||||||
The `#hero` is a "[template local variable](./template-syntax.html#local-vars")" *declaration*.
|
The `#hero` is a "[template local variable](./template-syntax.html#local-vars")" *declaration*.
|
||||||
The (#) prefix declares a local variable name named `hero`.
|
The (#) prefix declares a local variable name named `hero`.
|
||||||
|
@ -133,11 +133,11 @@ figure.image-display
|
||||||
context for the interpolation in the double curly braces.
|
context for the interpolation in the double curly braces.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
We happened to give `NgFor` an array to display.
|
We happened to give `NgFor` an array to display.
|
||||||
In fact, `NgFor` can repeat items for any [iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols)
|
In fact, `NgFor` can repeat items for any [iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols)
|
||||||
object.
|
object.
|
||||||
:markdown
|
:marked
|
||||||
## Register the NgFor Directive
|
## Register the NgFor Directive
|
||||||
|
|
||||||
Angular doesn't know that this template uses the `NgFor` directive.
|
Angular doesn't know that this template uses the `NgFor` directive.
|
||||||
|
@ -149,20 +149,20 @@ figure.image-display
|
||||||
Look for it in the following:
|
Look for it in the following:
|
||||||
+makeExample('displaying-data/ts/src/app/app.2.ts', 'imports')
|
+makeExample('displaying-data/ts/src/app/app.2.ts', 'imports')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Second, we register `NgFor` as a directive accessible to the template by updating the
|
Second, we register `NgFor` as a directive accessible to the template by updating the
|
||||||
`@Component` decorator with a `directives` array property whose only item is `NgFor`:
|
`@Component` decorator with a `directives` array property whose only item is `NgFor`:
|
||||||
|
|
||||||
+makeExample('displaying-data/ts/src/app/app.2.ts', 'directives')
|
+makeExample('displaying-data/ts/src/app/app.2.ts', 'directives')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Now the heroes will appear in the view as an unordered list.
|
Now the heroes will appear in the view as an unordered list.
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src="/resources/images/devguide/displaying-data/hero-names-list.png" alt="After ngfor")
|
img(src="/resources/images/devguide/displaying-data/hero-names-list.png" alt="After ngfor")
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Creating a class for the data
|
## Creating a class for the data
|
||||||
|
|
||||||
We are defining our data directly inside our component.
|
We are defining our data directly inside our component.
|
||||||
|
@ -177,7 +177,7 @@ figure.image-display
|
||||||
Create a new file called `hero.ts` and add the following short snippet to it.
|
Create a new file called `hero.ts` and add the following short snippet to it.
|
||||||
+makeExample('displaying-data/ts/src/app/hero.ts')
|
+makeExample('displaying-data/ts/src/app/hero.ts')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
We've defined a class with a constructor and two properties: `id` and `name`.
|
We've defined a class with a constructor and two properties: `id` and `name`.
|
||||||
|
|
||||||
If we are new to TypeScript, it may not look like we have properties. But we do. We're taking
|
If we are new to TypeScript, it may not look like we have properties. But we do. We're taking
|
||||||
|
@ -186,30 +186,30 @@ figure.image-display
|
||||||
Consider the first parameter:
|
Consider the first parameter:
|
||||||
+makeExample('displaying-data/ts/src/app/hero.ts', 'id-parameter')
|
+makeExample('displaying-data/ts/src/app/hero.ts', 'id-parameter')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
That brief syntax simultaneously
|
That brief syntax simultaneously
|
||||||
* declares a constructor parameter and its type
|
* declares a constructor parameter and its type
|
||||||
* declare a public property of the same name
|
* declare a public property of the same name
|
||||||
* initializes that property with the corresponding argument when we "new" an instance of the class.
|
* initializes that property with the corresponding argument when we "new" an instance of the class.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Use the Hero class
|
## Use the Hero class
|
||||||
Let's redefine the heroes property in our component to return an array of these Heroes
|
Let's redefine the heroes property in our component to return an array of these Heroes
|
||||||
and also set the `myHero` property with the first of these mock heroes.
|
and also set the `myHero` property with the first of these mock heroes.
|
||||||
+makeExample('displaying-data/ts/src/app/app.3.ts', 'heroes')
|
+makeExample('displaying-data/ts/src/app/app.3.ts', 'heroes')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
We'll have to update the template.
|
We'll have to update the template.
|
||||||
At the moment it displays the entire hero object which used to be a string value.
|
At the moment it displays the entire hero object which used to be a string value.
|
||||||
Let's fix that so we interpolate the `hero.name` property
|
Let's fix that so we interpolate the `hero.name` property
|
||||||
+makeExample('displaying-data/ts/src/app/app.3.ts', 'template')
|
+makeExample('displaying-data/ts/src/app/app.3.ts', 'template')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Our display looks the same but we know how much better it is under the hood.
|
Our display looks the same but we know how much better it is under the hood.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Conditional display with NgIf
|
## Conditional display with NgIf
|
||||||
|
|
||||||
Sometimes the app should display a view or a portion of a view only under prescribed circumstances.
|
Sometimes the app should display a view or a portion of a view only under prescribed circumstances.
|
||||||
|
@ -220,38 +220,38 @@ figure.image-display
|
||||||
We can see it in action by adding the following paragraph at the bottom of the template:
|
We can see it in action by adding the following paragraph at the bottom of the template:
|
||||||
+makeExample('displaying-data/ts/src/app/app.final.ts', 'message')
|
+makeExample('displaying-data/ts/src/app/app.final.ts', 'message')
|
||||||
.alert.is-important
|
.alert.is-important
|
||||||
:markdown
|
:marked
|
||||||
Don't forget the leading asterisk (\*) in front of `*ng-if`. It is an essential part of the syntax.
|
Don't forget the leading asterisk (\*) in front of `*ng-if`. It is an essential part of the syntax.
|
||||||
Learn more about this and `NgIf` in the [Template Syntax](./template-syntax.html#ng-if) chapter.
|
Learn more about this and `NgIf` in the [Template Syntax](./template-syntax.html#ng-if) chapter.
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
The [template expression](./template-syntax.html#template-expressions) inside the double quotes
|
The [template expression](./template-syntax.html#template-expressions) inside the double quotes
|
||||||
looks much like JavaScript and it is much like JavaScript.
|
looks much like JavaScript and it is much like JavaScript.
|
||||||
When the component's list of heroes has more than 3 items, Angular adds the paragraph to the DOM and the message appears.
|
When the component's list of heroes has more than 3 items, Angular adds the paragraph to the DOM and the message appears.
|
||||||
If there were 3 or fewer items, Angular omits the the paragraph and there is no message.
|
If there were 3 or fewer items, Angular omits the the paragraph and there is no message.
|
||||||
|
|
||||||
.alert.is-helpful
|
.alert.is-helpful
|
||||||
:markdown
|
:marked
|
||||||
Angular isn't showing and hiding the message. It is adding and removing the paragraph element from the DOM.
|
Angular isn't showing and hiding the message. It is adding and removing the paragraph element from the DOM.
|
||||||
That hardly matters here. It would matter a great deal from a performance perspective if
|
That hardly matters here. It would matter a great deal from a performance perspective if
|
||||||
we were conditionally including or excluding a big chunk of HTML with many data bindings.
|
we were conditionally including or excluding a big chunk of HTML with many data bindings.
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
As with the `NgFor`, we must add the `NgIf` directive to the component's metadata.
|
As with the `NgFor`, we must add the `NgIf` directive to the component's metadata.
|
||||||
|
|
||||||
We should extend our `import` statement as before ...
|
We should extend our `import` statement as before ...
|
||||||
+makeExample('displaying-data/ts/src/app/app.3.ts', 'import-ng-if')
|
+makeExample('displaying-data/ts/src/app/app.3.ts', 'import-ng-if')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
... and add it to the directives array:
|
... and add it to the directives array:
|
||||||
+makeExample('displaying-data/ts/src/app/app.3.ts', 'directives')
|
+makeExample('displaying-data/ts/src/app/app.3.ts', 'directives')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Try it out. We have four items in the array so the message should appear.
|
Try it out. We have four items in the array so the message should appear.
|
||||||
Delete one of the elements from the array, refresh the browser, and the message should no longer appear.
|
Delete one of the elements from the array, refresh the browser, and the message should no longer appear.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Use the CORE_DIRECTIVES Constant
|
## Use the CORE_DIRECTIVES Constant
|
||||||
|
|
||||||
There are other core Angular directives, such as `NgClass` and `NgSwitch`,
|
There are other core Angular directives, such as `NgClass` and `NgSwitch`,
|
||||||
|
@ -266,15 +266,15 @@ figure.image-display
|
||||||
We'll revise our `import` statement one last time.
|
We'll revise our `import` statement one last time.
|
||||||
+makeExample('displaying-data/ts/src/app/app.final.ts', 'imports')
|
+makeExample('displaying-data/ts/src/app/app.final.ts', 'imports')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
and update the `directives` metadata
|
and update the `directives` metadata
|
||||||
+makeExample('displaying-data/ts/src/app/app.final.ts', 'directives')
|
+makeExample('displaying-data/ts/src/app/app.final.ts', 'directives')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Pro tip: we register this constant in almost every template we write.
|
Pro tip: we register this constant in almost every template we write.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Summary
|
## Summary
|
||||||
Now we know how to
|
Now we know how to
|
||||||
- use **interpolation** with the double curly braces to display a component property,
|
- use **interpolation** with the double curly braces to display a component property,
|
||||||
|
@ -291,7 +291,7 @@ figure.image-display
|
||||||
'app.ts, hero.ts')
|
'app.ts, hero.ts')
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Next Steps
|
## Next Steps
|
||||||
In addition to displaying data, most applications need to respond to user input.
|
In addition to displaying data, most applications need to respond to user input.
|
||||||
Learn about that in the [User Input](./user-input.html) chapter.
|
Learn about that in the [User Input](./user-input.html) chapter.
|
||||||
|
|
|
@ -1,205 +1,205 @@
|
||||||
include ../../../../_includes/_util-fns
|
include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
<!-- http://plnkr.co/edit/wg154K -->
|
<!-- http://plnkr.co/edit/wg154K -->
|
||||||
:markdown
|
:marked
|
||||||
We’ve all used a form to login, submit a help request, place an order, book a flight,
|
We’ve all used a form to login, submit a help request, place an order, book a flight,
|
||||||
schedule a meeting and perform countless other data entry tasks.
|
schedule a meeting and perform countless other data entry tasks.
|
||||||
Forms are the mainstay of business applications.
|
Forms are the mainstay of business applications.
|
||||||
|
|
||||||
Any seasoned web developer can slap together an HTML form with all the right tags.
|
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
|
It's more challenging to create a cohesive data entry experience that guides the
|
||||||
user efficiently and effectively through the workflow behind the form.
|
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.
|
*That* takes design skills that are, to be frank, well out of scope for this chapter.
|
||||||
|
|
||||||
It also takes framework support for
|
It also takes framework support for
|
||||||
**two-way data binding, change tracking, validation, and error handling**
|
**two-way data binding, change tracking, validation, and error handling**
|
||||||
... which we shall cover in this chapter on Angular forms.
|
... 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
|
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
|
- how to build an Angular form with a component and template
|
||||||
|
|
||||||
- the `ng-model` two-way data binding syntax for reading and writing values to input controls
|
- the `ng-model` two-way data binding syntax for reading and writing values to input controls
|
||||||
|
|
||||||
- the `ng-control` directive to track the change state and validity of form controls
|
- the `ng-control` directive to track the change state and validity of form controls
|
||||||
|
|
||||||
- the special CSS classes that `ng-control` adds to form controls and how we can use them to provide strong visual feedback
|
- the special CSS classes that `ng-control` adds to form controls and how we can use them to provide strong visual feedback
|
||||||
|
|
||||||
- how to display validation errors to users and enable/disable form controls
|
- how to display validation errors to users and enable/disable form controls
|
||||||
|
|
||||||
- how to share information across controls with template local variables
|
- how to share information across controls with template local variables
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Template-Driven Forms
|
## Template-Driven Forms
|
||||||
|
|
||||||
Many of us will build forms by writing templates in the Angular [template syntax](./template-syntax.html) with
|
Many of us will build forms by writing templates in the Angular [template syntax](./template-syntax.html) with
|
||||||
the form-specific Directives and techniques described in this chapter.
|
the form-specific Directives and techniques described in this chapter.
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
That's not the only way to create a form but it's the way we'll cover in this chapter.
|
That's not the only way to create a form but it's the way we'll cover in this chapter.
|
||||||
:markdown
|
:marked
|
||||||
We can build almost any form we need with an Angular template ... login forms, contact forms ... pretty much any business forms.
|
We can build almost any form we need with an Angular template ... login forms, contact forms ... pretty much any business forms.
|
||||||
We can lay out the controls creatively, bind them to data, specify validation rules and display validation errors,
|
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.
|
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 repeative, boiler plate tasks we'd
|
It will be pretty easy because Angular handles many of the repeative, boiler plate tasks we'd
|
||||||
otherwise wrestle with ourselves.
|
otherwise wrestle with ourselves.
|
||||||
|
|
||||||
We'll discuss and learn to build the following template-driven form:
|
We'll discuss and learn to build the following template-driven form:
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src="/resources/images/devguide/forms/hf-1.png" alt="Clean Form")
|
img(src="/resources/images/devguide/forms/hf-1.png" alt="Clean Form")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Here at the *Hero Employment Agency* we use this form to maintain personal information about the
|
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!
|
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.
|
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:
|
If we delete the hero name, the form displays a validation error in an attention grabbing style:
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src="/resources/images/devguide/forms/hf-2.png" alt="Invalid, Name Required")
|
img(src="/resources/images/devguide/forms/hf-2.png" alt="Invalid, Name Required")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Note that the submit button is disabled and the "required" bar to the left of the input control changed from green to red.
|
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
|
|
||||||
p We'll' customize the colors and location of the "required" bar with standard CSS.
|
|
||||||
|
|
||||||
:markdown
|
.l-sub-section
|
||||||
|
p We'll' customize the colors and location of the "required" bar with standard CSS.
|
||||||
|
|
||||||
|
:marked
|
||||||
We will build this form in the following sequence of small steps
|
We will build this form in the following sequence of small steps
|
||||||
|
|
||||||
1. Create the `Hero` model class
|
1. Create the `Hero` model class
|
||||||
1. Create the component that controls the form
|
1. Create the component that controls the form
|
||||||
1. Create a template with the initial form layout
|
1. Create a template with the initial form layout
|
||||||
1. Add the **ng-model** directive to each form input control
|
1. Add the **ng-model** directive to each form input control
|
||||||
1. Add the **ng-control** directive to each form input control
|
1. Add the **ng-control** directive to each form input control
|
||||||
1. Add custom CSS to provide visual feedback
|
1. Add custom CSS to provide visual feedback
|
||||||
1. Show and hide validation error messages
|
1. Show and hide validation error messages
|
||||||
1. Handle form submission with **ng-submit**
|
1. Handle form submission with **ng-submit**
|
||||||
1. Disable the form’s submit button until the form is valid
|
1. Disable the form’s submit button until the form is valid
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
## Setup
|
## Setup
|
||||||
Create a new project folder (`angular2-forms`) and follow the steps in the [QuickStart](../quickstart.html).
|
Create a new project folder (`angular2-forms`) and follow the steps in the [QuickStart](../quickstart.html).
|
||||||
|
|
||||||
## Create the Hero Model Class
|
## Create the Hero Model Class
|
||||||
|
|
||||||
As users enter form data, we capture their changes and update an instance of a model.
|
As users enter form data, we capture their changes and update an instance of a model.
|
||||||
We can't layout the form until we know what the model looks like.
|
We can't layout 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.
|
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`)
|
That describes well our `Hero` class with its three required fields (`id`, `name`, `power`)
|
||||||
and one optional field (`alterEgo`).
|
and one optional field (`alterEgo`).
|
||||||
|
|
||||||
Create a new file called `hero.ts` and give it the following class definition:
|
Create a new file called `hero.ts` and give it the following class definition:
|
||||||
|
|
||||||
+makeExample('forms/ts/src/app/hero.ts')
|
+makeExample('forms/ts/src/app/hero.ts')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
It's an anemic model with few requirements and no behavior. Perfect for our demo.
|
It's an anemic model with few requirements and no behavior. Perfect for our demo.
|
||||||
|
|
||||||
The TypeScript compiler generates a public field for each `public` constructor parameter and
|
The TypeScript compiler generates a public field for each `public` constructor parameter and
|
||||||
assigns the parameter’s value to that field automatically when we create new heroes like this:
|
assigns the parameter’s value to that field automatically when we create new heroes like this:
|
||||||
```
|
```
|
||||||
let myHero = new Hero(42, 'SkyDog', 'Fetch any object at any distance', 'Leslie Rollover');
|
let myHero = new Hero(42, 'SkyDog', 'Fetch any object at any distance', 'Leslie Rollover');
|
||||||
console.log('My hero is called ' + myHero.name); // "My hero is called SkyDog"
|
console.log('My hero is called ' + myHero.name); // "My hero is called SkyDog"
|
||||||
```
|
```
|
||||||
The `alterEgo` is optional and the constructor lets us omit it; note the (?) in `alterEgo?`.
|
The `alterEgo` is optional and the constructor lets us omit it; note the (?) in `alterEgo?`.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Create a Form component
|
## 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.
|
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.
|
We begin with the Component because it states, in brief, what the Hero editor can do.
|
||||||
|
|
||||||
Create a new file called `hero-form.component.ts` and give it the following definition:
|
Create a new file called `hero-form.component.ts` and give it the following definition:
|
||||||
|
|
||||||
+makeExample('forms/ts/src/app/hero-form.component.ts', 'first')
|
+makeExample('forms/ts/src/app/hero-form.component.ts', 'first')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
There’s nothing special about this component, nothing to distinguish it from any component we've written before,
|
There’s nothing special about this component, nothing to distinguish it from any component we've written before,
|
||||||
nothing form-specific about it ... except, perhaps, the tell-tale `FORM_DIRECTIVES` import.
|
nothing form-specific about it ... except, perhaps, the tell-tale `FORM_DIRECTIVES` import.
|
||||||
|
|
||||||
Understanding this component requires only the Angular 2 concepts we’ve learned in previous chapters
|
Understanding this component requires only the Angular 2 concepts we’ve learned in previous chapters
|
||||||
|
|
||||||
1. We import a standard set of symbols from the Angular library.
|
1. We import a standard set of symbols from the Angular library.
|
||||||
We don't have a template yet but we usually import `CORE_DIRECTIVES` and it doesn't surprise us to
|
We don't have a template yet but we usually import `CORE_DIRECTIVES` and it doesn't surprise us to
|
||||||
import something called `FORM_DIRECTIVES`, given that we'll be writing a form
|
import something called `FORM_DIRECTIVES`, given that we'll be writing a form
|
||||||
|
|
||||||
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 `@Component` selector value of "hero-form" means we can drop this form in a parent template with a `<hero-form>` tag.
|
||||||
|
|
||||||
1. The `templateUrl` property points to a separate file for template HTML called `hero-form.component.html`.
|
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.
|
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
|
Down the road, we can inject a data service to get and save real data
|
||||||
or perhaps expose these properties as [inputs and outputs](./template-syntax.html#inputs-outputs) for binding to a
|
or perhaps expose these properties as [inputs and outputs](./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.
|
parent component. None of this concerns us now and these future changes won't affect our form.
|
||||||
|
|
||||||
1. We threw in a `diagnostic` property at the end to return a JSON representation of our model.
|
1. We threw in a `diagnostic` property at the end to return a JSON representation of 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.
|
It'll help us see what we're doing during our development; we've left ourselves a cleanup note to discard it later.
|
||||||
|
|
||||||
We may wonder why we aren't writing the template inline in the component file as we have often done
|
We may wonder why we aren't writing the template inline in the component file as we have often done
|
||||||
elsewhere in the Developer Guide.
|
elsewhere in the Developer Guide.
|
||||||
|
|
||||||
There is no “right” answer for all occasions. We kind of like inline templates when they are short.
|
There is no “right” answer for all occasions. We kind of like inline templates when they are short.
|
||||||
Most form templates won't be short. TypeScript and JavaScript files generally aren't the best place to
|
Most form templates won't be short. TypeScript and JavaScript 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.
|
write (or read) large stretches of HTML and few editors are much help with files that have a mix of HTML and code.
|
||||||
We also like short files with a clear and obvious purpose like this one.
|
We also like short files with a clear and obvious purpose like this one.
|
||||||
|
|
||||||
We made a good choice to put the HTML template elsewhere. Let's write it.
|
We made a good choice to put the HTML template elsewhere. Let's write it.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Revise the *app.ts*
|
## Revise the *app.ts*
|
||||||
|
|
||||||
`app.ts` is the application's root component. It will host our new `HeroFormComponent`.
|
`app.ts` is the application's root component. It will host our new `HeroFormComponent`.
|
||||||
|
|
||||||
Replace the contents of the "QuickStart" version with the following:
|
Replace the contents of the "QuickStart" version with the following:
|
||||||
+makeExample('forms/ts/src/app/app.ts')
|
+makeExample('forms/ts/src/app/app.ts')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
There are only three changes:
|
There are only three changes:
|
||||||
|
|
||||||
1. We import the new `HeroFormComponent`.
|
1. We import the new `HeroFormComponent`.
|
||||||
|
|
||||||
1. The `template` is simply the new element tag identified by the component's `select` property.
|
1. The `template` is simply the new element tag identified by the component's `select` property.
|
||||||
|
|
||||||
1. The `directives` array tells Angular that our templated depends upon the `HeroFormComponent`
|
1. The `directives` array tells Angular that our templated depends upon the `HeroFormComponent`
|
||||||
which is itself a Directive (as are all Components).
|
which is itself a Directive (as are all Components).
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Create an initial HTML Form Template
|
## Create an initial HTML Form Template
|
||||||
|
|
||||||
Create a new template file called `hero-form.component.html` and give it the following definition:
|
Create a new template file called `hero-form.component.html` and give it the following definition:
|
||||||
|
|
||||||
+makeExample('forms/ts/src/app/hero-form.component.html', 'start')
|
+makeExample('forms/ts/src/app/hero-form.component.html', 'start')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
That is plain old HTML 5. We're presenting two of the `Hero` fields, `name` and `alterEgo`, and
|
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.
|
opening them up for user input in input boxes.
|
||||||
|
|
||||||
The "Name" `<input>` control has the HTML5 `required` attribute;
|
The "Name" `<input>` control has the HTML5 `required` attribute;
|
||||||
the "Alter Ego" `<input>` control does not because `alterEgo` is optional.
|
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'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.
|
**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 CSS Bootstrap. Purely cosmetic.
|
The `container`,`form-group`, `form-control`, and `btn` classes are CSS Bootstrap. Purely cosmetic.
|
||||||
We're using Bootstrap to gussy up our form.
|
We're using Bootstrap to gussy up our form.
|
||||||
Hey, what's a form without a little style!
|
Hey, what's a form without a little style!
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
Since we're using [CSS Boostrap](http://getbootstrap.com/css/).
|
Since we're using [CSS Boostrap](http://getbootstrap.com/css/).
|
||||||
now might be a good time to install it into our project.
|
now might be a good time to install it into our project.
|
||||||
We can do that with npm.
|
We can do that with npm.
|
||||||
|
@ -207,275 +207,275 @@ figure.image-display
|
||||||
Open a terminal window and enter the command:
|
Open a terminal window and enter the command:
|
||||||
code-example(language="html" escape="html").
|
code-example(language="html" escape="html").
|
||||||
npm install bootstrap
|
npm install bootstrap
|
||||||
:markdown
|
:marked
|
||||||
<br>Open the `index.html` and add the following line wherever we like to put our CSS
|
<br>Open the `index.html` and add the following line wherever we like to put our CSS
|
||||||
+makeExample('forms/ts/src/index.html', 'bootstrap')(format=".")
|
+makeExample('forms/ts/src/index.html', 'bootstrap')(format=".")
|
||||||
|
|
||||||
.callout.is-important
|
.callout.is-important
|
||||||
header Angular Forms Does Not Require A Style Library
|
header Angular Forms Does Not Require A Style Library
|
||||||
:markdown
|
:marked
|
||||||
Angular makes no use of the `container`, `form-group`, `form-control`, and `btn` classes or
|
Angular makes no use of the `container`, `form-group`, `form-control`, and `btn` classes or
|
||||||
the styles of any external library. We are welcome to use the CSS library we choose
|
the styles of any external library. We are welcome to use the CSS library we choose
|
||||||
... or none at all.
|
... or none at all.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Add Powers with ***ng-for**
|
## Add Powers with ***ng-for**
|
||||||
Our hero may choose one super power from a fixed list of Agency-approved powers.
|
Our hero may choose one super power from a fixed list of Agency-approved powers.
|
||||||
We maintain that list internally (in `HeroFormComponent`).
|
We maintain that list internally (in `HeroFormComponent`).
|
||||||
|
|
||||||
We'll add a `select` to our
|
We'll add a `select` to our
|
||||||
form and bind the options to the `powers` list using `NgFor`,
|
form and bind the options to the `powers` list using `NgFor`,
|
||||||
a technique we might have seen before in the ["Displaying Data"](./displaying-data.html) chapter.
|
a technique we might have seen before in the ["Displaying Data"](./displaying-data.html) chapter.
|
||||||
|
|
||||||
Add the following HTML *immediately below* the "Alter Ego" group.
|
Add the following HTML *immediately below* the "Alter Ego" group.
|
||||||
+makeExample('forms/ts/src/app/hero-form.component.html', 'powers')
|
+makeExample('forms/ts/src/app/hero-form.component.html', 'powers')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
We are repeating the `<options>` tag for each power in the list of Powers.
|
We are repeating the `<options>` tag for each power in the list of Powers.
|
||||||
The `#p` local template variable is a different power in each iteration;
|
The `#p` local template variable is a different power in each iteration;
|
||||||
we display its name using the interpolation syntax with the double-curly-braces.
|
we display its name using the interpolation syntax with the double-curly-braces.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Two-way data binding with ***ng-model**
|
## Two-way data binding with ***ng-model**
|
||||||
We might be disappointed if we ran the app right now.
|
We might be disappointed if we ran the app right now.
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src="/resources/images/devguide/forms/hf-3.png" alt="Early form with no binding")
|
img(src="/resources/images/devguide/forms/hf-3.png" alt="Early form with no binding")
|
||||||
:markdown
|
:marked
|
||||||
We quickly realize that we are not binding to the `Hero` yet.
|
We quickly realize that we are not binding to the `Hero` yet.
|
||||||
We know how to do that from earlier chapters.
|
We know how to do that from earlier chapters.
|
||||||
We learned show data on screen with a Property Binding in "[Displaying Data](./displaying-data.html)".
|
We learned show data on screen with a Property Binding in "[Displaying Data](./displaying-data.html)".
|
||||||
We learned to listen for DOM events with an
|
We learned to listen for DOM events with an
|
||||||
Event Binding and how to extract values from the screen
|
Event Binding and how to extract values from the screen
|
||||||
in "[User Input](./user-input.html)".
|
in "[User Input](./user-input.html)".
|
||||||
|
|
||||||
Now we need to display, listen, and extract at the same time.
|
Now we need to display, listen, and extract at the same time.
|
||||||
|
|
||||||
We could use those techniques again in our form.
|
We could use those techniques again in our form.
|
||||||
Instead we'll introduce something new, the `NgModel` directive, that
|
Instead we'll introduce something new, the `NgModel` directive, that
|
||||||
makes binding our form to the model super-easy.
|
makes binding our form to the model super-easy.
|
||||||
|
|
||||||
Find the `<input>` tag for the "Name" and update it like this
|
Find the `<input>` tag for the "Name" and update it like this
|
||||||
|
|
||||||
+makeExample('forms/ts/src/app/hero-form.component.html', 'ng-model-1')
|
+makeExample('forms/ts/src/app/hero-form.component.html', 'ng-model-1')
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
We appended a diagnostic interpolation after the input tag
|
We appended a diagnostic interpolation after the input tag
|
||||||
so we can see what we're doing.
|
so we can see what we're doing.
|
||||||
We left ourselves a note to throw it way when we're done.
|
We left ourselves a note to throw it way when we're done.
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Focus on the binding syntax: `[(ng-model)]="..."`.
|
Focus on the binding syntax: `[(ng-model)]="..."`.
|
||||||
|
|
||||||
If we ran the app right now and started typing in the "Name" input box,
|
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
|
adding and deleting characters, we'd see them appearing and disappearing
|
||||||
from the interpolated text.
|
from the interpolated text.
|
||||||
At some point it might look like this.
|
At some point it might look like this.
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src="/resources/images/devguide/forms/ng-model-in-action.png" alt="ng-model in action")
|
img(src="/resources/images/devguide/forms/ng-model-in-action.png" alt="ng-model in action")
|
||||||
:markdown
|
:marked
|
||||||
The diagnostic is evidence that we really are flowing values from the input box to the model and
|
The diagnostic is evidence that we really are flowing values from the input box to the model and
|
||||||
back again. **That's two-way data binding!**
|
back again. **That's two-way data binding!**
|
||||||
|
|
||||||
Let's add similar `[(ng-model)]` bindings to "Alter Ego" and "Hero Power".
|
Let's add similar `[(ng-model)]` bindings to "Alter Ego" and "Hero Power".
|
||||||
We'll ditch the input box binding message
|
We'll ditch the input box binding message
|
||||||
and add a new binding at the top to the component's `diagnostic` property.
|
and add a new binding at the top to the component's `diagnostic` property.
|
||||||
Then we can confirm that we are in fact two-way data binding *to the entire Hero model*.
|
Then we can confirm that we are in fact two-way data binding *to the entire Hero model*.
|
||||||
|
|
||||||
After revision the core of our form should have three `[(ng-model)]` bindings that
|
After revision the core of our form should have three `[(ng-model)]` bindings that
|
||||||
look much like this:
|
look much like this:
|
||||||
|
|
||||||
+makeExample('forms/ts/src/app/hero-form.component.html', 'ng-model-2')
|
+makeExample('forms/ts/src/app/hero-form.component.html', 'ng-model-2')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
If we ran the app right now and made a bunch of changes at some point it might look like this.
|
If we ran the app right now and made a bunch of changes at some point it might look like this.
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src="/resources/images/devguide/forms/ng-model-in-action-2.png" alt="ng-model in super action")
|
img(src="/resources/images/devguide/forms/ng-model-in-action-2.png" alt="ng-model in super action")
|
||||||
:markdown
|
:marked
|
||||||
We've changed every Hero model property and the diagnostic near the top of the form
|
We've changed every Hero model property and the diagnostic near the top of the form
|
||||||
confirms that our changes are reflected in the model.
|
confirms that our changes are reflected in the model.
|
||||||
|
|
||||||
** We're done with the diagnostic binding. Delete it now.**
|
** We're done with the diagnostic binding. Delete it now.**
|
||||||
|
|
||||||
.alert.is-helpful
|
.alert.is-helpful
|
||||||
:markdown
|
:marked
|
||||||
Although `NgModel` is officially a "Forms" directive we can use `[(ng-model)]` and two-way binding outside of forms too.
|
Although `NgModel` is officially a "Forms" directive we can use `[(ng-model)]` and two-way binding outside of forms too.
|
||||||
:markdown
|
:marked
|
||||||
## Inside [(ng-model)]
|
## Inside [(ng-model)]
|
||||||
Do we *really want* to know? If we're just happy that it works, move on to the next topic in this chapter.
|
Do we *really want* to know? If we're just happy that it works, move on to the next topic in this chapter.
|
||||||
|
|
||||||
Otherwise, stick around for this note.
|
Otherwise, stick around for this note.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
The punctuation in the binding syntax, <span style="font-family:courier"><b>[()]</b></span>, is a good clue to what's going on.
|
The punctuation in the binding syntax, <span style="font-family:courier"><b>[()]</b></span>, is a good clue to what's going on.
|
||||||
|
|
||||||
We write a Property Binding to flow data from the model to a target property on screen.
|
We write a Property Binding to flow data 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>.
|
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**.
|
This is a one-way data binding **from the model to the view**.
|
||||||
|
|
||||||
We write an Event Binding to flow data from the target property on screen to the model.
|
We write an Event Binding to flow data from the target property on screen to the model.
|
||||||
We identify that target property by surrounding its name in parentheses, <span style="font-family:courier"><b>()</b></span>.
|
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**.
|
This is a one-way data binding in the opposite direction **from the view to the model**.
|
||||||
|
|
||||||
No wonder Angular chose to combine the punctuation as <span style="font-family:courier"><b>[()]</b></span>
|
No wonder Angular chose to combine the punctuation as <span style="font-family:courier"><b>[()]</b></span>
|
||||||
to signify a two-way data binding and a **flow of data in both directions**.
|
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
|
In fact, we can break the `NgModel` binding into its two separate modes
|
||||||
as we do in this re-write of the "Name" `<input>` binding:
|
as we do in this re-write of the "Name" `<input>` binding:
|
||||||
+makeExample('forms/ts/src/app/hero-form.component.html', 'ng-model-3')
|
+makeExample('forms/ts/src/app/hero-form.component.html', 'ng-model-3')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
<br>The Property Binding should feel familiar. The Event Binding might seem strange.
|
<br>The Property Binding should feel familiar. The Event Binding might seem strange.
|
||||||
|
|
||||||
The name `ng-model-change` is not an event we recognize.
|
The name `ng-model-change` is not an event we recognize.
|
||||||
It is a real event property ... of the `NgModel` directive.
|
It is a real event property ... of the `NgModel` directive.
|
||||||
When Angular sees a binding target in the form <span style="font-family:courier">[(abc)]</span>,
|
When Angular sees a binding target in the form <span style="font-family:courier">[(abc)]</span>,
|
||||||
it expects the `abc` directive to have an `abc` input property and an `abc-change` output property.
|
it expects the `abc` directive to have an `abc` input property and an `abc-change` output property.
|
||||||
|
|
||||||
The other oddity is the template expression, `model.name = $event`.
|
The other oddity is the template expression, `model.name = $event`.
|
||||||
We're used to seeing an `$event` object coming from a DOM event.
|
We're used to seeing an `$event` object coming from a DOM event.
|
||||||
The `ng-model-change` property doesn't produce a DOM event; it's an Angular `EventEmitter`
|
The `ng-model-change` 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
|
property that returns the input box value when it fires ... which is precisely what
|
||||||
we should assign to the model's `name' property.
|
we should assign to the model's `name' property.
|
||||||
|
|
||||||
Nice to know but is it practical? We'd always prefer the `[(ng-model)]`.
|
Nice to know but is it practical? We'd always prefer the `[(ng-model)]`.
|
||||||
We might split the binding if we had to do something special in
|
We might split the binding if we had to do something special in
|
||||||
the event handling such as debounce or throttle the key strokes.
|
the event handling such as debounce or throttle the key strokes.
|
||||||
|
|
||||||
Learn more about `NgModel` and other template syntax in the
|
Learn more about `NgModel` and other template syntax in the
|
||||||
[Template Syntax](./template-syntax.html) chapter.
|
[Template Syntax](./template-syntax.html) chapter.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Track change-state and validity with **ng-control**
|
## Track change-state and validity with **ng-control**
|
||||||
|
|
||||||
A form isn't just about data binding. We'd also like to know the state of the controls on our form.
|
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.
|
The `NgControl` directive keeps track of control state for us.
|
||||||
|
|
||||||
.callout.is-helpful
|
.callout.is-helpful
|
||||||
header NgControl requires Form
|
header NgControl requires Form
|
||||||
:markdown
|
:marked
|
||||||
The `NgControl` is one of a family of `NgForm` directives that can only be applied to
|
The `NgControl` is one of a family of `NgForm` directives that can only be applied to
|
||||||
a control within a `<form`> tag.
|
a control within a `<form`> tag.
|
||||||
:markdown
|
:marked
|
||||||
Our application can ask an `NgControl` instance if
|
Our application can ask an `NgControl` instance if
|
||||||
* the user touched the control (`ng-touched` | `ng-untouched`)
|
* the user touched the control (`ng-touched` | `ng-untouched`)
|
||||||
* the value changed (`ng-pristine` | `ng-dirty`)
|
* the value changed (`ng-pristine` | `ng-dirty`)
|
||||||
* is the value is valid (`ng-valid` | `ng-invalid`)
|
* is the value is valid (`ng-valid` | `ng-invalid`)
|
||||||
|
|
||||||
`NgControl` doesn't just track state; it updates the control with special
|
`NgControl` doesn't just track state; it updates the control with special
|
||||||
Angular CSS classes from the set we listed above.
|
Angular CSS classes from the set we listed above.
|
||||||
We can leverage those class names to change the appearance of the
|
We can leverage those class names to change the appearance of the
|
||||||
control and make messages appear or disappear.
|
control and make messages appear or disappear.
|
||||||
|
|
||||||
We'll explore those effects soon. Right now
|
We'll explore those effects soon. Right now
|
||||||
we should **add `ng-control`to all three of our form controls**,
|
we should **add `ng-control`to all three of our form controls**,
|
||||||
starting with the "Name" input box
|
starting with the "Name" input box
|
||||||
+makeExample('forms/ts/src/app/hero-form.component.html', 'ng-control-1')
|
+makeExample('forms/ts/src/app/hero-form.component.html', 'ng-control-1')
|
||||||
:markdown
|
:marked
|
||||||
Be sure to assign a unique name to each `ng-control` directive.
|
Be sure to assign a unique name to each `ng-control` directive.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
Angular registers controls under their `ng-control` names
|
Angular registers controls under their `ng-control` names
|
||||||
with the `NgForm`.
|
with the `NgForm`.
|
||||||
We didn't add the `NgForm` directive explicitly but it's here
|
We didn't add the `NgForm` directive explicitly but it's here
|
||||||
and we'll talk it [later in this chapter](#ng-form).
|
and we'll talk it [later in this chapter](#ng-form).
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Add Custom CSS for Visual Feedback
|
## Add Custom CSS for Visual Feedback
|
||||||
|
|
||||||
`NgControl` doesn't just track state. It updates the control with three classes, one
|
`NgControl` doesn't just track state. It updates the control with three classes, one
|
||||||
each from the following pairs of Angular form CSS classes.
|
each from the following pairs of Angular form CSS classes.
|
||||||
* control visited: (`ng-touched` | `ng-untouched`)
|
* control visited: (`ng-touched` | `ng-untouched`)
|
||||||
* value changed: (`ng-pristine` | `ng-dirty`)
|
* value changed: (`ng-pristine` | `ng-dirty`)
|
||||||
* validity: (`ng-valid` | `ng-invalid`)
|
* validity: (`ng-valid` | `ng-invalid`)
|
||||||
|
|
||||||
Let's add a temporary [local template variable](./template-syntax.html#local-vars) named **spy**
|
Let's add a temporary [local template variable](./template-syntax.html#local-vars) named **spy**
|
||||||
to the "Name" `<input>` tag and use the spy to display those classes with an interpolation binding.
|
to the "Name" `<input>` tag and use the spy to display those classes with an interpolation binding.
|
||||||
|
|
||||||
+makeExample('forms/ts/src/app/hero-form.component.html', 'ng-control-2')
|
+makeExample('forms/ts/src/app/hero-form.component.html', 'ng-control-2')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
If we ran the app, focused our attention on the "Name" input box, and followed the next four steps *precisely*
|
If we ran the app, focused our attention on the "Name" input box, and followed the next four steps *precisely*
|
||||||
|
|
||||||
1. Look but don't touched
|
1. Look but don't touched
|
||||||
1. Click in the input box, then click outside the text input box
|
1. Click in the input box, then click outside the text input box
|
||||||
1. Add slashes to the end of the name
|
1. Add slashes to the end of the name
|
||||||
1. Erase the name
|
1. Erase the name
|
||||||
|
|
||||||
... we would see the following four sets of class names and their transitions:
|
... we would see the following four sets of class names and their transitions:
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src="/resources/images/devguide/forms/ng-control-class-changes.png" alt="Invalid Form")
|
img(src="/resources/images/devguide/forms/ng-control-class-changes.png" alt="Invalid Form")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
The (`ng-valid` | `ng-invalid`) pair are most interesting to us. We want to send a
|
The (`ng-valid` | `ng-invalid`) pair are most interesting to us. We want to send a
|
||||||
strong visual signal when the data are invalid and we want to mark required fields.
|
strong visual signal when the data are invalid and we want to mark required fields.
|
||||||
|
|
||||||
We realize we can do both at the same time with a colored bar on the left of the input box:
|
We realize we can do both at the same time with a colored bar on the left of the input box:
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src="/resources/images/devguide/forms/validity-required-indicator.png" alt="Invalid Form")
|
img(src="/resources/images/devguide/forms/validity-required-indicator.png" alt="Invalid Form")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
We achieve this effect by adding two styles to a new `styles.css` file
|
We achieve this effect by adding two styles to a new `styles.css` file
|
||||||
that we add to our project as a sibling to `index.html`.
|
that we add to our project as a sibling to `index.html`.
|
||||||
|
|
||||||
+makeExample('forms/ts/src/styles.css')
|
+makeExample('forms/ts/src/styles.css')
|
||||||
:markdown
|
:marked
|
||||||
These styles select for the two Angular validity classes and the HTML 5 "required" attribute.
|
These styles select for the two Angular validity classes and the HTML 5 "required" attribute.
|
||||||
|
|
||||||
We update the `<head>` of the `index.html` to include this style sheet.
|
We update the `<head>` of the `index.html` to include this style sheet.
|
||||||
+makeExample('forms/ts/src/index.html', 'styles')(format=".")
|
+makeExample('forms/ts/src/index.html', 'styles')(format=".")
|
||||||
:markdown
|
:marked
|
||||||
## Show and Hide Validation Error messages
|
## Show and Hide Validation Error messages
|
||||||
|
|
||||||
We can do better.
|
We can do better.
|
||||||
|
|
||||||
The "Name" input box is required. Clearing it turns the bar red. That says *something* is wrong but we
|
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.
|
don't know *what* is wrong or what to do about it.
|
||||||
We can leverage the `ng-invalid` class to reveal a helpful message.
|
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:
|
Here's the way it should look when the user deletes the name:
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src="/resources/images/devguide/forms/name-required-error.png" alt="Name required")
|
img(src="/resources/images/devguide/forms/name-required-error.png" alt="Name required")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
To achieve this effect we extend the `<input>` tag with
|
To achieve this effect we extend the `<input>` tag with
|
||||||
1. a [local template variable](./template-syntax.html#local-vars)
|
1. a [local template variable](./template-syntax.html#local-vars)
|
||||||
1. the "*is required*" message in a nearby `<div>` which we'll display only if the control is invalid.
|
1. the "*is required*" message in a nearby `<div>` which we'll display only if the control is invalid.
|
||||||
|
|
||||||
Here's how we do it for the "name" input box:
|
Here's how we do it for the "name" input box:
|
||||||
+makeExample('forms/ts/src/app/hero-form.component.html', 'name-with-error-msg')
|
+makeExample('forms/ts/src/app/hero-form.component.html', 'name-with-error-msg')
|
||||||
:markdown
|
:marked
|
||||||
We initialized the template local variable with the word "form" (`#name="form"`)
|
We initialized the template local variable with the word "form" (`#name="form"`)
|
||||||
|
|
||||||
Angular recognizes that syntax and sets the `name` varable
|
Angular recognizes that syntax and sets the `name` varable
|
||||||
to the `Control` object identified by the `ng-control` directive which,
|
to the `Control` object identified by the `ng-control` directive which,
|
||||||
not coincidentally, we called "name".
|
not coincidentally, we called "name".
|
||||||
|
|
||||||
We bind the `Control` object's `valid` property to the element's `hidden` property.
|
We bind the `Control` object's `valid` property to the element's `hidden` property.
|
||||||
While the control is valid, the message is hidden;
|
While the control is valid, the message is hidden;
|
||||||
if it becomes invalid, the message is revealed.
|
if it becomes invalid, the message is revealed.
|
||||||
<a id="ng-form"></a>
|
<a id="ng-form"></a>
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
Recall from the previous section that `ng-control` registered this input box with the
|
Recall from the previous section that `ng-control` registered this input box with the
|
||||||
`NgForm` directive as "name".
|
`NgForm` directive as "name".
|
||||||
|
|
||||||
We didn't add the **[`NgForm`](../api/core/NgForm-class.html) directive** explicitly.
|
We didn't add the **[`NgForm`](../api/core/NgForm-class.html) directive** explicitly.
|
||||||
Angular added it surreptiously, wrapping it around the `<form>` element when we
|
Angular added it surreptiously, wrapping it around the `<form>` element when we
|
||||||
told the `HeroFormComponent` to use the `FORM_DIRECTIVES` like this
|
told the `HeroFormComponent` to use the `FORM_DIRECTIVES` like this
|
||||||
|
|
||||||
+makeExample('forms/ts/src/app/hero-form.component.ts', 'directives')
|
+makeExample('forms/ts/src/app/hero-form.component.ts', 'directives')
|
||||||
<br>
|
<br>
|
||||||
:markdown
|
:marked
|
||||||
The `NgForm` directive supplements the the `form` element with additional features.
|
The `NgForm` directive supplements the the `form` element with additional features.
|
||||||
It collects `Controls` (elements identified by an `ng-control` directive)
|
It collects `Controls` (elements identified by an `ng-control` directive)
|
||||||
and monitors their properties including their validity.
|
and monitors their properties including their validity.
|
||||||
It has its own `valid` property which is true only if every contained
|
It has its own `valid` property which is true only if every contained
|
||||||
|
@ -483,121 +483,121 @@ figure.image-display
|
||||||
|
|
||||||
In this example, we are pulling the "name" control out of its `controls` collection
|
In this example, we are pulling the "name" control out of its `controls` collection
|
||||||
and assigning it to the template local variable so that we can
|
and assigning it to the template local variable so that we can
|
||||||
access the control's properties ... such as the control's own `valid` property.
|
access the control's properties ... such as the control's own `valid` property.
|
||||||
:markdown
|
:marked
|
||||||
The "AlterEgo" is optional so we can leave that be.
|
The "AlterEgo" is optional so we can leave that be.
|
||||||
|
|
||||||
"Power" selection is required.
|
"Power" selection is required.
|
||||||
We can add the same kind of error handling to the `<select>` if we want
|
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
|
but it's not imperative because the selection box already constrains the
|
||||||
power to valid value.
|
power to valid value.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Submit the form with **ng-submit**
|
## Submit the form with **ng-submit**
|
||||||
The user should be able to submit this form after filling it in.
|
The user should be able to submit this form after filling it in.
|
||||||
The "Submit" button at the bottom of the form
|
The "Submit" button at the bottom of the form
|
||||||
does nothing on its own but it will
|
does nothing on its own but it will
|
||||||
trigger a form submit because of its type (`type="submit"`).
|
trigger a form submit because of its type (`type="submit"`).
|
||||||
|
|
||||||
A "form submit" is meaningless at the moment.
|
A "form submit" is meaningless at the moment.
|
||||||
We'll update the `<form>` tag with another Angular directive, `NgSubmit`,
|
We'll update the `<form>` tag with another Angular directive, `NgSubmit`,
|
||||||
and bind it to our `HeroFormComponent.submit()` method with an EventBinding
|
and bind it to our `HeroFormComponent.submit()` method with an EventBinding
|
||||||
+makeExample('forms/ts/src/app/hero-form.component.html', 'ng-submit')
|
+makeExample('forms/ts/src/app/hero-form.component.html', 'ng-submit')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
We slipped in something extra there at the end! We defined a
|
We slipped in something extra there at the end! We defined a
|
||||||
template local variable, **`#hf`**, and initialized it with the value, "form".
|
template local variable, **`#hf`**, and initialized it with the value, "form".
|
||||||
|
|
||||||
The variable `hf` is now a handle to the `NgForm` as we [discussed earlier](#ng-form)
|
The variable `hf` is now a handle to the `NgForm` as we [discussed earlier](#ng-form)
|
||||||
with respect to `ng-control` although this time we have a reference to the form
|
with respect to `ng-control` although this time we have a reference to the form
|
||||||
rather than a control.
|
rather than a control.
|
||||||
|
|
||||||
We'll bind the Form's over-all validity via
|
We'll bind the Form's over-all validity via
|
||||||
the `hf` variable to the button's `disabled` property
|
the `hf` variable to the button's `disabled` property
|
||||||
using an Event Binding. Here's the code:
|
using an Event Binding. Here's the code:
|
||||||
+makeExample('forms/ts/src/app/hero-form.component.html', 'submit-button')
|
+makeExample('forms/ts/src/app/hero-form.component.html', 'submit-button')
|
||||||
:markdown
|
:marked
|
||||||
If we run the application now, we find that the button is enabled.
|
If we run the application now, we find that the button is enabled.
|
||||||
It doesn't do anything useful yet but it's alive.
|
It doesn't do anything useful yet but it's alive.
|
||||||
|
|
||||||
Now if we delete the "Name", we violate the "required" rule which
|
Now if we delete the "Name", we violate the "required" rule which
|
||||||
is duely noted in our error message.
|
is duely noted in our error message.
|
||||||
|
|
||||||
Check the "Submit" button. It should be disabled.
|
Check the "Submit" button. It should be disabled.
|
||||||
|
|
||||||
Not impressed? Think about it for a moment. What would we have to do to
|
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?
|
wire the button's enable/disabled state to the form's validity without Angular's help?
|
||||||
|
|
||||||
For us, it was as simple as
|
For us, it was as simple as
|
||||||
1. Define a template local variable on the (enhanced) form element
|
1. Define a template local variable on the (enhanced) form element
|
||||||
2. Reference that variable in a button some 50 lines away.
|
2. Reference that variable in a button some 50 lines away.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Toggle two form regions (Extra Credit)
|
## Toggle two form regions (Extra Credit)
|
||||||
Submitting the form isn't terribly dramatic at the moment.
|
Submitting the form isn't terribly dramatic at the moment.
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
An unsurprising observation for a demo. To be honest,
|
An unsurprising observation for a demo. To be honest,
|
||||||
jazzing it up won't teach us anything new about forms.
|
jazzing it up won't teach us anything new about forms.
|
||||||
But this is an opportunity to exercise some of our newly won
|
But this is an opportunity to exercise some of our newly won
|
||||||
binding skills.
|
binding skills.
|
||||||
If we're not interested, we can skip to the chapter's conclusion
|
If we're not interested, we can skip to the chapter's conclusion
|
||||||
and not miss a thing.
|
and not miss a thing.
|
||||||
:markdown
|
:marked
|
||||||
Let's do something more strikingly visual.
|
Let's do something more strikingly visual.
|
||||||
Let's hide the data entry area and display something else.
|
Let's hide the data entry area and display something else.
|
||||||
|
|
||||||
Start by wrapping the form in a `<div>` and binding
|
Start by wrapping the form in a `<div>` and binding
|
||||||
its `hidden` property to the `HeroFormComponent.submitted` property.
|
its `hidden` property to the `HeroFormComponent.submitted` property.
|
||||||
|
|
||||||
+makeExample('forms/ts/src/app/hero-form.component.html', 'edit-div')
|
+makeExample('forms/ts/src/app/hero-form.component.html', 'edit-div')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
The main form is visible from the start because the
|
The main form is visible from the start because the
|
||||||
the `submitted` property is false until we submit the form
|
the `submitted` property is false until we submit the form
|
||||||
... as this fragment from the `HeroFormComponent` reminds us:
|
... as this fragment from the `HeroFormComponent` reminds us:
|
||||||
|
|
||||||
+makeExample('forms/ts/src/app/hero-form.component.ts', 'submitted')
|
+makeExample('forms/ts/src/app/hero-form.component.ts', 'submitted')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
When we click the "Submit" button, the `submitted` flag becomes true and the form disappears
|
When we click the "Submit" button, the `submitted` flag becomes true and the form disappears
|
||||||
as planned.
|
as planned.
|
||||||
|
|
||||||
Now we need to show something else while the form is in the submitted state.
|
Now we need 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:
|
Add the following block of HTML below the `<div>` wrapper we just wrote:
|
||||||
+makeExample('forms/ts/src/app/hero-form.component.html', 'submitted')
|
+makeExample('forms/ts/src/app/hero-form.component.html', 'submitted')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
There's our hero again, displayed read-only with interpolation bindings.
|
There's our hero again, displayed read-only with interpolation bindings.
|
||||||
This slug of HTML only appears while the component is in the submitted state.
|
This slug of HTML only appears while the component is in the submitted state.
|
||||||
|
|
||||||
There's an "Edit" button whose click event we bound to an expression
|
There's an "Edit" button whose click event we bound to an expression
|
||||||
that clears the `submitted` flag.
|
that clears the `submitted` flag.
|
||||||
|
|
||||||
Click it and this block disappears and the editable form reappears.
|
Click it and this block disappears and the editable form reappears.
|
||||||
|
|
||||||
That's as much drama as we can muster for now.
|
That's as much drama as we can muster for now.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Conclusion
|
## Conclusion
|
||||||
|
|
||||||
The Angular 2 form discussed in this chapter takes advantage of the following framework features to provide support for data modification, validation and more:
|
The Angular 2 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.
|
- An Angular HTML form template.
|
||||||
- A form component class with a `Component` decorator.
|
- A form component class with a `Component` decorator.
|
||||||
- The `ng-submit` directive for handling the form submission.
|
- The `ng-submit` directive for handling the form submission.
|
||||||
- Template local variables such as `#hf`, `#name`, `#alter-ego` and `#power`.
|
- Template local variables such as `#hf`, `#name`, `#alter-ego` and `#power`.
|
||||||
- The `ng-model` directive for two-way data binding.
|
- The `ng-model` directive for two-way data binding.
|
||||||
- The `ng-control` for validation and form element change tracking.
|
- The `ng-control` for validation and form element change tracking.
|
||||||
- The local variable’s `valid` property on input controls to check if a control is valid and show/hide error messages.
|
- The local variable’s `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.
|
- 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.
|
- Custom CSS classes that provide visual feedback to users about required invalid controls.
|
||||||
|
|
||||||
Here’s the final version of the application includes all of these framework features:
|
Here’s the final version of the application includes all of these framework features:
|
||||||
|
|
||||||
+makeTabs(
|
+makeTabs(
|
||||||
`forms/ts/src/app/hero-form.component.html,
|
`forms/ts/src/app/hero-form.component.html,
|
||||||
forms/ts/src/app/hero-form.component.ts,
|
forms/ts/src/app/hero-form.component.ts,
|
||||||
|
@ -605,14 +605,14 @@ figure.image-display
|
||||||
forms/ts/src/app/app.ts,
|
forms/ts/src/app/app.ts,
|
||||||
forms/ts/src/index.html,
|
forms/ts/src/index.html,
|
||||||
forms/ts/src/styles.css`,
|
forms/ts/src/styles.css`,
|
||||||
'final, final,,,,',
|
'final, final,,,,',
|
||||||
`hero-form.component.html,
|
`hero-form.component.html,
|
||||||
hero-form.component.ts,
|
hero-form.component.ts,
|
||||||
hero.ts,
|
hero.ts,
|
||||||
app.ts,
|
app.ts,
|
||||||
index.html,
|
index.html,
|
||||||
styles.css`)
|
styles.css`)
|
||||||
:markdown
|
:marked
|
||||||
Our final project folder structure should look like this:
|
Our final project folder structure should look like this:
|
||||||
```
|
```
|
||||||
angular2-forms
|
angular2-forms
|
||||||
|
@ -624,7 +624,7 @@ figure.image-display
|
||||||
│ | ├── hero-form.component.html
|
│ | ├── hero-form.component.html
|
||||||
│ | └── hero-form.component.ts
|
│ | └── hero-form.component.ts
|
||||||
│ ├── index.html
|
│ ├── index.html
|
||||||
│ ├── styles.css
|
│ ├── styles.css
|
||||||
│ └── tsconfig.json
|
│ └── tsconfig.json
|
||||||
└── package.json
|
└── package.json
|
||||||
```
|
```
|
|
@ -1,4 +1,4 @@
|
||||||
:markdown
|
:marked
|
||||||
# Angular 2 Glossary
|
# Angular 2 Glossary
|
||||||
#sg-tables.showcase.shadow-1
|
#sg-tables.showcase.shadow-1
|
||||||
header.showcase-header
|
header.showcase-header
|
||||||
|
@ -7,131 +7,131 @@
|
||||||
and the almost right word is the difference between
|
and the almost right word is the difference between
|
||||||
lightning and a lightning bug.</i> - Mark Twain
|
lightning and a lightning bug.</i> - Mark Twain
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Angular 2 has a vocabulary of its own.
|
Angular 2 has a vocabulary of its own.
|
||||||
Most Angular 2 terms are everyday English words
|
Most Angular 2 terms are everyday English words
|
||||||
with a specific meaning within the Angular system.
|
with a specific meaning within the Angular system.
|
||||||
|
|
||||||
We have gathered here the most prominent terms
|
We have gathered here the most prominent terms
|
||||||
and a few less familiar ones that have unusual or
|
and a few less familiar ones that have unusual or
|
||||||
unexpected definitions.
|
unexpected definitions.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Annotation
|
## Annotation
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
In practice a synonym for [Decoration](#decoration).
|
In practice a synonym for [Decoration](#decoration).
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
## Attribute Directive
|
## Attribute Directive
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
A category of [Directive](#directive) that can listen to and modify the behavior of
|
A category of [Directive](#directive) that can listen to and modify the behavior of
|
||||||
other HTML elements, attributes, properties, and components. They are usually represented
|
other HTML elements, attributes, properties, and components. They are usually represented
|
||||||
as HTML attributes, hence the name.
|
as HTML attributes, hence the name.
|
||||||
|
|
||||||
The `ng-class` directive for adding and removing CSS class names is a good example of
|
The `ng-class` directive for adding and removing CSS class names is a good example of
|
||||||
an Attribute Directive.
|
an Attribute Directive.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Binding
|
## Binding
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
Almost always refers to [Data Binding](#data-binding) and the act of
|
Almost always refers to [Data Binding](#data-binding) and the act of
|
||||||
binding an HTML object property to a data object property.
|
binding an HTML object property to a data object property.
|
||||||
|
|
||||||
May refer to a [Dependency Injection](#dependency-injection) binding
|
May refer to a [Dependency Injection](#dependency-injection) binding
|
||||||
between a "token" or "key" and a dependency [provider](#provider).
|
between a "token" or "key" and a dependency [provider](#provider).
|
||||||
This more rare usage should be clear in context.
|
This more rare usage should be clear in context.
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
## Bootstrap
|
## Bootstrap
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
We launch an Angular application by "bootstrapping" it with the `bootstrap` method.
|
We launch an Angular application by "bootstrapping" it with the `bootstrap` method.
|
||||||
The `bootstrap` method identifies an application's top level "root" [Component](#component)
|
The `bootstrap` method identifies an application's top level "root" [Component](#component)
|
||||||
and optionally registers service [providers](#provider) with the
|
and optionally registers service [providers](#provider) with the
|
||||||
[dependency injection system](#dependency-injection).
|
[dependency injection system](#dependency-injection).
|
||||||
|
|
||||||
One can bootstrap multiple apps in the same `index.html`, each with its own top level root.
|
One can bootstrap multiple apps in the same `index.html`, each with its own top level root.
|
||||||
|
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Component
|
## Component
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
An Angular class responsible for exposing data
|
An Angular class responsible for exposing data
|
||||||
to a [View](#view) and handling most of the view’s display
|
to a [View](#view) and handling most of the view’s display
|
||||||
and user-interaction logic.
|
and user-interaction logic.
|
||||||
|
|
||||||
The Component is one of the most important building blocks in the Angular system.
|
The Component is one of the most important building blocks in the Angular system.
|
||||||
It is, in fact, an Angular [Directive](directive) with a companion [Template](#template).
|
It is, in fact, an Angular [Directive](directive) with a companion [Template](#template).
|
||||||
|
|
||||||
The developer applies the `@Component` [decorator](decorator) to
|
The developer applies the `@Component` [decorator](decorator) to
|
||||||
the component class, thereby attaching to the class the essential component metadata
|
the component class, thereby attaching to the class the essential component metadata
|
||||||
that Angular needs to create a component instance and render it with its template
|
that Angular needs to create a component instance and render it with its template
|
||||||
as a view.
|
as a view.
|
||||||
|
|
||||||
Those familiar with "MVC" and "MVVM" patterns will recognize
|
Those familiar with "MVC" and "MVVM" patterns will recognize
|
||||||
the Component in the role of "Controller" or "View Model".
|
the Component in the role of "Controller" or "View Model".
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Data Binding
|
## Data Binding
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
Applications display data values to a user and respond to user
|
Applications display data values to a user and respond to user
|
||||||
actions (clicks, touches, keystrokes).
|
actions (clicks, touches, keystrokes).
|
||||||
|
|
||||||
We could push application data values into HTML, attach
|
We could push application data values into HTML, attach
|
||||||
event listeners, pull changed values from the screen, and
|
event listeners, pull changed values from the screen, and
|
||||||
update application data values ... all by hand.
|
update application data values ... all by hand.
|
||||||
|
|
||||||
Or we could declare the relationship between an HTML widget
|
Or we could declare the relationship between an HTML widget
|
||||||
and an application data source ... and let a data binding
|
and an application data source ... and let a data binding
|
||||||
framework handle the details.
|
framework handle the details.
|
||||||
|
|
||||||
Data Binding is that second approach. Angular has a rich
|
Data Binding is that second approach. Angular has a rich
|
||||||
data binding framework with a variety of data binding
|
data binding framework with a variety of data binding
|
||||||
operations and supporting declaration syntax.
|
operations and supporting declaration syntax.
|
||||||
|
|
||||||
The many forms of binding include:
|
The many forms of binding include:
|
||||||
* [Interpolation](./template-syntax.html#interpolation)
|
* [Interpolation](./template-syntax.html#interpolation)
|
||||||
* [Property Binding](./template-syntax.html#property-binding)
|
* [Property Binding](./template-syntax.html#property-binding)
|
||||||
* [Event Binding](./template-syntax.html#event-binding)
|
* [Event Binding](./template-syntax.html#event-binding)
|
||||||
* [Attribute Binding](./template-syntax.html#attribute-binding)
|
* [Attribute Binding](./template-syntax.html#attribute-binding)
|
||||||
* [Class Binding](./template-syntax.html#class-binding)
|
* [Class Binding](./template-syntax.html#class-binding)
|
||||||
* [Style Binding](./template-syntax.html#style-binding)
|
* [Style Binding](./template-syntax.html#style-binding)
|
||||||
* [Two-way data binding with ng-model](./template-syntax.html#ng-model)
|
* [Two-way data binding with ng-model](./template-syntax.html#ng-model)
|
||||||
|
|
||||||
Learn more about data binding in the
|
Learn more about data binding in the
|
||||||
[Template Syntax](./template-syntax.html#data-binding) chapter.
|
[Template Syntax](./template-syntax.html#data-binding) chapter.
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
<a id="decorator"></a> <a id="decoration"></a>
|
<a id="decorator"></a> <a id="decoration"></a>
|
||||||
## Decorator | Decoration
|
## Decorator | Decoration
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
A Decorator is a **function** that adds metadata to a class, its members (properties, methods) and function arguments.
|
A Decorator is a **function** that adds metadata to a class, its members (properties, methods) and function arguments.
|
||||||
|
|
||||||
Decorators are a JavaScript language [feature](https://github.com/wycats/javascript-decorators), implemented in TypeScript and proposed for ES2016 (AKA ES7).
|
Decorators are a JavaScript language [feature](https://github.com/wycats/javascript-decorators), implemented in TypeScript and proposed for ES2016 (AKA ES7).
|
||||||
|
|
||||||
We apply a decorator by positioning it
|
We apply a decorator by positioning it
|
||||||
immediately above or to the left of the thing it decorates.
|
immediately above or to the left of the thing it decorates.
|
||||||
|
|
||||||
Angular has its own set of decorators to help it interoperate with our application parts.
|
Angular has its own set of decorators to help it interoperate with our application parts.
|
||||||
Here is an example of a `@Component` decorator that identifies a
|
Here is an example of a `@Component` decorator that identifies a
|
||||||
class as an Angular [Component](#component) and an `@Input` decorator applied to a property
|
class as an Angular [Component](#component) and an `@Input` decorator applied to a property
|
||||||
of that component.
|
of that component.
|
||||||
The elided object argument to the `@Component` decorator would contain the pertinent component metadata.
|
The elided object argument to the `@Component` decorator would contain the pertinent component metadata.
|
||||||
```
|
```
|
||||||
@Component({...})
|
@Component({...})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
constructor(@Inject('SpecialFoo') public foo:Foo) {}
|
constructor(@Inject('SpecialFoo') public foo:Foo) {}
|
||||||
@Input()
|
@Input()
|
||||||
name:string;
|
name:string;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -140,328 +140,328 @@
|
||||||
classes appearing below it in the file.
|
classes appearing below it in the file.
|
||||||
|
|
||||||
.alert.is-important
|
.alert.is-important
|
||||||
:markdown
|
:marked
|
||||||
Always include the parentheses `()` when applying a decorator.
|
Always include the parentheses `()` when applying a decorator.
|
||||||
A decorator is a **function** that must be called when applied.
|
A decorator is a **function** that must be called when applied.
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
## Dependency Injection
|
## Dependency Injection
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
Dependency Injection is both a design pattern and a mechanism
|
Dependency Injection is both a design pattern and a mechanism
|
||||||
for creating and delivering parts of an application to other
|
for creating and delivering parts of an application to other
|
||||||
parts of an application that request them.
|
parts of an application that request them.
|
||||||
|
|
||||||
Angular developers prefer to build applications by defining many simple parts
|
Angular developers prefer to build applications by defining many simple parts
|
||||||
that each do one thing well and then wire them together at runtime.
|
that each do one thing well and then wire them together at runtime.
|
||||||
|
|
||||||
These parts often rely on other parts. An Angular [Component](#component)
|
These parts often rely on other parts. An Angular [Component](#component)
|
||||||
part might rely on a service part to get data or perform a calculation. When a
|
part might rely on a service part to get data or perform a calculation. When a
|
||||||
part "A" relies on another part "B", we say that "A" depends on "B" and
|
part "A" relies on another part "B", we say that "A" depends on "B" and
|
||||||
that "B" is a dependency of "A".
|
that "B" is a dependency of "A".
|
||||||
|
|
||||||
We can ask a "Dependency Injection System" to create "A"
|
We can ask a "Dependency Injection System" to create "A"
|
||||||
for us and handle all the dependencies.
|
for us and handle all the dependencies.
|
||||||
If "A" needs "B" and "B" needs "C", the system resolves that chain of dependencies
|
If "A" needs "B" and "B" needs "C", the system resolves that chain of dependencies
|
||||||
and returns a fully prepared instance of "A".
|
and returns a fully prepared instance of "A".
|
||||||
|
|
||||||
Angular provides and relies upon its own sophisticated
|
Angular provides and relies upon its own sophisticated
|
||||||
[Dependency Injection](./dependency-injection.html) system
|
[Dependency Injection](./dependency-injection.html) system
|
||||||
to assemble and run applications by "injecting" application parts
|
to assemble and run applications by "injecting" application parts
|
||||||
into other application parts where and when needed.
|
into other application parts where and when needed.
|
||||||
|
|
||||||
At the core is an [`Injector`](#injector) that returns dependency values on request.
|
At the core is an [`Injector`](#injector) that returns dependency values on request.
|
||||||
The expression `injector.get(token)` returns the value associated with the given token.
|
The expression `injector.get(token)` returns the value associated with the given token.
|
||||||
|
|
||||||
A token is an Angular type (`OpaqueToken`). We rarely deal with tokens directly; most
|
A token is an Angular type (`OpaqueToken`). We rarely deal with tokens directly; most
|
||||||
methods accept a class name (`Foo`) or a string ("foo") and Angular converts it
|
methods accept a class name (`Foo`) or a string ("foo") and Angular converts it
|
||||||
to a token. When we write `injector.get(Foo)`, the injector returns
|
to a token. When we write `injector.get(Foo)`, the injector returns
|
||||||
the value associated with the token for the `Foo` class, typically an instance of `Foo` itself.
|
the value associated with the token for the `Foo` class, typically an instance of `Foo` itself.
|
||||||
|
|
||||||
Angular makes similar requests internally during many of its operations
|
Angular makes similar requests internally during many of its operations
|
||||||
as when it creates a [`Component`](#AppComponent) for display.
|
as when it creates a [`Component`](#AppComponent) for display.
|
||||||
|
|
||||||
The `Injector` maintains an internal map of tokens to dependency values.
|
The `Injector` maintains an internal map of tokens to dependency values.
|
||||||
If the `Injector` can't find a value for a given token, it creates
|
If the `Injector` can't find a value for a given token, it creates
|
||||||
a new value using a `Provider` for that token.
|
a new value using a `Provider` for that token.
|
||||||
|
|
||||||
A [Provider](#provider) is a recipe for
|
A [Provider](#provider) is a recipe for
|
||||||
creating new instances of a dependency value associated with a particular token.
|
creating new instances of a dependency value associated with a particular token.
|
||||||
|
|
||||||
An injector can only create a value for a given token if it has
|
An injector can only create a value for a given token if it has
|
||||||
a `Provider` for that token in its internal provider registry.
|
a `Provider` for that token in its internal provider registry.
|
||||||
Registering providers is a critical preparatory step.
|
Registering providers is a critical preparatory step.
|
||||||
|
|
||||||
Angular registers some of its own providers with every injector.
|
Angular registers some of its own providers with every injector.
|
||||||
We can register our own providers. Quite often the best time to register a `Provider`
|
We can register our own providers. Quite often the best time to register a `Provider`
|
||||||
is when we [bootstrap](#bootstrap) the application.
|
is when we [bootstrap](#bootstrap) the application.
|
||||||
There are other opportunities to register as well.
|
There are other opportunities to register as well.
|
||||||
|
|
||||||
Learn more in the [Dependency Injection](./dependency-injection.html) chapter.
|
Learn more in the [Dependency Injection](./dependency-injection.html) chapter.
|
||||||
:markdown
|
:marked
|
||||||
## Directive
|
## Directive
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
An Angular class responsible for creating, re-shaping, and interacting with HTML elements
|
An Angular class responsible for creating, re-shaping, and interacting with HTML elements
|
||||||
in the browser DOM. Directives are Angular's most fundamental feature.
|
in the browser DOM. Directives are Angular's most fundamental feature.
|
||||||
|
|
||||||
A Directive is almost always associated with an HTML element or attribute.
|
A Directive is almost always associated with an HTML element or attribute.
|
||||||
We often refer to such an element or attribute as the directive itself.
|
We often refer to such an element or attribute as the directive itself.
|
||||||
When Angular finds a directive in an HTML template,
|
When Angular finds a directive in an HTML template,
|
||||||
it creates the matching directive class instance
|
it creates the matching directive class instance
|
||||||
and gives that instance control over that portion of the browser DOM.
|
and gives that instance control over that portion of the browser DOM.
|
||||||
|
|
||||||
Developers can invent custom HTML markup (e.g., `<my-directive>`) to
|
Developers can invent custom HTML markup (e.g., `<my-directive>`) to
|
||||||
associate with their custom directives. They add this custom markup to HTML templates
|
associate with their custom directives. They add this custom markup to HTML templates
|
||||||
as if they were writing native HTML. In this way, directives become extensions of
|
as if they were writing native HTML. In this way, directives become extensions of
|
||||||
HTML itself.
|
HTML itself.
|
||||||
|
|
||||||
Directives fall into one of three categories:
|
Directives fall into one of three categories:
|
||||||
|
|
||||||
1. [Components](#component) that combine application logic with an HTML template to
|
1. [Components](#component) that combine application logic with an HTML template to
|
||||||
render application [views]. Components are usually represented as HTML elements.
|
render application [views]. Components are usually represented as HTML elements.
|
||||||
They are the building blocks of an Angular application and the
|
They are the building blocks of an Angular application and the
|
||||||
developer can expect to write a lot of them.
|
developer can expect to write a lot of them.
|
||||||
|
|
||||||
1. [Attribute Directives](attribute-directive) that can listen to and modify the behavior of
|
1. [Attribute Directives](attribute-directive) that can listen to and modify the behavior of
|
||||||
other HTML elements, attributes, properties, and components. They are usually represented
|
other HTML elements, attributes, properties, and components. They are usually represented
|
||||||
as HTML attributes, hence the name.
|
as HTML attributes, hence the name.
|
||||||
|
|
||||||
1. [Structural Directives](#structural-directive), a directive responsible for
|
1. [Structural Directives](#structural-directive), a directive responsible for
|
||||||
shaping or re-shaping HTML layout, typically by adding, removing, or manipulating
|
shaping or re-shaping HTML layout, typically by adding, removing, or manipulating
|
||||||
elements and their children.
|
elements and their children.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## ECMAScript
|
## ECMAScript
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
The [official JavaScript language specification](https://en.wikipedia.org/wiki/ECMAScript).
|
The [official JavaScript language specification](https://en.wikipedia.org/wiki/ECMAScript).
|
||||||
|
|
||||||
The latest approved version of JavaScript is
|
The latest approved version of JavaScript is
|
||||||
[ECMAScript 2015](http://www.ecma-international.org/ecma-262/6.0/)
|
[ECMAScript 2015](http://www.ecma-international.org/ecma-262/6.0/)
|
||||||
(AKA "ES2015" or "ES6") and many Angular 2 developers will write their applications
|
(AKA "ES2015" or "ES6") and many Angular 2 developers will write their applications
|
||||||
either in this version of the language or a dialect that strives to be
|
either in this version of the language or a dialect that strives to be
|
||||||
compatible with it such as [TypeScript](#typesScript).
|
compatible with it such as [TypeScript](#typesScript).
|
||||||
|
|
||||||
Most modern browsers today only support the prior "ECMAScript 5" (AKA ES5) standard.
|
Most modern browsers today only support the prior "ECMAScript 5" (AKA ES5) standard.
|
||||||
Applications written in ES2015 or one of its dialects must be "[transpiled](transpile)"
|
Applications written in ES2015 or one of its dialects must be "[transpiled](transpile)"
|
||||||
to ES5 JavaScript.
|
to ES5 JavaScript.
|
||||||
|
|
||||||
Angular 2 developers may choose to write in ES5 directly.
|
Angular 2 developers may choose to write in ES5 directly.
|
||||||
:markdown
|
:marked
|
||||||
## ECMAScript 2015
|
## ECMAScript 2015
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
The lastest released version of JavaScript,
|
The lastest released version of JavaScript,
|
||||||
[ECMAScript 2015](http://www.ecma-international.org/ecma-262/6.0/)
|
[ECMAScript 2015](http://www.ecma-international.org/ecma-262/6.0/)
|
||||||
(AKA "ES2015" or "ES6")
|
(AKA "ES2015" or "ES6")
|
||||||
:markdown
|
:marked
|
||||||
## ES2015
|
## ES2015
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
Short hand for "[ECMAScript 2015](#ecmascript=2015)".
|
Short hand for "[ECMAScript 2015](#ecmascript=2015)".
|
||||||
:markdown
|
:marked
|
||||||
## ES6
|
## ES6
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
Short hand for "[ECMAScript 2015](#ecmascript=2015)".
|
Short hand for "[ECMAScript 2015](#ecmascript=2015)".
|
||||||
:markdown
|
:marked
|
||||||
## ES5
|
## ES5
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
Short hand for "ECMAScript 5", the version of JavaScript run by most modern browsers.
|
Short hand for "ECMAScript 5", the version of JavaScript run by most modern browsers.
|
||||||
See [ECMAScript](#ecmascript).
|
See [ECMAScript](#ecmascript).
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Injector
|
## Injector
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
An object in the Angular [dependency injection system](#dependency-injection)
|
An object in the Angular [dependency injection system](#dependency-injection)
|
||||||
that can find a named "dependency" in its cache or create such a thing
|
that can find a named "dependency" in its cache or create such a thing
|
||||||
with a registered [provider](#provider).
|
with a registered [provider](#provider).
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
## Input
|
## Input
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
A directive property that can be the ***target*** of a
|
A directive property that can be the ***target*** of a
|
||||||
[Property Binding](./template-syntax.html#property-binding).
|
[Property Binding](./template-syntax.html#property-binding).
|
||||||
Data values flow *into* this property from the data source identified
|
Data values flow *into* this property from the data source identified
|
||||||
in the template expression to the right of the equal sign.
|
in the template expression to the right of the equal sign.
|
||||||
|
|
||||||
See the [Template Syntax](./template-syntax.html#inputs-outputs) chapter.
|
See the [Template Syntax](./template-syntax.html#inputs-outputs) chapter.
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
## Interpolation
|
## Interpolation
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
A form of [Property Data Binding](#data-binding) in which a
|
A form of [Property Data Binding](#data-binding) in which a
|
||||||
[template expression](#template-expression) between double-curly braces
|
[template expression](#template-expression) between double-curly braces
|
||||||
renders as text. That text may be concatenated with neighboring text
|
renders as text. That text may be concatenated with neighboring text
|
||||||
before it is assigned to an element property
|
before it is assigned to an element property
|
||||||
or displayed between element tags as in this example.
|
or displayed between element tags as in this example.
|
||||||
|
|
||||||
code-example(language="html" escape="html").
|
code-example(language="html" escape="html").
|
||||||
<label>My current hero is {{hero.name}}</label>
|
<label>My current hero is {{hero.name}}</label>
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Learn more about interpolation in the
|
Learn more about interpolation in the
|
||||||
[Template Syntax](./template-syntax.html#interpolation) chapter.
|
[Template Syntax](./template-syntax.html#interpolation) chapter.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Output
|
## Output
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
A directive property that can be the ***target*** of an
|
A directive property that can be the ***target*** of an
|
||||||
[Event Binding](./template-syntax.html#property-binding).
|
[Event Binding](./template-syntax.html#property-binding).
|
||||||
Events stream *out* of this property to the receiver identified
|
Events stream *out* of this property to the receiver identified
|
||||||
in the template expression to the right of the equal sign.
|
in the template expression to the right of the equal sign.
|
||||||
|
|
||||||
See the [Template Syntax](./template-syntax.html#inputs-outputs) chapter.
|
See the [Template Syntax](./template-syntax.html#inputs-outputs) chapter.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Pipe
|
## Pipe
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
An Angular pipe is a function that transforms input values to output values for
|
An Angular pipe is a function that transforms input values to output values for
|
||||||
display in a [view](#view). We use the `@Pipe` [decorator](decorator)
|
display in a [view](#view). We use the `@Pipe` [decorator](decorator)
|
||||||
to associate the pipe function with a name. We then can use that
|
to associate the pipe function with a name. We then can use that
|
||||||
name in our HTML to declaratively transform values on screen.
|
name in our HTML to declaratively transform values on screen.
|
||||||
|
|
||||||
Here's an example that uses the built-in `currency` pipe to display
|
Here's an example that uses the built-in `currency` pipe to display
|
||||||
a numeric value in the local currency.
|
a numeric value in the local currency.
|
||||||
|
|
||||||
code-example(language="html" escape="html").
|
code-example(language="html" escape="html").
|
||||||
<label>Price: </label>{{product.price | currency}}
|
<label>Price: </label>{{product.price | currency}}
|
||||||
:markdown
|
:marked
|
||||||
Learn more in the chapter on [pipes](./pipes.html) .
|
Learn more in the chapter on [pipes](./pipes.html) .
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
## Provider
|
## Provider
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
A Provider creates a new instance of a dependency for the Dependency Injection system.
|
A Provider creates a new instance of a dependency for the Dependency Injection system.
|
||||||
It relates a lookup token to code - sometimes called a "recipe" - that can create a dependency value.
|
It relates a lookup token to code - sometimes called a "recipe" - that can create a dependency value.
|
||||||
|
|
||||||
For example, `new Provider(Foo, {useClass: Foo})` creates a `Provider`
|
For example, `new Provider(Foo, {useClass: Foo})` creates a `Provider`
|
||||||
that relates the `Foo` token to a function that creates a new instance of the `Foo` class.
|
that relates the `Foo` token to a function that creates a new instance of the `Foo` class.
|
||||||
|
|
||||||
There are other ways to create tokens and recipes.
|
There are other ways to create tokens and recipes.
|
||||||
See [Dependency Injection](#dependency-injection) chapter to learn more.
|
See [Dependency Injection](#dependency-injection) chapter to learn more.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Router
|
## Router
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
Most applications consist of many screens or [views](#view).
|
Most applications consist of many screens or [views](#view).
|
||||||
The user navigates among them by clicking links and buttons
|
The user navigates among them by clicking links and buttons
|
||||||
and taking other similar actions that cause the application to
|
and taking other similar actions that cause the application to
|
||||||
replace one view with another.
|
replace one view with another.
|
||||||
|
|
||||||
The [Angular router](./router.html) is a richly featured mechanism for configuring
|
The [Angular router](./router.html) is a richly featured mechanism for configuring
|
||||||
and managing the entire navigation process including the creation and destruction
|
and managing the entire navigation process including the creation and destruction
|
||||||
of views.
|
of views.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Structural Directive
|
## Structural Directive
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
A category of [Directive](#directive) that can
|
A category of [Directive](#directive) that can
|
||||||
shape or re-shape HTML layout, typically by adding, removing, or manipulating
|
shape or re-shape HTML layout, typically by adding, removing, or manipulating
|
||||||
elements and their children.
|
elements and their children.
|
||||||
|
|
||||||
The `ng-if` "conditional element" directive and the `ng-for` "repeater" directive are
|
The `ng-if` "conditional element" directive and the `ng-for` "repeater" directive are
|
||||||
good examples in this category.
|
good examples in this category.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Template
|
## Template
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
A template is a chunk of HTML that Angular uses to render a [view](#view) with
|
A template is a chunk of HTML that Angular uses to render a [view](#view) with
|
||||||
the support and continuing guidance of an Angular [Directive](#directive),
|
the support and continuing guidance of an Angular [Directive](#directive),
|
||||||
most notably a [Component](#component).
|
most notably a [Component](#component).
|
||||||
|
|
||||||
We write templates in a special [Template Syntax](./template-syntax.html).
|
We write templates in a special [Template Syntax](./template-syntax.html).
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
## Template Expression
|
## Template Expression
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
An expression in a JavaScript-like syntax that Angular evaluates within
|
An expression in a JavaScript-like syntax that Angular evaluates within
|
||||||
a [data binding](#data-binding). Learn how to write template expressions
|
a [data binding](#data-binding). Learn how to write template expressions
|
||||||
in the [Template Syntax](./template-syntax.html#template-expressions) chapter.
|
in the [Template Syntax](./template-syntax.html#template-expressions) chapter.
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
## Transpile
|
## Transpile
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
The process of transforming code written in one form of JavaScript
|
The process of transforming code written in one form of JavaScript
|
||||||
(e.g., TypeScript) into another form of JavaScript (e.g., [ES5](#es5)).
|
(e.g., TypeScript) into another form of JavaScript (e.g., [ES5](#es5)).
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
## TypeScript
|
## TypeScript
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
A version of JavaScript that supports most [ECMAScript 2015](#ecmascript=2015)
|
A version of JavaScript that supports most [ECMAScript 2015](#ecmascript=2015)
|
||||||
language features and many features that may arrive in future versions
|
language features and many features that may arrive in future versions
|
||||||
of JavaScript such as [Decorators](#decorator).
|
of JavaScript such as [Decorators](#decorator).
|
||||||
|
|
||||||
TypeScript is also noteable for its optional typing system which gives
|
TypeScript is also noteable for its optional typing system which gives
|
||||||
us compile-time type-checking and strong tooling support (e.g. "intellisense",
|
us compile-time type-checking and strong tooling support (e.g. "intellisense",
|
||||||
code completion, refactoring, and intelligent search). Many code editors
|
code completion, refactoring, and intelligent search). Many code editors
|
||||||
and IDEs support TypeScript either natively or with plugins.
|
and IDEs support TypeScript either natively or with plugins.
|
||||||
|
|
||||||
TypeScript is the preferred language for Angular 2 development although
|
TypeScript is the preferred language for Angular 2 development although
|
||||||
we are welcome to write in other JavaScript dialects such as [ES5](#es5).
|
we are welcome to write in other JavaScript dialects such as [ES5](#es5).
|
||||||
|
|
||||||
Angular 2 itself is written in TypeScript.
|
Angular 2 itself is written in TypeScript.
|
||||||
|
|
||||||
Learn more about TypeScript on its [website](http://www.typescriptlang.org/).
|
Learn more about TypeScript on its [website](http://www.typescriptlang.org/).
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## View
|
## View
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
A view is a portion of the screen that displays information and responds
|
A view is a portion of the screen that displays information and responds
|
||||||
to user actions such as clicks, mouse moves, and keystrokes.
|
to user actions such as clicks, mouse moves, and keystrokes.
|
||||||
|
|
||||||
Angular renders a view under the control of one or more [Directives](#directive),
|
Angular renders a view under the control of one or more [Directives](#directive),
|
||||||
especially [Component](#component) directives and their companion [Templates](#template).
|
especially [Component](#component) directives and their companion [Templates](#template).
|
||||||
The Component plays such a prominent role that we often
|
The Component plays such a prominent role that we often
|
||||||
find it convenient to refer to a component as a view.
|
find it convenient to refer to a component as a view.
|
||||||
|
|
||||||
Views often contain other views and any view might be loaded and unloaded
|
Views often contain other views and any view might be loaded and unloaded
|
||||||
dynamically as the user navigates through the application, typically
|
dynamically as the user navigates through the application, typically
|
||||||
under the control of a [router](#rounter).
|
under the control of a [router](#rounter).
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Zone
|
## Zone
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
Zones are a mechanism for encapsulating and intercepting
|
Zones are a mechanism for encapsulating and intercepting
|
||||||
a JavaScript application's asynchronous activity.
|
a JavaScript application's asynchronous activity.
|
||||||
|
|
||||||
The browser DOM and JavaScript have a limited number
|
The browser DOM and JavaScript have a limited number
|
||||||
of asynchronous activities, activities such as DOM events (e.g., clicks),
|
of asynchronous activities, activities such as DOM events (e.g., clicks),
|
||||||
[promises](#promise), and
|
[promises](#promise), and
|
||||||
[XHR](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)
|
[XHR](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)
|
||||||
calls to remote servers.
|
calls to remote servers.
|
||||||
|
|
||||||
Zones intercept all of these activities and give a "zone client" the opportunity
|
Zones intercept all of these activities and give a "zone client" the opportunity
|
||||||
to take action before and after the asynch activity completes.
|
to take action before and after the asynch activity completes.
|
||||||
|
|
||||||
Angular runs our application in a zone where it can respond to
|
Angular runs our application in a zone where it can respond to
|
||||||
asynchronous events by checking for data changes and updating
|
asynchronous events by checking for data changes and updating
|
||||||
the information it displays via [data binding](#data-binding).
|
the information it displays via [data binding](#data-binding).
|
||||||
|
|
||||||
Learn more about zones in this
|
Learn more about zones in this
|
||||||
[Brian Ford video](https://www.youtube.com/watch?v=3IqtmUscE_U).
|
[Brian Ford video](https://www.youtube.com/watch?v=3IqtmUscE_U).
|
|
@ -1,115 +1,115 @@
|
||||||
include ../../../../_includes/_util-fns
|
include ../../../../_includes/_util-fns
|
||||||
:markdown
|
:marked
|
||||||
We learned the basics of Angular Dependency injection in an
|
We learned the basics of Angular Dependency injection in an
|
||||||
[earlier chapter](./dependency-injection.html).
|
[earlier chapter](./dependency-injection.html).
|
||||||
|
|
||||||
In this chapter we learn that Angular has an
|
In this chapter we learn that Angular has an
|
||||||
Hierarchical Dependency Injection system that supports trees of injectors.
|
Hierarchical Dependency Injection system that supports trees of injectors.
|
||||||
|
|
||||||
In practice, there is a tree of injectors that parallel an application's component tree.
|
In practice, there is a tree of injectors that parallel an application's component tree.
|
||||||
We can re-configure the injectors at any level of that component tree with
|
We can re-configure the injectors at any level of that component tree with
|
||||||
interesting and useful results.
|
interesting and useful results.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## The Injector Tree
|
## The Injector Tree
|
||||||
|
|
||||||
In an [earlier chapter](./dependency-injection.html)
|
In an [earlier chapter](./dependency-injection.html)
|
||||||
we learned how to configure a dependency injector in different ways and how to retrieve dependencies where we need them.
|
we learned how to configure a dependency injector in different ways and how to retrieve dependencies where we need them.
|
||||||
|
|
||||||
What if we told you there is no such thing as ***the*** injector?
|
What if we told you there is no such thing as ***the*** injector?
|
||||||
In fact, each application has multiple injectors!
|
In fact, each application has multiple injectors!
|
||||||
|
|
||||||
We may have heard that an Angular application is a tree of components.
|
We may have heard that an Angular application is a tree of components.
|
||||||
It may surprise us to learn that there is a corresponding tree of injectors
|
It may surprise us to learn that there is a corresponding tree of injectors
|
||||||
and each component instance in that tree has its own injector!
|
and each component instance in that tree has its own injector!
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
That isn't literally true. Angular is more efficient than that. What is true is that each component instance
|
That isn't literally true. Angular is more efficient than that. What is true is that each component instance
|
||||||
has an injector and that components at different levels in the tree have different injectors.
|
has an injector and that components at different levels in the tree have different injectors.
|
||||||
|
|
||||||
It is helpful for our purposes to pretend that every component has its own injector.
|
It is helpful for our purposes to pretend that every component has its own injector.
|
||||||
:markdown
|
:marked
|
||||||
Consider a simple variation on the Tour of Heroes application consisting of three different components:
|
Consider a simple variation on the Tour of Heroes application consisting of three different components:
|
||||||
`HeroesApp`, `HeroesListComponent` and `HeroesCardComponent`.
|
`HeroesApp`, `HeroesListComponent` and `HeroesCardComponent`.
|
||||||
The `HeroesApp` holds a single instance of `HeroesListComponent`.
|
The `HeroesApp` holds a single instance of `HeroesListComponent`.
|
||||||
The new twist is that the `HeroesListComponent` may hold and manage multiple instances of the `HeroesCardComponent`.
|
The new twist is that the `HeroesListComponent` may hold and manage multiple instances of the `HeroesCardComponent`.
|
||||||
|
|
||||||
The following diagram represents the state of the component tree when there are three instances of `HeroesCardComponent`
|
The following diagram represents the state of the component tree when there are three instances of `HeroesCardComponent`
|
||||||
open simultaneously.
|
open simultaneously.
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src="/resources/images/devguide/dependency-injection/component-hierarchy.png" alt="injector tree")
|
img(src="/resources/images/devguide/dependency-injection/component-hierarchy.png" alt="injector tree")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Each component instance gets its own injector and an injector at one level is a child injector of the injector above it in the tree.
|
Each component instance gets its own injector and an injector at one level is a child injector of the injector above it in the tree.
|
||||||
|
|
||||||
When a component at the bottom requests a dependency, Angular tries to satisfy that dependency with a provider registered in that component's own injector.
|
When a component at the bottom requests a dependency, Angular tries to satisfy that dependency with a provider registered in that component's own injector.
|
||||||
If the component's injector lacks the provider, it passes the request up to its parent component's injector.
|
If the component's injector lacks the provider, it passes the request up to its parent component's injector.
|
||||||
If that injector can't satisfy the request, it passes it along to *its* parent component's injector.
|
If that injector can't satisfy the request, it passes it along to *its* parent component's injector.
|
||||||
The requests keep bubbling up until we find an injector that can handle the request or run out of component ancestors.
|
The requests keep bubbling up until we find an injector that can handle the request or run out of component ancestors.
|
||||||
If we run out of ancestors, Angular throws an error.
|
If we run out of ancestors, Angular throws an error.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
There's a third possibililty. An intermediate component can declare that it is the "host" component.
|
There's a third possibililty. An intermediate component can declare that it is the "host" component.
|
||||||
The hunt for providers will climb no higher than the injector for this host component.
|
The hunt for providers will climb no higher than the injector for this host component.
|
||||||
We'll reserve discussion of this option for another day.
|
We'll reserve discussion of this option for another day.
|
||||||
:markdown
|
:marked
|
||||||
Such a proliferation of injectors makes little sense until we consider the possiblity that injectors at different levels can be
|
Such a proliferation of injectors makes little sense until we consider the possiblity that injectors at different levels can be
|
||||||
configured with different providers. We don't *have* to re-configure providers at every level. But we *can*.
|
configured with different providers. We don't *have* to re-configure providers at every level. But we *can*.
|
||||||
|
|
||||||
If we don't re-configure, the tree of injectors appears to be flat. All requests bubble up to the root injector that we
|
If we don't re-configure, the tree of injectors appears to be flat. All requests bubble up to the root injector that we
|
||||||
configured with the `bootstrap` method.
|
configured with the `bootstrap` method.
|
||||||
|
|
||||||
The ability to configure one or more providers at different levels opens up interesting and useful possibilities.
|
The ability to configure one or more providers at different levels opens up interesting and useful possibilities.
|
||||||
|
|
||||||
Let’s return to our Car example.
|
Let’s return to our Car example.
|
||||||
Suppose configured the root injector (marked as A) with providers for `Car`, `Engine` and `Tires`.
|
Suppose configured the root injector (marked as A) with providers for `Car`, `Engine` and `Tires`.
|
||||||
We create a child component (B) that defines its own providers for `Car` and `Engine`
|
We create a child component (B) that defines its own providers for `Car` and `Engine`
|
||||||
This child is the parent of another component (C) that defines its own provider for `Car`.
|
This child is the parent of another component (C) that defines its own provider for `Car`.
|
||||||
|
|
||||||
Behind the scenes each component sets up its own injector with one or more providers defined for that component itself.
|
Behind the scenes each component sets up its own injector with one or more providers defined for that component itself.
|
||||||
|
|
||||||
When we resolve an instance of `Car` at the deepest component (C),
|
When we resolve an instance of `Car` at the deepest component (C),
|
||||||
it's injector produces an instance of `Car` resolved by injector (C) with an `Engine` resolved by injector (B) and
|
it's injector produces an instance of `Car` resolved by injector (C) with an `Engine` resolved by injector (B) and
|
||||||
`Tires` resolved by the root injector (A).
|
`Tires` resolved by the root injector (A).
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src="/resources/images/devguide/dependency-injection/injector-tree.png" alt="injector tree")
|
img(src="/resources/images/devguide/dependency-injection/injector-tree.png" alt="injector tree")
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Component Injectors
|
## Component Injectors
|
||||||
|
|
||||||
In the previous section, we talked about injectors and how they are organized like a tree. Lookups follow the injector tree upwards until they found the requested thing to inject. But when do we actually want to provide bindings on the root injector and when do we want to provide them on a child injector?
|
In the previous section, we talked about injectors and how they are organized like a tree. Lookups follow the injector tree upwards until they found the requested thing to inject. But when do we actually want to provide bindings on the root injector and when do we want to provide them on a child injector?
|
||||||
|
|
||||||
Consider you are building a component to show a list of super heroes that displays each super hero in a card with it’s name and superpower. There should also be an edit button that opens up an editor to change the name and superpower of our hero.
|
Consider you are building a component to show a list of super heroes that displays each super hero in a card with it’s name and superpower. There should also be an edit button that opens up an editor to change the name and superpower of our hero.
|
||||||
|
|
||||||
One important aspect of the editing functionality is that we want to allow multiple heroes to be in edit mode at the same time and that one can always either commit or cancel the proposed changes.
|
One important aspect of the editing functionality is that we want to allow multiple heroes to be in edit mode at the same time and that one can always either commit or cancel the proposed changes.
|
||||||
|
|
||||||
Let’s take a look at the `HeroesListComponent` which is the root component for this example.
|
Let’s take a look at the `HeroesListComponent` which is the root component for this example.
|
||||||
|
|
||||||
```
|
```
|
||||||
import {Component, bootstrap, CORE_DIRECTIVES} from 'angular2/angular2';
|
import {Component, bootstrap, CORE_DIRECTIVES} from 'angular2/angular2';
|
||||||
import {HeroService} from './hero.service';
|
import {HeroService} from './hero.service';
|
||||||
import {HeroCardComponent} from './hero-card.component';
|
import {HeroCardComponent} from './hero-card.component';
|
||||||
import {HeroEditorComponent} from './hero-editor.component';
|
import {HeroEditorComponent} from './hero-editor.component';
|
||||||
import {Hero} from './hero';
|
import {Hero} from './hero';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'heroes-list-component',
|
selector: 'heroes-list-component',
|
||||||
template: `
|
template: `
|
||||||
<div>
|
<div>
|
||||||
<ul>
|
<ul>
|
||||||
<li *ng-for="#editItem of heroes">
|
<li *ng-for="#editItem of heroes">
|
||||||
<hero-card-component
|
<hero-card-component
|
||||||
[hidden]="editItem.editing"
|
[hidden]="editItem.editing"
|
||||||
[hero]="editItem.item">
|
[hero]="editItem.item">
|
||||||
</hero-card-component>
|
</hero-card-component>
|
||||||
<button
|
<button
|
||||||
[hidden]="editItem.editing"
|
[hidden]="editItem.editing"
|
||||||
(click)="editItem.editing = true">
|
(click)="editItem.editing = true">
|
||||||
edit
|
edit
|
||||||
</button>
|
</button>
|
||||||
|
@ -130,43 +130,43 @@ figure.image-display
|
||||||
this.heroes = HeroService.getHeroes()
|
this.heroes = HeroService.getHeroes()
|
||||||
.map(item => new EditItem(item));
|
.map(item => new EditItem(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
onSaved (editItem: EditItem<Hero>, updatedHero: Hero) {
|
onSaved (editItem: EditItem<Hero>, updatedHero: Hero) {
|
||||||
editItem.item = updatedHero;
|
editItem.item = updatedHero;
|
||||||
editItem.editing = false;
|
editItem.editing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onCanceled (editItem: EditItem<Hero>) {
|
onCanceled (editItem: EditItem<Hero>) {
|
||||||
editItem.editing = false;
|
editItem.editing = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class EditItem<T> {
|
class EditItem<T> {
|
||||||
item: T;
|
item: T;
|
||||||
editing: boolean
|
editing: boolean
|
||||||
constructor (public item T) {}
|
constructor (public item T) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
bootstrap(HeroesListComponent, [HeroService]);
|
bootstrap(HeroesListComponent, [HeroService]);
|
||||||
```
|
```
|
||||||
|
|
||||||
Notice that it imports the `HeroService` that we’ve used before so we can skip its declaration. The only difference is that we’ve used a more formal approach for our `Hero`model and defined it upfront as such.
|
Notice that it imports the `HeroService` that we’ve used before so we can skip its declaration. The only difference is that we’ve used a more formal approach for our `Hero`model and defined it upfront as such.
|
||||||
|
|
||||||
```
|
```
|
||||||
export class Hero {
|
export class Hero {
|
||||||
name: string;
|
name: string;
|
||||||
power: string;
|
power: string;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Our `HeroesListComponent` defines a template that creates a list of `HeroCardComponents` and `HeroEditorComponents`, each bound to an instance of hero that is returned from the `HeroService`. Ok, that’s not entirely true. It actually binds to an `EditItem<Hero>` which is a simple generic datatype that can wrap any type and indicate if the item being wrapped is currently being edited or not.
|
Our `HeroesListComponent` defines a template that creates a list of `HeroCardComponents` and `HeroEditorComponents`, each bound to an instance of hero that is returned from the `HeroService`. Ok, that’s not entirely true. It actually binds to an `EditItem<Hero>` which is a simple generic datatype that can wrap any type and indicate if the item being wrapped is currently being edited or not.
|
||||||
|
|
||||||
But how is `HeroCardComponent` implemented? Let’s take a look.
|
But how is `HeroCardComponent` implemented? Let’s take a look.
|
||||||
|
|
||||||
```
|
```
|
||||||
import {Component, bootstrap, CORE_DIRECTIVES} from 'angular2/angular2';
|
import {Component, bootstrap, CORE_DIRECTIVES} from 'angular2/angular2';
|
||||||
import {Hero} from './hero';
|
import {Hero} from './hero';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'hero-card.component',
|
selector: 'hero-card.component',
|
||||||
properties: ['hero'],
|
properties: ['hero'],
|
||||||
|
@ -181,16 +181,16 @@ figure.image-display
|
||||||
hero: Hero;
|
hero: Hero;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The `HeroCardComponent` is basically a component that defines a template to render a hero. Nothing more.
|
The `HeroCardComponent` is basically a component that defines a template to render a hero. Nothing more.
|
||||||
|
|
||||||
Let’s get to the interesting part and take a look at the `HeroEditComponent`
|
Let’s get to the interesting part and take a look at the `HeroEditComponent`
|
||||||
|
|
||||||
```
|
```
|
||||||
import {Component, FORM_DIRECTIVES, EventEmitter, bootstrap, CORE_DIRECTIVES} from 'angular2/angular2';
|
import {Component, FORM_DIRECTIVES, EventEmitter, bootstrap, CORE_DIRECTIVES} from 'angular2/angular2';
|
||||||
import {RestoreService} from './restore.service';
|
import {RestoreService} from './restore.service';
|
||||||
import {Hero} from './hero';
|
import {Hero} from './hero';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'hero-editor-component',
|
selector: 'hero-editor-component',
|
||||||
events: ['canceled', 'saved'],
|
events: ['canceled', 'saved'],
|
||||||
|
@ -210,98 +210,98 @@ figure.image-display
|
||||||
export class HeroEditorComponent {
|
export class HeroEditorComponent {
|
||||||
canceled = new EventEmitter();
|
canceled = new EventEmitter();
|
||||||
saved = new EventEmitter();
|
saved = new EventEmitter();
|
||||||
|
|
||||||
constructor(private restoreService: RestoreService<Hero>) {}
|
constructor(private restoreService: RestoreService<Hero>) {}
|
||||||
|
|
||||||
set hero (hero: Hero) {
|
set hero (hero: Hero) {
|
||||||
this.restoreService.setItem(hero);
|
this.restoreService.setItem(hero);
|
||||||
}
|
}
|
||||||
|
|
||||||
get hero () {
|
get hero () {
|
||||||
return this.restoreService.getItem();
|
return this.restoreService.getItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
onSaved () {
|
onSaved () {
|
||||||
this.saved.next(this.restoreService.getItem());
|
this.saved.next(this.restoreService.getItem());
|
||||||
}
|
}
|
||||||
|
|
||||||
onCanceled () {
|
onCanceled () {
|
||||||
this.hero = this.restoreService.restoreItem();
|
this.hero = this.restoreService.restoreItem();
|
||||||
this.canceled.next(this.hero);
|
this.canceled.next(this.hero);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Now here it’s getting interesting. The `HeroEditComponent`defines a template with an input to change the name of the hero and a `cancel` and a `save` button. Remember that we said we want to have the flexibility to cancel our editing and restore the old value? This means we need to maintain two copies of our `Hero` that we want to edit. Thinking ahead this is a perfect use case to abstract it into it’s own generic service since we have probably more cases like this in our app.
|
Now here it’s getting interesting. The `HeroEditComponent`defines a template with an input to change the name of the hero and a `cancel` and a `save` button. Remember that we said we want to have the flexibility to cancel our editing and restore the old value? This means we need to maintain two copies of our `Hero` that we want to edit. Thinking ahead this is a perfect use case to abstract it into it’s own generic service since we have probably more cases like this in our app.
|
||||||
|
|
||||||
And this is where the `RestoreService` enters the stage.
|
And this is where the `RestoreService` enters the stage.
|
||||||
|
|
||||||
```
|
```
|
||||||
export class RestoreService<T> {
|
export class RestoreService<T> {
|
||||||
originalItem: T;
|
originalItem: T;
|
||||||
currentItem: T;
|
currentItem: T;
|
||||||
|
|
||||||
setItem (item: T) {
|
setItem (item: T) {
|
||||||
this.originalItem = item;
|
this.originalItem = item;
|
||||||
this.currentItem = this.clone(item);
|
this.currentItem = this.clone(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
getItem () :T {
|
getItem () :T {
|
||||||
return this.currentItem;
|
return this.currentItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreItem () :T {
|
restoreItem () :T {
|
||||||
this.currentItem = this.originalItem;
|
this.currentItem = this.originalItem;
|
||||||
return this.getItem();
|
return this.getItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
clone (item: T) :T {
|
clone (item: T) :T {
|
||||||
// super poor clone implementation
|
// super poor clone implementation
|
||||||
return JSON.parse(JSON.stringify(item));
|
return JSON.parse(JSON.stringify(item));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
All this tiny service does is define an API to set a value of any type which can be altered, retrieved or set back to it’s initial value. That’s exactly what we need to implement the desired functionality.
|
All this tiny service does is define an API to set a value of any type which can be altered, retrieved or set back to it’s initial value. That’s exactly what we need to implement the desired functionality.
|
||||||
|
|
||||||
Our `HeroEditComponent` uses this services under the hood for it’s `hero` property. It intercepts the `get` and `set` method to delegate the actual work to our `RestoreService` which in turn makes sure that we won’t work on the original item but on a copy instead.
|
Our `HeroEditComponent` uses this services under the hood for it’s `hero` property. It intercepts the `get` and `set` method to delegate the actual work to our `RestoreService` which in turn makes sure that we won’t work on the original item but on a copy instead.
|
||||||
|
|
||||||
At this point we may be scratching our heads asking what this has to do with component injectors? If we look closely at our `HeroEditComponent` we’ll notice this piece of code
|
At this point we may be scratching our heads asking what this has to do with component injectors? If we look closely at our `HeroEditComponent` we’ll notice this piece of code
|
||||||
|
|
||||||
```
|
```
|
||||||
…
|
…
|
||||||
providers: [RestoreService]
|
providers: [RestoreService]
|
||||||
…
|
…
|
||||||
```
|
```
|
||||||
|
|
||||||
This creates a binding for the `RestoreService` in the injector of the `HeroEditComponent`. But couldn’t we simply alter our bootstrap call to this?
|
This creates a binding for the `RestoreService` in the injector of the `HeroEditComponent`. But couldn’t we simply alter our bootstrap call to this?
|
||||||
|
|
||||||
```
|
```
|
||||||
bootstrap(HeroesListComponent, [HeroService, RestoreService]);
|
bootstrap(HeroesListComponent, [HeroService, RestoreService]);
|
||||||
```
|
```
|
||||||
|
|
||||||
Technically we could, but our component wouldn’t quite behave the way it is supposed to. Remember that each injector treats the services that it provides as singletons. However, in order to be able to have multiple instances of `HeroEditComponent` edit multiple heroes at the same time we need to have multiple instances of the `RestoreService`. More specifically each instance of `HeroEditComponent` needs to be bound to it’s own instance of the `RestoreService`.
|
Technically we could, but our component wouldn’t quite behave the way it is supposed to. Remember that each injector treats the services that it provides as singletons. However, in order to be able to have multiple instances of `HeroEditComponent` edit multiple heroes at the same time we need to have multiple instances of the `RestoreService`. More specifically each instance of `HeroEditComponent` needs to be bound to it’s own instance of the `RestoreService`.
|
||||||
|
|
||||||
By configuring a binding for the `RestoreService` on the `HeroEditComponent`, we get exactly one instance of the `RestoreService`per `HeroEditComponent`.
|
By configuring a binding for the `RestoreService` on the `HeroEditComponent`, we get exactly one instance of the `RestoreService`per `HeroEditComponent`.
|
||||||
|
|
||||||
Does that mean that services aren’t singletons anymore in Angular 2? Yes and no.
|
Does that mean that services aren’t singletons anymore in Angular 2? Yes and no.
|
||||||
While there’s only one instance per binding per injector there may be multiple instances of the same type across
|
While there’s only one instance per binding per injector there may be multiple instances of the same type across
|
||||||
the entire application due to the fact that we can create multiple bindings for the same type on different components.
|
the entire application due to the fact that we can create multiple bindings for the same type on different components.
|
||||||
|
|
||||||
If we had only defined a binding for `RestoreService` on the root component,
|
If we had only defined a binding for `RestoreService` on the root component,
|
||||||
we would have exactly one instance of the across the entire applicatoin. That’s clearly not what we want in this scenario.
|
we would have exactly one instance of the across the entire applicatoin. That’s clearly not what we want in this scenario.
|
||||||
We don’t want to share an instance. We want each component to have its own instance of the `RestoreService`.
|
We don’t want to share an instance. We want each component to have its own instance of the `RestoreService`.
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
## Advanced Dependency Injection in Angular 2
|
## Advanced Dependency Injection in Angular 2
|
||||||
|
|
||||||
Restrict Dependency Lookups
|
Restrict Dependency Lookups
|
||||||
[TODO] (@Host) This has been postponed for now until we come up with a decent use case
|
[TODO] (@Host) This has been postponed for now until we come up with a decent use case
|
||||||
|
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Dependency Visibility
|
## Dependency Visibility
|
||||||
|
|
||||||
[TODO] (bindings vs viewBindings) This has been postponed for now until come up with a decent use case
|
[TODO] (bindings vs viewBindings) This has been postponed for now until come up with a decent use case
|
||||||
-->
|
-->
|
||||||
|
|
|
@ -1,110 +1,110 @@
|
||||||
include ../../../../_includes/_util-fns
|
include ../../../../_includes/_util-fns
|
||||||
:markdown
|
:marked
|
||||||
Every application starts out with what seems like a simple task: get data, transform them, and show them to users.
|
Every application starts out with what seems like a simple task: get data, transform them, and show them to users.
|
||||||
|
|
||||||
Getting data could be as simple as creating a local variable or as complex as streaming data over a Websocket.
|
Getting data could be as simple as creating a local variable or as complex as streaming data over a Websocket.
|
||||||
|
|
||||||
Once data arrive, we could push their raw `toString` values directly to screen.
|
Once data arrive, we could push their raw `toString` values directly to screen.
|
||||||
That rarely makes for a good user experience.
|
That rarely makes for a good user experience.
|
||||||
Almost everyone prefers a simple birthday date
|
Almost everyone prefers a simple birthday date
|
||||||
(<span style="font-family:courier">April 15, 1988</span>) to the original raw string format
|
(<span style="font-family:courier">April 15, 1988</span>) to the original raw string format
|
||||||
( <span style="font-family:courier">Fri Apr 15 1988 00:00:00 GMT-0700 (Pacific Daylight Time)</span> ).
|
( <span style="font-family:courier">Fri Apr 15 1988 00:00:00 GMT-0700 (Pacific Daylight Time)</span> ).
|
||||||
|
|
||||||
Clearly some values benefit from a bit of massage. We soon discover that we
|
Clearly some values benefit from a bit of massage. We soon discover that we
|
||||||
desire many of the same transformations repeatedly, both within and across many applications.
|
desire many of the same transformations repeatedly, both within and across many applications.
|
||||||
We almost think of them as styles.
|
We almost think of them as styles.
|
||||||
In fact, we'd like to apply them in our HTML templates as we do styles.
|
In fact, we'd like to apply them in our HTML templates as we do styles.
|
||||||
|
|
||||||
Welcome, Angular pipes, the simple display-value transformations that we can declare in our HTML!
|
Welcome, Angular pipes, the simple display-value transformations that we can declare in our HTML!
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Using Pipes
|
## Using Pipes
|
||||||
|
|
||||||
A pipe takes in data as input and transforms it to a desired output.
|
A pipe takes in data as input and transforms it to a desired output.
|
||||||
We'll illustrate by transforming a component's birthday property into
|
We'll illustrate by transforming a component's birthday property into
|
||||||
a human-friendly date.
|
a human-friendly date.
|
||||||
|
|
||||||
Here's a complete mini-app with a `DatePipe`:
|
Here's a complete mini-app with a `DatePipe`:
|
||||||
<!--
|
<!--
|
||||||
All date samples are in my plunker
|
All date samples are in my plunker
|
||||||
http://plnkr.co/edit/RDlOma?p=preview
|
http://plnkr.co/edit/RDlOma?p=preview
|
||||||
-->
|
-->
|
||||||
|
|
||||||
+makeExample('pipes/ts/src/app/app.ts', 'hero-birthday')
|
+makeExample('pipes/ts/src/app/app.ts', 'hero-birthday')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Focus on the component's template to see how we applied the built-in `DatePipe`
|
Focus on the component's template to see how we applied the built-in `DatePipe`
|
||||||
while binding the `birthday` property.
|
while binding the `birthday` property.
|
||||||
+makeExample('pipes/ts/src/app/app.html', 'hero-birthday-template')(format=".")
|
+makeExample('pipes/ts/src/app/app.html', 'hero-birthday-template')(format=".")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Angular [template syntax](./template-syntax.html#pipe) includes a pipe operator ( | ) which we're
|
Angular [template syntax](./template-syntax.html#pipe) includes a pipe operator ( | ) which we're
|
||||||
using to flow the birthday value on the left through to the `Date` pipe function on the right.
|
using to flow the birthday value on the left through to the `Date` pipe function on the right.
|
||||||
All pipes work this way.
|
All pipes work this way.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Built-in pipes
|
## Built-in pipes
|
||||||
Angular comes with a stock set of pipes such as
|
Angular comes with a stock set of pipes such as
|
||||||
`DatePipe`, `UpperCasePipe`, `LowerCasePipe`, `CurrencyPipe`, and `PercentPipe`.
|
`DatePipe`, `UpperCasePipe`, `LowerCasePipe`, `CurrencyPipe`, and `PercentPipe`.
|
||||||
They are all immediately available for use in any template.
|
They are all immediately available for use in any template.
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
Learn more about these and many other built-in pipes in the the [API Reference](../api/);
|
Learn more about these and many other built-in pipes in the the [API Reference](../api/);
|
||||||
filter for entries that include the word "pipe".
|
filter for entries that include the word "pipe".
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Parameterizing a Pipe
|
## Parameterizing a Pipe
|
||||||
A pipe may accept any number of optional parameters to fine-tune its output.
|
A pipe may accept any number of optional parameters to fine-tune its output.
|
||||||
|
|
||||||
We add parameters to a pipe by following the pipe name with a colon ( : ) and then the parameter value
|
We add parameters to a pipe by following the pipe name with a colon ( : ) and then the parameter value
|
||||||
(e.g., `currency:'EUR'`). If our pipe accepts multiple parameters, we separate the values with colons (e.g. `slice:1:5`)
|
(e.g., `currency:'EUR'`). If our pipe accepts multiple parameters, we separate the values with colons (e.g. `slice:1:5`)
|
||||||
|
|
||||||
We'll modify our birthday example to give the date pipe a format parameter.
|
We'll modify our birthday example to give the date pipe a format parameter.
|
||||||
The formatted date should display as **<span style="font-family:courier">04/15/88</span>**.
|
The formatted date should display as **<span style="font-family:courier">04/15/88</span>**.
|
||||||
|
|
||||||
+makeExample('pipes/ts/src/app/app.html', 'format-birthday')(format=".")
|
+makeExample('pipes/ts/src/app/app.html', 'format-birthday')(format=".")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
The parameter value can be any valid
|
The parameter value can be any valid
|
||||||
[template expression](./template-expression.html#template-expressions)
|
[template expression](./template-expression.html#template-expressions)
|
||||||
such as a string literal or a component property.
|
such as a string literal or a component property.
|
||||||
|
|
||||||
Let's revise our example to bind the pipe's format parameter
|
Let's revise our example to bind the pipe's format parameter
|
||||||
to the component's `format` property.
|
to the component's `format` property.
|
||||||
+makeExample('pipes/ts/src/app/hero-birthday.2.ts', 'hero-birthday2')
|
+makeExample('pipes/ts/src/app/hero-birthday.2.ts', 'hero-birthday2')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
We also added a button to the template and bound its click event to the component's `toggleFormat` method.
|
We also added a button to the template and bound its click event to the component's `toggleFormat` method.
|
||||||
That method toggles the component's `format` property between a short form
|
That method toggles the component's `format` property between a short form
|
||||||
('shortDate') and a longer form ('fullDate').
|
('shortDate') and a longer form ('fullDate').
|
||||||
|
|
||||||
As we click the button, the displayed date alternates between
|
As we click the button, the displayed date alternates between
|
||||||
"**<span style="font-family:courier">04/15/1988</span>**" and
|
"**<span style="font-family:courier">04/15/1988</span>**" and
|
||||||
"**<span style="font-family:courier">Friday, April 15, 1988</span>**".
|
"**<span style="font-family:courier">Friday, April 15, 1988</span>**".
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/pipes/date-format-toggle-anim.gif' alt="Date Format Toggle")
|
img(src='/resources/images/devguide/pipes/date-format-toggle-anim.gif' alt="Date Format Toggle")
|
||||||
:markdown
|
:marked
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
Learn more about the `DatePipes` format options in the [API Docs](../api/core/DatePipe-class.html).
|
Learn more about the `DatePipes` format options in the [API Docs](../api/core/DatePipe-class.html).
|
||||||
:markdown
|
:marked
|
||||||
## Chaining pipes
|
## Chaining pipes
|
||||||
We can chain pipes together in potentially useful combinations.
|
We can chain pipes together in potentially useful combinations.
|
||||||
In the following example, we chain the birthday to the `DatePipe` and on to the `UpperCasePipe`
|
In the following example, we chain the birthday to the `DatePipe` and on to the `UpperCasePipe`
|
||||||
so we can display the birthday in uppercase. The following birthday displays as
|
so we can display the birthday in uppercase. The following birthday displays as
|
||||||
**<span style="font-family:courier">APR 15, 1988</span>**
|
**<span style="font-family:courier">APR 15, 1988</span>**
|
||||||
|
|
||||||
+makeExample('pipes/ts/src/app/app.html', 'chained-birthday')
|
+makeExample('pipes/ts/src/app/app.html', 'chained-birthday')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
If we pass a parameter to a filter, we have to add parentheses
|
If we pass a parameter to a filter, we have to add parentheses
|
||||||
to help the template compiler with the evaluation order.
|
to help the template compiler with the evaluation order.
|
||||||
The following example displays
|
The following example displays
|
||||||
**<span style="font-family:courier">FRIDAY, APRIL 15, 1988</span>**
|
**<span style="font-family:courier">FRIDAY, APRIL 15, 1988</span>**
|
||||||
|
|
||||||
+makeExample('pipes/ts/src/app/app.html', 'chained-parameter-birthday')
|
+makeExample('pipes/ts/src/app/app.html', 'chained-parameter-birthday')
|
||||||
|
|
||||||
|
@ -112,14 +112,14 @@ figure.image-display
|
||||||
p Future improvements in the template compiler may eliminate the need for parentheses.
|
p Future improvements in the template compiler may eliminate the need for parentheses.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Custom Pipes
|
## Custom Pipes
|
||||||
|
|
||||||
We can write our own custom pipes.
|
We can write our own custom pipes.
|
||||||
|
|
||||||
Let's make a custom pipe named `ExponentialStrengthPipe`
|
Let's make a custom pipe named `ExponentialStrengthPipe`
|
||||||
that can boost a hero's powers.
|
that can boost a hero's powers.
|
||||||
|
|
||||||
Create a new file, `exponential-strength-pipe.ts`, and enter the following:
|
Create a new file, `exponential-strength-pipe.ts`, and enter the following:
|
||||||
<!--
|
<!--
|
||||||
The exponential pipe samples are in my plunker
|
The exponential pipe samples are in my plunker
|
||||||
|
@ -127,16 +127,16 @@ figure.image-display
|
||||||
-->
|
-->
|
||||||
+makeExample('pipes/ts/src/app/exponential-strength-pipe.ts')
|
+makeExample('pipes/ts/src/app/exponential-strength-pipe.ts')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
This pipe definition reveals several few key points
|
This pipe definition reveals several few key points
|
||||||
* We import the `Pipe` decorator from the Angular library (while getting the usual symbols)
|
* We import the `Pipe` decorator from the Angular library (while getting the usual symbols)
|
||||||
* A pipe is a class
|
* A pipe is a class
|
||||||
* We decorate the class with the `@Pipe` decorator function.
|
* We decorate the class with the `@Pipe` decorator function.
|
||||||
* The `@Pipe` decorator takes an object with a name property whose value is the
|
* The `@Pipe` decorator takes an object with a name property whose value is the
|
||||||
pipe name that we'll use within a template expression. It must be a valid JavaScript identier.
|
pipe name that we'll use within a template expression. It must be a valid JavaScript identier.
|
||||||
Our pipe's name is `exponenentialStrength`.
|
Our pipe's name is `exponenentialStrength`.
|
||||||
* The pipe class implements a `transform` method
|
* The pipe class implements a `transform` method
|
||||||
* `transform` takes a value and an optional array of strings.
|
* `transform` takes a value and an optional array of strings.
|
||||||
The value can be of any type but the arguments array must be an array of strings.
|
The value can be of any type but the arguments array must be an array of strings.
|
||||||
* There will be one item in the array for each parameter passed to the pipe
|
* There will be one item in the array for each parameter passed to the pipe
|
||||||
* `transform` returns a modified value that Angular converts to a string.
|
* `transform` returns a modified value that Angular converts to a string.
|
||||||
|
@ -146,24 +146,24 @@ figure.image-display
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/pipes/power-booster.png' alt="Power Booster")
|
img(src='/resources/images/devguide/pipes/power-booster.png' alt="Power Booster")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Two things to note:
|
Two things to note:
|
||||||
1. We use the pipe in the template expression exactly as we described in the pipe's comments.
|
1. We use the pipe in the template expression exactly as we described in the pipe's comments.
|
||||||
We pass the value to transform from the left and give our pipe an exponent parameter of `10`.
|
We pass the value to transform from the left and give our pipe an exponent parameter of `10`.
|
||||||
|
|
||||||
1. We must list our pipe in the @Component decorator's `pipes` array.
|
1. We must list our pipe in the @Component decorator's `pipes` array.
|
||||||
|
|
||||||
.callout.is-critical
|
.callout.is-critical
|
||||||
header Remember the pipes array!
|
header Remember the pipes array!
|
||||||
:markdown
|
:marked
|
||||||
Angular reports an error if we neglect to list our custom pipe.
|
Angular reports an error if we neglect to list our custom pipe.
|
||||||
We didn't list the `DatePipe` in our previous example because all
|
We didn't list the `DatePipe` in our previous example because all
|
||||||
Angular built-in pipes are pre-registered.
|
Angular built-in pipes are pre-registered.
|
||||||
Custom pipes must be registered manually.
|
Custom pipes must be registered manually.
|
||||||
:markdown
|
:marked
|
||||||
If we are inclined to try this in a live-coding tool (such a [plunker](http://plnkr.co/)),
|
If we are inclined to try this in a live-coding tool (such a [plunker](http://plnkr.co/)),
|
||||||
we can probe its behavior by changing the value and the optional exponent in the template.
|
we can probe its behavior by changing the value and the optional exponent in the template.
|
||||||
|
|
||||||
## Power Boost Calculator (extra-credit)
|
## Power Boost Calculator (extra-credit)
|
||||||
It's not much fun updating the template to test our custom pipe.
|
It's not much fun updating the template to test our custom pipe.
|
||||||
We could upgrade the example to a "Power Boost Calculator" that combines
|
We could upgrade the example to a "Power Boost Calculator" that combines
|
||||||
|
@ -173,56 +173,56 @@ figure.image-display
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/pipes/power-boost-calculator.png' alt="Power Boost Calculator")
|
img(src='/resources/images/devguide/pipes/power-boost-calculator.png' alt="Power Boost Calculator")
|
||||||
:markdown
|
:marked
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Stateful Pipes
|
## Stateful Pipes
|
||||||
|
|
||||||
There are two categories of pipes, stateless and stateful.
|
There are two categories of pipes, stateless and stateful.
|
||||||
|
|
||||||
Stateless pipes are pure functions that flow input data
|
Stateless pipes are pure functions that flow input data
|
||||||
through without remembering anything or causing detectable side-effects.
|
through without remembering anything or causing detectable side-effects.
|
||||||
|
|
||||||
Most pipes are stateless. The `DatePipe` in our first example is a stateless pipe. So is our custom `ExponentialStrengthPipe`.
|
Most pipes are stateless. The `DatePipe` in our first example is a stateless pipe. So is our custom `ExponentialStrengthPipe`.
|
||||||
|
|
||||||
Stateful pipes are conceptually similar to classes in object-oriented programming. They can manage the data they transform. A pipe that creates an HTTP request, stores the response and displays the output, is a stateful pipe.
|
Stateful pipes are conceptually similar to classes in object-oriented programming. They can manage the data they transform. A pipe that creates an HTTP request, stores the response and displays the output, is a stateful pipe.
|
||||||
Pipes that retrieve or request data should be used cautiously, since working with network data tends to introduce error conditions that are better handled in JavaScript than in a template.
|
Pipes that retrieve or request data should be used cautiously, since working with network data tends to introduce error conditions that are better handled in JavaScript than in a template.
|
||||||
We can mitigate this risk by creating a custom pipe for a particular backend and bake-in the essential error-handling.
|
We can mitigate this risk by creating a custom pipe for a particular backend and bake-in the essential error-handling.
|
||||||
|
|
||||||
## The stateful `AsyncPipe`
|
## The stateful `AsyncPipe`
|
||||||
The Angular Async pipe is a remarkable example of a stateful pipe.
|
The Angular Async pipe is a remarkable example of a stateful pipe.
|
||||||
The Async pipe can receive a Promise or Observable as input
|
The Async pipe can receive a Promise or Observable as input
|
||||||
and subscribe to the input automatically, eventually returning the emitted value(s).
|
and subscribe to the input automatically, eventually returning the emitted value(s).
|
||||||
|
|
||||||
It is stateful because the pipe maintains a subscription to the input and its returned values depend on that subscription.
|
It is stateful because the pipe maintains a subscription to the input and its returned values depend on that subscription.
|
||||||
|
|
||||||
In the next example, we bind a simple promise to a view with the async pipe.
|
In the next example, we bind a simple promise to a view with the async pipe.
|
||||||
+makeExample('pipes/ts/src/app/app.ts', 'async-message')
|
+makeExample('pipes/ts/src/app/app.ts', 'async-message')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
The Async pipe saves boilerplate in the component code.
|
The Async pipe saves boilerplate in the component code.
|
||||||
The component doesn't have to subscribe to the async data source,
|
The component doesn't have to subscribe to the async data source,
|
||||||
it doesn't extract the resolved values and expose them for binding,
|
it doesn't extract the resolved values and expose them for binding,
|
||||||
and (in the case of Obsevable stream sources like `EventEmitter`)
|
and (in the case of Obsevable stream sources like `EventEmitter`)
|
||||||
the component doesn't have to unsubscribe when it is destroyed
|
the component doesn't have to unsubscribe when it is destroyed
|
||||||
(a potent source of memory leaks).
|
(a potent source of memory leaks).
|
||||||
|
|
||||||
### Implementing a Stateful Pipe
|
### Implementing a Stateful Pipe
|
||||||
Pipes are stateless by default.
|
Pipes are stateless by default.
|
||||||
We must declare a pipe to be stateful
|
We must declare a pipe to be stateful
|
||||||
by setting the `pure` property of the `@Pipe` decorator to `false`.
|
by setting the `pure` property of the `@Pipe` decorator to `false`.
|
||||||
This setting tells Angular’s change detection system to
|
This setting tells Angular’s change detection system to
|
||||||
check the output of this pipe each cycle, whether its input has changed or not.
|
check the output of this pipe each cycle, whether its input has changed or not.
|
||||||
|
|
||||||
Here's how we'll decorate our new stateful `FetchJsonPipe` that
|
Here's how we'll decorate our new stateful `FetchJsonPipe` that
|
||||||
makes an HTTP `fetch` request and (eventually) displays the data in the server's response:
|
makes an HTTP `fetch` request and (eventually) displays the data in the server's response:
|
||||||
+makeExample('pipes/ts/src/app/fetch-json-pipe.ts', 'pipe-metadata')
|
+makeExample('pipes/ts/src/app/fetch-json-pipe.ts', 'pipe-metadata')
|
||||||
:markdown
|
:marked
|
||||||
Immediately below we have the finished pipe. Its input value is an url to an endpoint that returns a JSON file.
|
Immediately below we have the finished pipe. Its input value is an url to an endpoint that returns a JSON file.
|
||||||
The pipe makes a one-time async request to the server and eventually receives the JSON response.
|
The pipe makes a one-time async request to the server and eventually receives the JSON response.
|
||||||
+makeExample('pipes/ts/src/app/fetch-json-pipe.ts')
|
+makeExample('pipes/ts/src/app/fetch-json-pipe.ts')
|
||||||
:markdown
|
:marked
|
||||||
Next we use this pipe in two template bindings where we
|
Next we use this pipe in two template bindings where we
|
||||||
1. display hero names in an `ng-for` repeater
|
1. display hero names in an `ng-for` repeater
|
||||||
1. chain the fetched results to the built-in `JsonPipe` that renders
|
1. chain the fetched results to the built-in `JsonPipe` that renders
|
||||||
|
@ -234,13 +234,13 @@ figure.image-display
|
||||||
img(src='/resources/images/devguide/pipes/hero-list.png' alt="Hero List")
|
img(src='/resources/images/devguide/pipes/hero-list.png' alt="Hero List")
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
||||||
Pipes are a great way to encapsulate and share common display-value
|
Pipes are a great way to encapsulate and share common display-value
|
||||||
transformations. We use them like styles, dropping them
|
transformations. We use them like styles, dropping them
|
||||||
into our templates expressions to enrich the appeal and usability
|
into our templates expressions to enrich the appeal and usability
|
||||||
of our views.
|
of our views.
|
||||||
|
|
||||||
Explore Angular's inventory of built-in pipes in the [API Reference](../api/).
|
Explore Angular's inventory of built-in pipes in the [API Reference](../api/).
|
||||||
Try writing a custom pipe and perhaps contributing it to the community.
|
Try writing a custom pipe and perhaps contributing it to the community.
|
|
@ -1,9 +1,9 @@
|
||||||
include ../../../../_includes/_util-fns
|
include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
# App Navigation with the Router
|
# App Navigation with the Router
|
||||||
It's coming soon!
|
It's coming soon!
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## What's not to love?
|
## What's not to love?
|
File diff suppressed because it is too large
Load Diff
|
@ -1,27 +1,27 @@
|
||||||
include ../../../../_includes/_util-fns
|
include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
When the user clicks a link, pushes a button, or types on the keyboard
|
When the user clicks a link, pushes a button, or types on the keyboard
|
||||||
we want to know about it. These user actions all raise DOM events.
|
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.
|
In this chapter we learn to bind to those events using the Angular Event Binding syntax.
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
## Binding to User Input Events
|
## Binding to User Input Events
|
||||||
|
|
||||||
We can listen to [any DOM event](https://developer.mozilla.org/en-US/docs/Web/Events)
|
We can listen to [any DOM event](https://developer.mozilla.org/en-US/docs/Web/Events)
|
||||||
with an [Angular Event Binding](./template-syntax.html#event-binding).
|
with an [Angular Event Binding](./template-syntax.html#event-binding).
|
||||||
|
|
||||||
The syntax is simple. We assign a template expression to the DOM event name, surrounded in parentheses.
|
The syntax is simple. We assign a template expression to the DOM event name, surrounded in parentheses.
|
||||||
A click Event Binding makes for a quick illustration.
|
A click Event Binding makes for a quick illustration.
|
||||||
+makeExample('user-input/ts/src/app/app.html', 'click-me-button')(format=".")
|
+makeExample('user-input/ts/src/app/app.html', 'click-me-button')(format=".")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
The `(click)` to the left of the equal sign identifies the button's click event as the **target of the binding**.
|
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 expression**" in which we
|
The text within quotes on the right is the "**template expression**" in which we
|
||||||
respond to the click event by calling the component's `onClickMe` method. A [template expression](./template-syntax.html#template-expressions) is a subset
|
respond to the click event by calling the component's `onClickMe` method. A [template expression](./template-syntax.html#template-expressions) is a subset
|
||||||
of JavaScript with a few added tricks.
|
of JavaScript with a few added tricks.
|
||||||
|
|
||||||
When writing a binding we must be aware of a template expression's **execution context**.
|
When writing a binding we must be aware of a template expression's **execution context**.
|
||||||
The identifers appearing within an expression belong to a specific context object.
|
The identifers appearing within an expression belong to a specific context object.
|
||||||
That object is usually the Angular component that controls the template ... which it definitely is
|
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:
|
in this case because that snippet of HTML belongs to the following component:
|
||||||
|
@ -29,112 +29,112 @@ include ../../../../_includes/_util-fns
|
||||||
These sample can be found in http://plnkr.co/edit/mr63T5
|
These sample can be found in http://plnkr.co/edit/mr63T5
|
||||||
-->
|
-->
|
||||||
+makeExample('user-input/ts/src/app/app.ts', 'click-me-component')
|
+makeExample('user-input/ts/src/app/app.ts', 'click-me-component')
|
||||||
:markdown
|
:marked
|
||||||
The `onClickMe` in the template refers to the `onClickMe` method of the component.
|
The `onClickMe` in the template refers to the `onClickMe` method of the component.
|
||||||
When the user clicks the button, Angular calls the component's `onClickMe` method.
|
When the user clicks the button, Angular calls the component's `onClickMe` method.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Get user input from the $event object
|
## 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
|
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.
|
what the user types back onto the screen.
|
||||||
|
|
||||||
This time we'll both listen to an event and grab the user's input.
|
This time we'll both listen to an event and grab the user's input.
|
||||||
+makeExample('user-input/ts/src/app/app.ts', 'key-up-component')
|
+makeExample('user-input/ts/src/app/app.ts', 'key-up-component')
|
||||||
:markdown
|
:marked
|
||||||
Angular makes an event object available in the **`$event`** variable. The user data we want is in that variable somewhere.
|
Angular makes an event object available in the **`$event`** variable. The user data we want is in that variable somewhere.
|
||||||
|
|
||||||
The shape of the `$event` object is determined by whatever raises the event.
|
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 `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 the
|
The `$event.target` gives us the
|
||||||
[`HTMLInputElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement) which
|
[`HTMLInputElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement) which
|
||||||
has a `value` property and that's where we find our user input data.
|
has a `value` property and that's where we find our user input data.
|
||||||
|
|
||||||
With had this in mind when we passed `$event` to our `onKey()` component method. That method extracts the user's input and
|
With had this in mind when we passed `$event` to our `onKey()` component method. That method extracts the user's input and
|
||||||
concatenates it to the previous user data that we're accumulating in the component's' `values` property.
|
concatenates it to the previous user data that we're accumulating in the component's' `values` property.
|
||||||
We then use [interpolation](./template-syntax.html#interpolation)
|
We then use [interpolation](./template-syntax.html#interpolation)
|
||||||
to display the `values` property back on screen.
|
to display the `values` property back on screen.
|
||||||
|
|
||||||
Enter the letters "abc", backspace to remove them, and we should see:
|
Enter the letters "abc", backspace to remove them, and we should see:
|
||||||
code-example().
|
code-example().
|
||||||
a | ab | abc | ab | a | |
|
a | ab | abc | ab | a | |
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/user-input/keyup1-anim.gif' alt="key up 1")
|
img(src='/resources/images/devguide/user-input/keyup1-anim.gif' alt="key up 1")
|
||||||
:markdown
|
:marked
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Get user input from a local template variable
|
## Get user input from a local template variable
|
||||||
There's another way to get the user data without the `$event` variable.
|
There's another way to get the user data without the `$event` variable.
|
||||||
|
|
||||||
Angular has syntax feature called [**local template variables**](./template-syntax.html#local-vars).
|
Angular has syntax feature called [**local template variables**](./template-syntax.html#local-vars).
|
||||||
These variables grant us direct access to an element.
|
These variables grant us direct access to an element.
|
||||||
We declare a local template variable by preceding an identifier with a hash/pound character (#).
|
We declare a local template variable by preceding an identifier with a hash/pound character (#).
|
||||||
|
|
||||||
Let's demonstrate with a clever keystroke loopback in a single line of template HTML.
|
Let's demonstrate with a clever keystroke loopback in a single line of template HTML.
|
||||||
We don't actually need a dedicated component to do this but we'll make one anyway.
|
We don't actually need a dedicated component to do this but we'll make one anyway.
|
||||||
+makeExample('user-input/ts/src/app/app.ts', 'loop-back-component')
|
+makeExample('user-input/ts/src/app/app.ts', 'loop-back-component')
|
||||||
:markdown
|
:marked
|
||||||
We've declared a template local variable named `box` on the `<input>` element.
|
We've declared a template local variable named `box` on the `<input>` element.
|
||||||
The `box` variable is a reference to the `<input>` element itself which means we can
|
The `box` variable is a reference to the `<input>` element itself which means we can
|
||||||
grab the input element's `value` and display it
|
grab the input element's `value` and display it
|
||||||
with interpolation between `<p>` tags. The display updates as we type. *Voila!*
|
with interpolation between `<p>` tags. The display updates as we type. *Voila!*
|
||||||
|
|
||||||
**This won't work at all unless we bind to an event**. Angular only updates the bindings
|
**This won't work at all unless we bind to an event**. Angular only updates the bindings
|
||||||
(and therefore the screen)
|
(and therefore the screen)
|
||||||
if we do something in response to asynchronous events such as keystrokes.
|
if we do something in response to asynchronous events such as keystrokes.
|
||||||
|
|
||||||
In this silly example we aren't really interested in the event at all.
|
In this silly example we aren't really interested in the event at all.
|
||||||
But an Event Binding requires a template expression to evaluate when the event fires.
|
But an Event Binding requires a template expression to evaluate when the event fires.
|
||||||
Many things qualify as expressions, none simpler than a one-character literal
|
Many things qualify as expressions, none simpler than a one-character literal
|
||||||
like the number zero. That's all it takes to keep Angular happy. We said it would be clever!
|
like the number zero. That's all it takes to keep Angular happy. We said it would be clever!
|
||||||
|
|
||||||
That local template variable is intriguing. It's clearly easer to get to the textbox with that
|
That local template variable is intriguing. It's clearly easer to get to the textbox with that
|
||||||
variable than to go through the `$event` object. Maybe we can re-write our previous
|
variable than to go through the `$event` object. Maybe we can re-write our previous
|
||||||
"key-up" example using the variable to acquire the user's' input. Let's give it a try.
|
"key-up" example using the variable to acquire the user's' input. Let's give it a try.
|
||||||
+makeExample('user-input/ts/src/app/app.ts', 'key-up2-component')
|
+makeExample('user-input/ts/src/app/app.ts', 'key-up2-component')
|
||||||
:markdown
|
:marked
|
||||||
That sure seems easier.
|
That sure seems easier.
|
||||||
An especially nice aspect of this approach is that our component code gets clean data values from the view.
|
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.
|
It no longer requires knowledge of the `$event` and its structure.
|
||||||
|
|
||||||
<a id="key-event"></a>
|
<a id="key-event"></a>
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Key event filtering (with `key.enter`)
|
## Key event filtering (with `key.enter`)
|
||||||
Perhaps we don't care about every keystroke.
|
Perhaps we don't care about every keystroke.
|
||||||
We're only interested in the input box value when the user hits the "Enter" key. We'd like to ignore all other keys.
|
We're only interested in the input box value when the user hits the "Enter" key. We'd like to ignore all other keys.
|
||||||
When we bind to the `(keyup)` event, our event handling expression hears *every key stroke*.
|
When we bind to the `(keyup)` event, our event handling expression hears *every key stroke*.
|
||||||
We could filter the keys first, examining every `$event.keyCode`, and update the `values` property only if the key is "Enter".
|
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.
|
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.
|
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 ...
|
Only then do we update the component's `values` property ...
|
||||||
inside the event expression rather than in the component ...
|
inside the event expression rather than in the component ...
|
||||||
because we can ... even if it is a dubious practice.
|
because we can ... even if it is a dubious practice.
|
||||||
+makeExample('user-input/ts/src/app/app.ts', 'key-up3-component')
|
+makeExample('user-input/ts/src/app/app.ts', 'key-up3-component')
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/user-input/keyup3-anim.gif' alt="key up 3")
|
img(src='/resources/images/devguide/user-input/keyup3-anim.gif' alt="key up 3")
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## On blur
|
## On blur
|
||||||
|
|
||||||
Our previous example won't transfer the current state of the input box if the user mouses away and clicks
|
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 only update the component's `values` property when the user presses "Enter"
|
elsewhere on the page. We only update the component's `values` property when the user presses "Enter"
|
||||||
inside the input box.
|
inside the input box.
|
||||||
|
|
||||||
Let's fix that by listening to the input box's blur event as well.
|
Let's fix that by listening to the input box's blur event as well.
|
||||||
|
|
||||||
+makeExample('user-input/ts/src/app/app.ts', 'key-up4-component')
|
+makeExample('user-input/ts/src/app/app.ts', 'key-up4-component')
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Put it all together
|
## Put it all together
|
||||||
We learned how to [display data](./displaying-data.html) in the previous chapter.
|
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.
|
We've acquired a small arsenal of event binding techniques in this chapter.
|
||||||
|
|
||||||
Let's put it all together in a micro-app
|
Let's put it all together in a micro-app
|
||||||
that can display a list of heroes and add new heroes to that list
|
that can display a list of heroes and add new heroes to that list
|
||||||
by typing in the input box and hitting "Enter", clicking "Add", or clicking
|
by typing in the input box and hitting "Enter", clicking "Add", or clicking
|
||||||
|
@ -142,24 +142,24 @@ figure.image-display
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/user-input/little-tour-anim.gif' alt="Little Tour of Heroes")
|
img(src='/resources/images/devguide/user-input/little-tour-anim.gif' alt="Little Tour of Heroes")
|
||||||
:markdown
|
:marked
|
||||||
Below is the entire "Little Tour of Heroes" micro-app in a single component.
|
Below is the entire "Little Tour of Heroes" micro-app in a single component.
|
||||||
We'll call out the highlights after we bask briefly in its minimalist glory.
|
We'll call out the highlights after we bask briefly in its minimalist glory.
|
||||||
<!--
|
<!--
|
||||||
This example in http://plnkr.co/edit/JWeIqq
|
This example in http://plnkr.co/edit/JWeIqq
|
||||||
-->
|
-->
|
||||||
+makeExample('user-input/ts/src/app/app.ts', 'little-tour-of-heroes-app')
|
+makeExample('user-input/ts/src/app/app.ts', 'little-tour-of-heroes-app')
|
||||||
:markdown
|
:marked
|
||||||
We've seen almost everything here before. A few things are new or bear repeating.
|
We've seen almost everything here before. A few things are new or bear repeating.
|
||||||
|
|
||||||
### **Beware of camelCase variable names**
|
### **Beware of camelCase variable names**
|
||||||
We enter new hero names in the `<input>` element so we chose `newHero` to be the name of the local template variable.
|
We enter new hero names in the `<input>` element so we chose `newHero` to be the name of the local template variable.
|
||||||
|
|
||||||
Unfortunately, we can't use that name when we declare the variable with (#).
|
Unfortunately, we can't use that name when we declare the variable with (#).
|
||||||
The browser forces all attribute and element names to lowercase, turning what would be `#newHero`
|
The browser forces all attribute and element names to lowercase, turning what would be `#newHero`
|
||||||
into `#newhero` (all lowercase). We don't want a `newhero` variable name in our template expressions.
|
into `#newhero` (all lowercase). We don't want a `newhero` variable name in our template expressions.
|
||||||
|
|
||||||
The Angular workaround is to spell the declaration in "snake case". Angular translates "#new-hero"
|
The Angular workaround is to spell the declaration in "snake case". Angular translates "#new-hero"
|
||||||
to `newHero` for template expressions ... which is exactly what we want.
|
to `newHero` for template expressions ... which is exactly what we want.
|
||||||
|
|
||||||
### **newHero refers to the `<input>` element**
|
### **newHero refers to the `<input>` element**
|
||||||
|
@ -171,27 +171,26 @@ figure.image-display
|
||||||
|
|
||||||
Ready access to the `<input>` element also makes it easy for the `addHero` method
|
Ready access to the `<input>` element also makes it easy for the `addHero` method
|
||||||
to clear the textbox after processing the new hero.
|
to clear the textbox after processing the new hero.
|
||||||
|
|
||||||
### **The *ng-for repeater**
|
### **The *ng-for repeater**
|
||||||
The `ng-for` directive repeats the template as many times as there are heroes in the `heroes` list.
|
The `ng-for` directive repeats the template as many times as there are heroes in the `heroes` list.
|
||||||
We must remember to list `NgFor` among the directives used by the component's template
|
We must remember to list `NgFor` among the directives used by the component's template
|
||||||
by importing the `CORE_DIRECTIVES` constant and adding it to the
|
by importing the `CORE_DIRECTIVES` constant and adding it to the
|
||||||
@Component decorator's `directives` array.
|
@Component decorator's `directives` array.
|
||||||
|
|
||||||
We learned about `NgFor` in the "[Displaying Data](./displaying-data.html)" chapter.
|
We learned about `NgFor` in the "[Displaying Data](./displaying-data.html)" chapter.
|
||||||
|
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
||||||
We've mastered the basic primitives for responding to user input and gestures.
|
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
|
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
|
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.
|
we should be writing two-way bindings between data entry fields and model properties.
|
||||||
|
|
||||||
Angular has a two-way binding called `NgModel` and we learn about it
|
Angular has a two-way binding called `NgModel` and we learn about it
|
||||||
in the `Forms` chapter.
|
in the `Forms` chapter.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
include ../../../_includes/_util-fns
|
include ../../../_includes/_util-fns
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Let's start from zero and build a super simple Angular 2 application in TypeScript.
|
Let's start from zero and build a super simple Angular 2 application in TypeScript.
|
||||||
|
|
||||||
.callout.is-helpful
|
.callout.is-helpful
|
||||||
header Don't want TypeScript?
|
header Don't want TypeScript?
|
||||||
:markdown
|
:marked
|
||||||
Although we're getting started in TypeScript, you can also write Angular 2 apps
|
Although we're getting started in TypeScript, you can also write Angular 2 apps
|
||||||
in JavaScript and Dart by selecting either of those languages from the combo-box in the banner.
|
in JavaScript and Dart by selecting either of those languages from the combo-box in the banner.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## The shortest, quickest ...
|
## The shortest, quickest ...
|
||||||
|
|
||||||
Let's put something on the screen in Angular 2 as quickly as we can.
|
Let's put something on the screen in Angular 2 as quickly as we can.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
While we are about to describe steps to take on your development machine,
|
While we are about to describe steps to take on your development machine,
|
||||||
you could take these same steps in an interactive, online coding environment
|
you could take these same steps in an interactive, online coding environment
|
||||||
such as [plunker](http://plnkr.co/ "Plunker"). You won't have to
|
such as [plunker](http://plnkr.co/ "Plunker"). You won't have to
|
||||||
|
@ -25,21 +25,21 @@ include ../../../_includes/_util-fns
|
||||||
If you like what you see - and we think you will - you can repeat this
|
If you like what you see - and we think you will - you can repeat this
|
||||||
exercise on your own machine later.
|
exercise on your own machine later.
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
**Create a new folder** to hold our application project, perhaps like this:
|
**Create a new folder** to hold our application project, perhaps like this:
|
||||||
```
|
```
|
||||||
mkdir angular2-quickstart
|
mkdir angular2-quickstart
|
||||||
cd angular2-quickstart
|
cd angular2-quickstart
|
||||||
```
|
```
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Our first Angular component
|
## Our first Angular component
|
||||||
|
|
||||||
**Add a new file** called **`app.ts`** and paste the following lines:
|
**Add a new file** called **`app.ts`** and paste the following lines:
|
||||||
|
|
||||||
+makeExample('quickstart/ts/src/app/app.ts', null, 'app.ts')
|
+makeExample('quickstart/ts/src/app/app.ts', null, 'app.ts')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
We've just defined an Angular 2 **component**,
|
We've just defined an Angular 2 **component**,
|
||||||
one of the most important Angular 2 features.
|
one of the most important Angular 2 features.
|
||||||
Components are our primary means of creating application views
|
Components are our primary means of creating application views
|
||||||
|
@ -52,12 +52,12 @@ include ../../../_includes/_util-fns
|
||||||
Above the class we see the `@Component` decoration.
|
Above the class we see the `@Component` decoration.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
The `@` symbol before the method name identifies `Component` as a decoration.
|
The `@` symbol before the method name identifies `Component` as a decoration.
|
||||||
A "decoration" is a TypeScript language feature
|
A "decoration" is a TypeScript language feature
|
||||||
for creating metadata about the class. Angular finds this metadata
|
for creating metadata about the class. Angular finds this metadata
|
||||||
in the transpiled JavaScript and responds appropriately.
|
in the transpiled JavaScript and responds appropriately.
|
||||||
:markdown
|
:marked
|
||||||
`@Component` tells Angular that this class *is an Angular component*.
|
`@Component` tells Angular that this class *is an Angular component*.
|
||||||
The configuration object passed to the `@Component` method has two
|
The configuration object passed to the `@Component` method has two
|
||||||
field, a `selector` and a `template`.
|
field, a `selector` and a `template`.
|
||||||
|
@ -81,7 +81,7 @@ include ../../../_includes/_util-fns
|
||||||
We `import` exactly what we need, as we need it, from named file and library resources.
|
We `import` exactly what we need, as we need it, from named file and library resources.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Add `index.html`
|
## Add `index.html`
|
||||||
|
|
||||||
**Create** an `index.html` file.
|
**Create** an `index.html` file.
|
||||||
|
@ -90,7 +90,7 @@ include ../../../_includes/_util-fns
|
||||||
|
|
||||||
+makeExample('quickstart/ts/src/index.1.html', null, 'index.html')
|
+makeExample('quickstart/ts/src/index.1.html', null, 'index.html')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
We see three noteworthy sections of HTML:
|
We see three noteworthy sections of HTML:
|
||||||
|
|
||||||
1. We load JavaScript libraries from the web.
|
1. We load JavaScript libraries from the web.
|
||||||
|
@ -110,17 +110,17 @@ include ../../../_includes/_util-fns
|
||||||
adorning our `AppComponent` class.
|
adorning our `AppComponent` class.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Run it!
|
## Run it!
|
||||||
|
|
||||||
We need a static file server to serve our application to the browser.
|
We need a static file server to serve our application to the browser.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
Don't have a static file server handy? Let's install one of our favorites
|
Don't have a static file server handy? Let's install one of our favorites
|
||||||
called [live-server](https://www.npmjs.com/package/live-server "Live-server")
|
called [live-server](https://www.npmjs.com/package/live-server "Live-server")
|
||||||
with the **npm package manager**.
|
with the **npm package manager**.
|
||||||
|
|
||||||
Don't have npm?
|
Don't have npm?
|
||||||
[Get it now](https://docs.npmjs.com/getting-started/installing-node "Installing Node.js and updating npm")
|
[Get it now](https://docs.npmjs.com/getting-started/installing-node "Installing Node.js and updating npm")
|
||||||
because we're going to use it now and repeatedly throughout this documentation.
|
because we're going to use it now and repeatedly throughout this documentation.
|
||||||
|
@ -130,27 +130,27 @@ include ../../../_includes/_util-fns
|
||||||
pre.prettyprint.lang-bash
|
pre.prettyprint.lang-bash
|
||||||
code npm install -g live-server
|
code npm install -g live-server
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Open a terminal window and enter
|
Open a terminal window and enter
|
||||||
|
|
||||||
pre.prettyprint.lang-bash
|
pre.prettyprint.lang-bash
|
||||||
code live-server
|
code live-server
|
||||||
:markdown
|
:marked
|
||||||
In a few moments, a browser tab should open and display
|
In a few moments, a browser tab should open and display
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/quickstart/my-first-app.png' alt="Output of quickstart app")
|
img(src='/resources/images/devguide/quickstart/my-first-app.png' alt="Output of quickstart app")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Congratulations! We are in business.
|
Congratulations! We are in business.
|
||||||
|
|
||||||
.alert.is-helpful
|
.alert.is-helpful
|
||||||
:markdown
|
:marked
|
||||||
If you see `Loading...` displayed instead, see the
|
If you see `Loading...` displayed instead, see the
|
||||||
[Browser ES6 support appendix](#es6support).
|
[Browser ES6 support appendix](#es6support).
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## What's wrong with this?
|
## What's wrong with this?
|
||||||
|
|
||||||
We were up and running in a hurry and we could explore Angular
|
We were up and running in a hurry and we could explore Angular
|
||||||
|
@ -177,7 +177,7 @@ include ../../../_includes/_util-fns
|
||||||
We have tools and procedures for that.
|
We have tools and procedures for that.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Upping our game
|
## Upping our game
|
||||||
Let's take a few more steps to put our development on a better foundation. We will
|
Let's take a few more steps to put our development on a better foundation. We will
|
||||||
|
|
||||||
|
@ -189,7 +189,7 @@ include ../../../_includes/_util-fns
|
||||||
Shut down the `live-server` running in the terminal window (Ctrl-C) and proceed as follows.
|
Shut down the `live-server` running in the terminal window (Ctrl-C) and proceed as follows.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Revise the application project structure
|
## Revise the application project structure
|
||||||
|
|
||||||
At the moment we're dumping everything into the "angular2-quickstart" **root folder**.
|
At the moment we're dumping everything into the "angular2-quickstart" **root folder**.
|
||||||
|
@ -205,13 +205,13 @@ include ../../../_includes/_util-fns
|
||||||
pre.prettyprint.lang-bash
|
pre.prettyprint.lang-bash
|
||||||
code mkdir src/app
|
code mkdir src/app
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
In Windows:
|
In Windows:
|
||||||
|
|
||||||
pre.prettyprint.lang-bash
|
pre.prettyprint.lang-bash
|
||||||
code mkdir src\app
|
code mkdir src\app
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
**Move `index.html`** into the **`src`** folder.
|
**Move `index.html`** into the **`src`** folder.
|
||||||
|
|
||||||
**Move `app.ts`** into the **`src/app`** folder.
|
**Move `app.ts`** into the **`src/app`** folder.
|
||||||
|
@ -226,7 +226,7 @@ include ../../../_includes/_util-fns
|
||||||
```
|
```
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Install npm packages locally
|
## Install npm packages locally
|
||||||
|
|
||||||
We'll replace the web-based scripts in our `index.html` with
|
We'll replace the web-based scripts in our `index.html` with
|
||||||
|
@ -261,17 +261,17 @@ include ../../../_includes/_util-fns
|
||||||
|
|
||||||
+makeJson('quickstart/ts/package.json', { paths: 'name, version, dependencies, devDependencies'})
|
+makeJson('quickstart/ts/package.json', { paths: 'name, version, dependencies, devDependencies'})
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
There is also a `scripts` section. **Find and replace** it with the following:
|
There is also a `scripts` section. **Find and replace** it with the following:
|
||||||
|
|
||||||
+makeJson('quickstart/ts/package.json', { paths: 'scripts'})
|
+makeJson('quickstart/ts/package.json', { paths: 'scripts'})
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
We've just extended our project world with script commands
|
We've just extended our project world with script commands
|
||||||
that we'll be running very soon.
|
that we'll be running very soon.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Update `index.html`
|
## Update `index.html`
|
||||||
|
|
||||||
**Replace** the library scripts section with references to
|
**Replace** the library scripts section with references to
|
||||||
|
@ -279,13 +279,13 @@ include ../../../_includes/_util-fns
|
||||||
|
|
||||||
+makeExample('quickstart/ts/src/index.html', 'libraries')
|
+makeExample('quickstart/ts/src/index.html', 'libraries')
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
**Update** the `System` configuration script as follows.
|
**Update** the `System` configuration script as follows.
|
||||||
|
|
||||||
+makeExample('quickstart/ts/src/index.html', 'systemjs')
|
+makeExample('quickstart/ts/src/index.html', 'systemjs')
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
We won't be transpiling TypeScript in the browser anymore.
|
We won't be transpiling TypeScript in the browser anymore.
|
||||||
We'll do that on our machine and ship the generated JavaScript
|
We'll do that on our machine and ship the generated JavaScript
|
||||||
files to the server.
|
files to the server.
|
||||||
|
@ -297,19 +297,19 @@ include ../../../_includes/_util-fns
|
||||||
|
|
||||||
pre.prettyprint.lang-bash
|
pre.prettyprint.lang-bash
|
||||||
code import {Foo} from './app/foo'
|
code import {Foo} from './app/foo'
|
||||||
:markdown
|
:marked
|
||||||
`system.js`will know to look for a file named `foo.js` in the `src/app` folder.
|
`system.js`will know to look for a file named `foo.js` in the `src/app` folder.
|
||||||
|
|
||||||
That's exactly what we're doing in the last line. We're
|
That's exactly what we're doing in the last line. We're
|
||||||
importing our main application file `app` (the generated `app.js` to be precise)
|
importing our main application file `app` (the generated `app.js` to be precise)
|
||||||
from the `src/app/` folder (we moved it there, remember?)
|
from the `src/app/` folder (we moved it there, remember?)
|
||||||
:markdown
|
:marked
|
||||||
Here's the final version
|
Here's the final version
|
||||||
|
|
||||||
+makeExample('quickstart/ts/src/index.html', null, 'index.html')
|
+makeExample('quickstart/ts/src/index.html', null, 'index.html')
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Prepare for TypeScript Compilation
|
## Prepare for TypeScript Compilation
|
||||||
|
|
||||||
### Add the TypeScript configuration file
|
### Add the TypeScript configuration file
|
||||||
|
@ -322,12 +322,12 @@ include ../../../_includes/_util-fns
|
||||||
+makeJson('quickstart/ts/src/tsconfig.json', null, 'tsconfig.json')
|
+makeJson('quickstart/ts/src/tsconfig.json', null, 'tsconfig.json')
|
||||||
|
|
||||||
.alert.is-helpful
|
.alert.is-helpful
|
||||||
:markdown
|
:marked
|
||||||
See the [TypeScript configuration appendix](#tsconfig) to learn more about
|
See the [TypeScript configuration appendix](#tsconfig) to learn more about
|
||||||
this file and these settings.
|
this file and these settings.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Final structure
|
## Final structure
|
||||||
Our final project folder structure should look like this:
|
Our final project folder structure should look like this:
|
||||||
```
|
```
|
||||||
|
@ -342,7 +342,7 @@ include ../../../_includes/_util-fns
|
||||||
```
|
```
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Compile the TypeScript to JavaScript
|
## Compile the TypeScript to JavaScript
|
||||||
|
|
||||||
We no longer transpile TypeScript to JavaScript in the browser.
|
We no longer transpile TypeScript to JavaScript in the browser.
|
||||||
|
@ -353,7 +353,7 @@ include ../../../_includes/_util-fns
|
||||||
pre.prettyprint.lang-bash
|
pre.prettyprint.lang-bash
|
||||||
code npm run tsc
|
code npm run tsc
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
When it's done we should find the generated *app.js* file in the *src* folder and also an *app.map.js* file that
|
When it's done we should find the generated *app.js* file in the *src* folder and also an *app.map.js* file that
|
||||||
helps debuggers navigate between the JavaScript and the TypeScript source.
|
helps debuggers navigate between the JavaScript and the TypeScript source.
|
||||||
|
|
||||||
|
@ -366,7 +366,7 @@ include ../../../_includes/_util-fns
|
||||||
You can stop it anytime with `Ctrl-C`.
|
You can stop it anytime with `Ctrl-C`.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Run the app!
|
## Run the app!
|
||||||
|
|
||||||
Now we are ready to see our app in action.
|
Now we are ready to see our app in action.
|
||||||
|
@ -378,14 +378,14 @@ include ../../../_includes/_util-fns
|
||||||
pre.prettyprint.lang-bash
|
pre.prettyprint.lang-bash
|
||||||
code npm start
|
code npm start
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
**live-server** loads the browser for us, serves the HTML and JavaScript files,
|
**live-server** loads the browser for us, serves the HTML and JavaScript files,
|
||||||
and displays our application message once more:
|
and displays our application message once more:
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/quickstart/my-first-app.png' alt="Output of quickstart app")
|
img(src='/resources/images/devguide/quickstart/my-first-app.png' alt="Output of quickstart app")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
### Make some changes
|
### Make some changes
|
||||||
**`live-server`** detects changes to our files and refreshes the browser page for us automatically.
|
**`live-server`** detects changes to our files and refreshes the browser page for us automatically.
|
||||||
|
|
||||||
|
@ -397,7 +397,7 @@ include ../../../_includes/_util-fns
|
||||||
Keep `live-server` running in this terminal window. You can stop it anytime with `Ctrl-C`.
|
Keep `live-server` running in this terminal window. You can stop it anytime with `Ctrl-C`.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## What have we done?
|
## What have we done?
|
||||||
Our first application doesn't do much. It's basically "Hello, World" for Angular 2.
|
Our first application doesn't do much. It's basically "Hello, World" for Angular 2.
|
||||||
|
|
||||||
|
@ -428,7 +428,7 @@ include ../../../_includes/_util-fns
|
||||||
|
|
||||||
<!-- Move this to the Style Guide when we have one -->
|
<!-- Move this to the Style Guide when we have one -->
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
<a id="tsconfig"></a>
|
<a id="tsconfig"></a>
|
||||||
### Appendix: TypeScript configuration
|
### Appendix: TypeScript configuration
|
||||||
We added a TypeScript configuration file (`tsconfig.js`) to our project to
|
We added a TypeScript configuration file (`tsconfig.js`) to our project to
|
||||||
|
@ -463,26 +463,26 @@ include ../../../_includes/_util-fns
|
||||||
```
|
```
|
||||||
"suppressImplicitAnyIndexErrors":true
|
"suppressImplicitAnyIndexErrors":true
|
||||||
```
|
```
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
<a id="es6support"></a>
|
<a id="es6support"></a>
|
||||||
### Appendix: Browser ES6 support
|
### Appendix: Browser ES6 support
|
||||||
Angular 2 requires ES6 support, such as can be found in most modern
|
Angular 2 requires ES6 support, such as can be found in most modern
|
||||||
browsers. For older browsers (including IE 11) you can use a shim to get
|
browsers. For older browsers (including IE 11) you can use a shim to get
|
||||||
the needed functionality.
|
the needed functionality.
|
||||||
|
|
||||||
After creating `package.json` (halfway through the quickguide), run this
|
After creating `package.json` (halfway through the quickguide), run this
|
||||||
command to add a shim to the project:
|
command to add a shim to the project:
|
||||||
|
|
||||||
code-example(language="sh" format=".").
|
code-example(language="sh" format=".").
|
||||||
npm install es6-shim --save
|
npm install es6-shim --save
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Now you can load the shim in your `index.html` before the other scripts:
|
Now you can load the shim in your `index.html` before the other scripts:
|
||||||
|
|
||||||
code-example(language="html" format=".").
|
code-example(language="html" format=".").
|
||||||
<script src="../node_modules/es6-shim/es6-shim.js"></script>
|
<script src="../node_modules/es6-shim/es6-shim.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
include ../../../../_includes/_util-fns
|
include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
We’ll need an Angular application to test, one as simple as possible while having all the angular features we want to test.
|
We’ll need an Angular application to test, one as simple as possible while having all the angular features we want to test.
|
||||||
|
|
||||||
<!-- TODO We have such an app that you can download [here](./#). -->It’s a one-screen variation on the “Tour of Heroes” that should be familiar to you as a reader of this Developers Guide.
|
<!-- TODO We have such an app that you can download [here](./#). -->It’s a one-screen variation on the “Tour of Heroes” that should be familiar to you as a reader of this Developers Guide.
|
||||||
|
@ -11,7 +11,7 @@ figure.image-display
|
||||||
img(src='/resources/images/devguide/application-under-test/bongos-heroes.png'
|
img(src='/resources/images/devguide/application-under-test/bongos-heroes.png'
|
||||||
style="width:400px;" alt="Bongo's Heroes")
|
style="width:400px;" alt="Bongo's Heroes")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
At the top is a master list of heroes; at the bottom the detail for the current hero. Click a hero in the list to change the current hero. Change the name in the textbox and that name updates everywhere. The *Update* button modifies the `Hero.name` in an arbitrary way and that change also propagates everywhere on screen. The *Delete* button deletes the hero from the list and a new hero becomes current. *Refresh* clears both the list and detail, then restores the original list of heroes.
|
At the top is a master list of heroes; at the bottom the detail for the current hero. Click a hero in the list to change the current hero. Change the name in the textbox and that name updates everywhere. The *Update* button modifies the `Hero.name` in an arbitrary way and that change also propagates everywhere on screen. The *Delete* button deletes the hero from the list and a new hero becomes current. *Refresh* clears both the list and detail, then restores the original list of heroes.
|
||||||
|
|
||||||
<!-- TODO You can see a short video of the app in action [here](./#) -->
|
<!-- TODO You can see a short video of the app in action [here](./#) -->
|
||||||
|
@ -33,6 +33,6 @@ figure.image-display
|
||||||
We’ll examine the implementation details as we evolve our tests.
|
We’ll examine the implementation details as we evolve our tests.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## What’s Next?
|
## What’s Next?
|
||||||
Now that we’re familiar with how the test app works, we’re ready to poke at it with our first application tests written in Jasmine.
|
Now that we’re familiar with how the test app works, we’re ready to poke at it with our first application tests written in Jasmine.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
include ../../../../_includes/_util-fns
|
include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
In this chapter we’ll setup the environment for testing our sample application and write a few easy Jasmine tests of the app’s simplest parts.
|
In this chapter we’ll setup the environment for testing our sample application and write a few easy Jasmine tests of the app’s simplest parts.
|
||||||
We'll learn:
|
We'll learn:
|
||||||
- to test one of our application classes
|
- to test one of our application classes
|
||||||
- why we prefer our test files to be next to their corresponding source files
|
- why we prefer our test files to be next to their corresponding source files
|
||||||
|
@ -10,7 +10,7 @@ include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
.callout.is-helpful
|
.callout.is-helpful
|
||||||
header Prior Knowledge
|
header Prior Knowledge
|
||||||
:markdown
|
:marked
|
||||||
The Unit Testing chapters build upon each other. We recommend reading them in order.
|
The Unit Testing chapters build upon each other. We recommend reading them in order.
|
||||||
We're also assuming that you're already comfortable with basic Angular 2 concepts and the tools
|
We're also assuming that you're already comfortable with basic Angular 2 concepts and the tools
|
||||||
we introduced in the [QuickStart](../quickstart.html) and
|
we introduced in the [QuickStart](../quickstart.html) and
|
||||||
|
@ -18,7 +18,7 @@ include ../../../../_includes/_util-fns
|
||||||
such as <code>npm</code>, <code>gulp</code>, and <code>live-server</code>.
|
such as <code>npm</code>, <code>gulp</code>, and <code>live-server</code>.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Create the test-runner HTML
|
## Create the test-runner HTML
|
||||||
|
|
||||||
Step away from the Jasmine 101 folder and turn to the root folder of the application that we downloaded in the previous chapter.
|
Step away from the Jasmine 101 folder and turn to the root folder of the application that we downloaded in the previous chapter.
|
||||||
|
@ -47,7 +47,7 @@ include ../../../../_includes/_util-fns
|
||||||
We’re picking up right where we left off. All we’ve done is change the title.
|
We’re picking up right where we left off. All we’ve done is change the title.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Update `package.json` for testing
|
## Update `package.json` for testing
|
||||||
|
|
||||||
We’ll assume that the application has `package.json` file that looks more or less like
|
We’ll assume that the application has `package.json` file that looks more or less like
|
||||||
|
@ -61,7 +61,7 @@ pre.prettyprint.lang-bash
|
||||||
|
|
||||||
.alert.is-important Be sure to install <code>jasmine-core</code> , not <code>jasmine</code>!
|
.alert.is-important Be sure to install <code>jasmine-core</code> , not <code>jasmine</code>!
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Let’s make one more change to the `package.json` script commands.
|
Let’s make one more change to the `package.json` script commands.
|
||||||
|
|
||||||
**Open the `package.json` ** and scroll to the `scripts` node. Look for the command named `test`. Change it to:
|
**Open the `package.json` ** and scroll to the `scripts` node. Look for the command named `test`. Change it to:
|
||||||
|
@ -71,7 +71,7 @@ pre.prettyprint.lang-bash
|
||||||
That command will launch `live-server` and open a browser to the `unit-tests.html` page we just wrote.
|
That command will launch `live-server` and open a browser to the `unit-tests.html` page we just wrote.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## First app tests
|
## First app tests
|
||||||
|
|
||||||
Believe it or not … we could start testing *some* of our app right away. For example, we can test the `Hero` class:
|
Believe it or not … we could start testing *some* of our app right away. For example, we can test the `Hero` class:
|
||||||
|
@ -135,7 +135,7 @@ pre.prettyprint.lang-bash
|
||||||
The description should be sufficient to identify the tested application part and its source file. Almost any convention will do as long as you and your team follow it consistently and are never confused.
|
The description should be sufficient to identify the tested application part and its source file. Almost any convention will do as long as you and your team follow it consistently and are never confused.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Run the tests
|
## Run the tests
|
||||||
|
|
||||||
Open one terminal window and run the watching compiler command: `npm run tsc`
|
Open one terminal window and run the watching compiler command: `npm run tsc`
|
||||||
|
@ -149,7 +149,7 @@ figure.image-display
|
||||||
|
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Critique
|
## Critique
|
||||||
|
|
||||||
Is this `Hero` class even worth testing? It’s essentially a property bag with almost no logic. Maybe we should have tested the cloning feature. Maybe we should have tested id generation. We didn’t bother because there wasn’t much to learn by doing that.
|
Is this `Hero` class even worth testing? It’s essentially a property bag with almost no logic. Maybe we should have tested the cloning feature. Maybe we should have tested id generation. We didn’t bother because there wasn’t much to learn by doing that.
|
||||||
|
@ -161,7 +161,7 @@ figure.image-display
|
||||||
We need to relocate these tests to a separate file. Let’s do that next.
|
We need to relocate these tests to a separate file. Let’s do that next.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Where do tests go?
|
## Where do tests go?
|
||||||
|
|
||||||
Some people like to keep their tests in a `tests` folder parallel to the application source folder.
|
Some people like to keep their tests in a `tests` folder parallel to the application source folder.
|
||||||
|
@ -178,7 +178,7 @@ figure.image-display
|
||||||
You may put your tests elsewhere if you wish. We’re putting ours inside the app, next to the source files that they test.
|
You may put your tests elsewhere if you wish. We’re putting ours inside the app, next to the source files that they test.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## First spec file
|
## First spec file
|
||||||
|
|
||||||
**Create** a new file, ** `hero.spec.ts` ** in `src/app` next to `hero.ts`.
|
**Create** a new file, ** `hero.spec.ts` ** in `src/app` next to `hero.ts`.
|
||||||
|
@ -187,7 +187,7 @@ figure.image-display
|
||||||
|
|
||||||
.alert.is-important All of our unit test files follow this .spec naming pattern.
|
.alert.is-important All of our unit test files follow this .spec naming pattern.
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Move the tests we just wrote in`unit-tests.html` to `hero.spec.ts` and convert them from JavaScript into TypeScript:
|
Move the tests we just wrote in`unit-tests.html` to `hero.spec.ts` and convert them from JavaScript into TypeScript:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -230,26 +230,26 @@ figure.image-display
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/first-app-tests/Jasmine-not-running-tests.png' style="width:400px;" alt="Jasmine not running any tests")
|
img(src='/resources/images/devguide/first-app-tests/Jasmine-not-running-tests.png' style="width:400px;" alt="Jasmine not running any tests")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
That’s Jasmine saying “**things are _so_ bad that _I’m not running any tests_.**”
|
That’s Jasmine saying “**things are _so_ bad that _I’m not running any tests_.**”
|
||||||
|
|
||||||
Open the browser’s Developer Tools (F12, Ctrl-Shift-i). There’s an error:
|
Open the browser’s Developer Tools (F12, Ctrl-Shift-i). There’s an error:
|
||||||
|
|
||||||
code-example(format="" language="html").
|
code-example(format="" language="html").
|
||||||
Uncaught ReferenceError: exports is not defined
|
Uncaught ReferenceError: exports is not defined
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Load tests with systemjs
|
## Load tests with systemjs
|
||||||
|
|
||||||
The immediate cause of the error is the `export` statement in `hero.ts`.
|
The immediate cause of the error is the `export` statement in `hero.ts`.
|
||||||
That error was there all along.
|
That error was there all along.
|
||||||
It wasn’t a problem until we tried to `import` the `Hero` class in our tests.
|
It wasn’t a problem until we tried to `import` the `Hero` class in our tests.
|
||||||
|
|
||||||
Our test environment lacks support for module loading.
|
Our test environment lacks support for module loading.
|
||||||
Apparently we can’t simply load our application and test scripts like we do with 3rd party JavaScript libraries.
|
Apparently we can’t simply load our application and test scripts like we do with 3rd party JavaScript libraries.
|
||||||
|
|
||||||
We are committed to module loading in our application.
|
We are committed to module loading in our application.
|
||||||
Our app will call `import`. Our tests must do so too.
|
Our app will call `import`. Our tests must do so too.
|
||||||
|
|
||||||
We add module loading support in four steps:
|
We add module loading support in four steps:
|
||||||
|
@ -296,39 +296,39 @@ figure.image-display
|
||||||
|
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Observations
|
## Observations
|
||||||
|
|
||||||
### System.config
|
### System.config
|
||||||
System.js demands that we specify a default extension for the filenames that correspond to whatever it is asked to import.
|
System.js demands that we specify a default extension for the filenames that correspond to whatever it is asked to import.
|
||||||
Without that default, it would translate an import statement such as `import {Hero} from ‘./here’` to a request for the file named `hero`.
|
Without that default, it would translate an import statement such as `import {Hero} from ‘./here’` to a request for the file named `hero`.
|
||||||
Not `hero.js`. Just plain `hero`. Our server error with “404 - not found” because it doesn’t have a file of that name.
|
Not `hero.js`. Just plain `hero`. Our server error with “404 - not found” because it doesn’t have a file of that name.
|
||||||
|
|
||||||
Once configured with a default extension of ‘js’, Systemjs requests `hero.js` which *does* exist and is promptly returned by our server.
|
Once configured with a default extension of ‘js’, Systemjs requests `hero.js` which *does* exist and is promptly returned by our server.
|
||||||
|
|
||||||
### Asynchronous System.import
|
### Asynchronous System.import
|
||||||
The call to `System.import` shouldn’t surprise us but it’s asynchronous nature might.
|
The call to `System.import` shouldn’t surprise us but it’s asynchronous nature might.
|
||||||
If we ponder this for a moment, we realize that it must be asynchronous because
|
If we ponder this for a moment, we realize that it must be asynchronous because
|
||||||
System.js may have to fetch the corresponding JavaScript file from the server.
|
System.js may have to fetch the corresponding JavaScript file from the server.
|
||||||
Accordingly, `System.import` returns a promise and we must wait for that promise to resolve.
|
Accordingly, `System.import` returns a promise and we must wait for that promise to resolve.
|
||||||
Only then can Jasmine start evaluating the imported tests.
|
Only then can Jasmine start evaluating the imported tests.
|
||||||
|
|
||||||
### window.onload
|
### window.onload
|
||||||
Jasmine doesn’t have a `start` method. It wires its own start to the browser window’s `load` event.
|
Jasmine doesn’t have a `start` method. It wires its own start to the browser window’s `load` event.
|
||||||
That makes sense if we’re loading our tests with script tags.
|
That makes sense if we’re loading our tests with script tags.
|
||||||
The browser raises the `load` event when it finishes loading all scripts.
|
The browser raises the `load` event when it finishes loading all scripts.
|
||||||
|
|
||||||
But we’re not loading test scripts inline anymore.
|
But we’re not loading test scripts inline anymore.
|
||||||
We’re using the systemjs module loader and it won’t be done until long after the browser raised the `load` event.
|
We’re using the systemjs module loader and it won’t be done until long after the browser raised the `load` event.
|
||||||
Meanwhile, Jasmine started and ran to completion … with no tests to evaluate … before the import completed.
|
Meanwhile, Jasmine started and ran to completion … with no tests to evaluate … before the import completed.
|
||||||
|
|
||||||
So we must wait until the import completes and only then call the window `onLoad` handler.
|
So we must wait until the import completes and only then call the window `onLoad` handler.
|
||||||
Jasmine re-starts, this time with our imported test queued up.
|
Jasmine re-starts, this time with our imported test queued up.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## What’s Next?
|
## What’s Next?
|
||||||
We are able to test a part of our application with simple Jasmine tests.
|
We are able to test a part of our application with simple Jasmine tests.
|
||||||
The part was a stand-alone class that made no mention or use of Angular.
|
The part was a stand-alone class that made no mention or use of Angular.
|
||||||
|
|
||||||
That’s not rare but it’s not typical either. Most of our application parts make some use of the Angular framework.
|
That’s not rare but it’s not typical either. Most of our application parts make some use of the Angular framework.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
include ../../../../_includes/_util-fns
|
include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
We write **unit tests** to explore and confirm the **behavior** of parts of our application.
|
We write **unit tests** to explore and confirm the **behavior** of parts of our application.
|
||||||
|
|
||||||
We like *having* unit tests for many reasons, three of them in particular:
|
We like *having* unit tests for many reasons, three of them in particular:
|
||||||
|
@ -22,7 +22,7 @@ include ../../../../_includes/_util-fns
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/unit-testing/spectrum.png' alt="Functional Testing Spectrum")
|
img(src='/resources/images/devguide/unit-testing/spectrum.png' alt="Functional Testing Spectrum")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
<table style="box-shadow: none">
|
<table style="box-shadow: none">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="border-bottom: none">Pure unit test</td>
|
<td style="border-bottom: none">Pure unit test</td>
|
||||||
|
@ -113,12 +113,12 @@ figure.image-display
|
||||||
|
|
||||||
.callout.is-helpful
|
.callout.is-helpful
|
||||||
header How to Use This Guide
|
header How to Use This Guide
|
||||||
:markdown
|
:marked
|
||||||
The Unit Testing chapters build upon each other. We recommend reading them in order.
|
The Unit Testing chapters build upon each other. We recommend reading them in order.
|
||||||
We're also assuming that you're already comfortable with basic Angular 2 concepts and the tools
|
We're also assuming that you're already comfortable with basic Angular 2 concepts and the tools
|
||||||
we introduced in the [QuickStart](../quickstart.html) and
|
we introduced in the [QuickStart](../quickstart.html) and
|
||||||
the [Tour of Heroes](../tutorial/) tutorial
|
the [Tour of Heroes](../tutorial/) tutorial
|
||||||
such as <code>npm</code>, <code>gulp</code>, and <code>live-server</code>.
|
such as <code>npm</code>, <code>gulp</code>, and <code>live-server</code>.
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Let’s get started!
|
Let’s get started!
|
|
@ -1,6 +1,6 @@
|
||||||
include ../../../../_includes/_util-fns
|
include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
We’ll write our tests with the [Jasmine test framework](http://jasmine.github.io/2.3/introduction.html).
|
We’ll write our tests with the [Jasmine test framework](http://jasmine.github.io/2.3/introduction.html).
|
||||||
We’ll start by getting *some* tests to work - *any* tests at all.
|
We’ll start by getting *some* tests to work - *any* tests at all.
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
**Create a new project folder** perhaps called `angular2-unit-testing`.
|
**Create a new project folder** perhaps called `angular2-unit-testing`.
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Install npm packages locally
|
## Install npm packages locally
|
||||||
|
|
||||||
Next follow all of the steps prescribed in “Install npm packages locally” of the
|
Next follow all of the steps prescribed in “Install npm packages locally” of the
|
||||||
|
@ -24,19 +24,19 @@ pre.prettyprint.lang-bash
|
||||||
code npm install jasmine-core --save-dev --save-exact
|
code npm install jasmine-core --save-dev --save-exact
|
||||||
|
|
||||||
.alert.is-important
|
.alert.is-important
|
||||||
:markdown
|
:marked
|
||||||
Be sure to install `jasmine-core` , not `jasmine`!
|
Be sure to install `jasmine-core` , not `jasmine`!
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
**Create a sub-folder `src` ** for our tests and then **cd into it**.
|
**Create a sub-folder `src` ** for our tests and then **cd into it**.
|
||||||
|
|
||||||
We are going to **display and control our tests in the browser**.
|
We are going to **display and control our tests in the browser**.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
The browser is nice during development of a few tests. It’s not the best venue for working with a lot of tests and it won’t do at all for build automation. We’ll switch to the karma test-runner when the time comes. But the browser will do for now.
|
The browser is nice during development of a few tests. It’s not the best venue for working with a lot of tests and it won’t do at all for build automation. We’ll switch to the karma test-runner when the time comes. But the browser will do for now.
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Create a new file called`unit-tests.html` and enter the following:
|
Create a new file called`unit-tests.html` and enter the following:
|
||||||
```
|
```
|
||||||
<html>
|
<html>
|
||||||
|
@ -70,20 +70,20 @@ pre.prettyprint.lang-bash
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/jasmine-testing-101/jasmine-1-spec-0-failures.png' style="height:170px;" alt="Jasmine HTML test output")
|
img(src='/resources/images/devguide/jasmine-testing-101/jasmine-1-spec-0-failures.png' style="height:170px;" alt="Jasmine HTML test output")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
It doesn’t get much simpler than that!
|
It doesn’t get much simpler than that!
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## First TypeScript Test
|
## First TypeScript Test
|
||||||
Perhaps too simple. We won’t write our entire test suite inside one HTML file.
|
Perhaps too simple. We won’t write our entire test suite inside one HTML file.
|
||||||
Let’s **extract** that line of test code to a **new file in `src` called `1st.spec.ts` ** .
|
Let’s **extract** that line of test code to a **new file in `src` called `1st.spec.ts` ** .
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
Among Jasmine developers, a test is known as a “spec” and test filenames include the word “spec”. We’ll stick with that convention.
|
Among Jasmine developers, a test is known as a “spec” and test filenames include the word “spec”. We’ll stick with that convention.
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
The test we wrote is valid TypeScript because any JavaScript is valid TypeScript. But let’s make it more modern with an arrow function:
|
The test we wrote is valid TypeScript because any JavaScript is valid TypeScript. But let’s make it more modern with an arrow function:
|
||||||
```
|
```
|
||||||
it('true is true', () => expect(true).toEqual(true));
|
it('true is true', () => expect(true).toEqual(true));
|
||||||
|
@ -97,7 +97,7 @@ figure.image-display
|
||||||
That’s a reminder that we need to compile our TypeScript test files as we do our TypeScript application files. Do that next.
|
That’s a reminder that we need to compile our TypeScript test files as we do our TypeScript application files. Do that next.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Prepare for TypeScript
|
## Prepare for TypeScript
|
||||||
|
|
||||||
As we’ve seen before, we first have to tell the compiler how to compile our TypeScript files with
|
As we’ve seen before, we first have to tell the compiler how to compile our TypeScript files with
|
||||||
|
@ -125,12 +125,12 @@ pre.prettyprint.lang-bash
|
||||||
code npm run tsc
|
code npm run tsc
|
||||||
|
|
||||||
.alert.is-helpful
|
.alert.is-helpful
|
||||||
:markdown
|
:marked
|
||||||
Our editor and the compiler may complain that they don’t know
|
Our editor and the compiler may complain that they don’t know
|
||||||
what `it` and `expect` are because they lack the typing files that describe Jasmine.
|
what `it` and `expect` are because they lack the typing files that describe Jasmine.
|
||||||
We can ignore those annoying complaints for now as they are harmless.
|
We can ignore those annoying complaints for now as they are harmless.
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
If we reload the browser, we should see the same Jasmine test-runner output as before.
|
If we reload the browser, we should see the same Jasmine test-runner output as before.
|
||||||
|
|
||||||
We’ll be evolving these tests rapidly and it would be nice to have the browser refresh automatically as we make changes and recompile.
|
We’ll be evolving these tests rapidly and it would be nice to have the browser refresh automatically as we make changes and recompile.
|
||||||
|
@ -140,13 +140,13 @@ pre.prettyprint.lang-bash
|
||||||
pre.prettyprint.lang-bash
|
pre.prettyprint.lang-bash
|
||||||
code npm start
|
code npm start
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Now navigate to `1st-tests.html`
|
Now navigate to `1st-tests.html`
|
||||||
|
|
||||||
We should get the same Jasmine test-runner output as before.
|
We should get the same Jasmine test-runner output as before.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Add a describe and another test
|
## Add a describe and another test
|
||||||
|
|
||||||
We can’t tell what file produced these test results. We only have one file at the moment but soon we’ll write more.
|
We can’t tell what file produced these test results. We only have one file at the moment but soon we’ll write more.
|
||||||
|
@ -166,7 +166,7 @@ pre.prettyprint.lang-bash
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/jasmine-testing-101/test-report-1-spec-0-failures.png' style="height:100px;" alt="1 spec, 0 failures")
|
img(src='/resources/images/devguide/jasmine-testing-101/test-report-1-spec-0-failures.png' style="height:100px;" alt="1 spec, 0 failures")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Let’s add another Jasmine test to `1st.spec.ts`
|
Let’s add another Jasmine test to `1st.spec.ts`
|
||||||
```
|
```
|
||||||
it('null is not the same thing as undefined',
|
it('null is not the same thing as undefined',
|
||||||
|
@ -178,23 +178,23 @@ figure.image-display
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/jasmine-testing-101/test-report-2-specs-0-failures.png' style="height:100px;" alt="refreshed 2 specs, 0 failures")
|
img(src='/resources/images/devguide/jasmine-testing-101/test-report-2-specs-0-failures.png' style="height:100px;" alt="refreshed 2 specs, 0 failures")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
What does a failing test look like? Remove the `.not`. The browser refreshes and shows:
|
What does a failing test look like? Remove the `.not`. The browser refreshes and shows:
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/jasmine-testing-101/test-report-2-specs-1-failure.png' style="height:190px;" alt="failing test 2 specs, 1 failure")
|
img(src='/resources/images/devguide/jasmine-testing-101/test-report-2-specs-1-failure.png' style="height:190px;" alt="failing test 2 specs, 1 failure")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Click the `Spec List` link just below “2 specs, 1 failure” to see the summary again:
|
Click the `Spec List` link just below “2 specs, 1 failure” to see the summary again:
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/jasmine-testing-101/spec-list-2-specs-1-failure.png' style="height:140px;" alt="2 specs, 1 failure")
|
img(src='/resources/images/devguide/jasmine-testing-101/spec-list-2-specs-1-failure.png' style="height:140px;" alt="2 specs, 1 failure")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
We can re-run just the failing test by double-clicking it. Try it!
|
We can re-run just the failing test by double-clicking it. Try it!
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Debug the test
|
## Debug the test
|
||||||
Suppose we didn’t know what was going on. We can debug it in the browser.
|
Suppose we didn’t know what was going on. We can debug it in the browser.
|
||||||
|
|
||||||
|
@ -209,7 +209,7 @@ figure.image-display
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/jasmine-testing-101/null-to-equal-undefined.png' style="height:500px;" alt="null === undefined")
|
img(src='/resources/images/devguide/jasmine-testing-101/null-to-equal-undefined.png' style="height:500px;" alt="null === undefined")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
How about that! They really aren’t equal.
|
How about that! They really aren’t equal.
|
||||||
- remove the breakpoint (right-click in the “Breakpoints” section and chose “Remove breakpoint”)
|
- remove the breakpoint (right-click in the “Breakpoints” section and chose “Remove breakpoint”)
|
||||||
- Click the “play” icon to resume the test (or F8)
|
- Click the “play” icon to resume the test (or F8)
|
||||||
|
@ -222,14 +222,14 @@ figure.image-display
|
||||||
|
|
||||||
<!-- TODO
|
<!-- TODO
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Learn more
|
## Learn more
|
||||||
Learn more about basic Jasmine testing here
|
Learn more about basic Jasmine testing here
|
||||||
[Resources TBD](./#)
|
[Resources TBD](./#)
|
||||||
-->
|
-->
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## What’s Next?
|
## What’s Next?
|
||||||
Now that we’re familiar with Jasmine on its own, we’re ready to test an application.
|
Now that we’re familiar with Jasmine on its own, we’re ready to test an application.
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
include ../../../../_includes/_util-fns
|
include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
We’ll test an Angular pipe in this chapter
|
We’ll test an Angular pipe in this chapter
|
||||||
|
|
||||||
An Angular pipe is a declarative way in HTML to transform some input into some displayable output.
|
An Angular pipe is a declarative way in HTML to transform some input into some displayable output.
|
||||||
|
@ -12,7 +12,7 @@ include ../../../../_includes/_util-fns
|
||||||
code-example(format="linenums" language="html" escape="html").
|
code-example(format="linenums" language="html" escape="html").
|
||||||
<h2>{{hero.name | initCaps}} is {{userName}}'s current super hero!</h2>
|
<h2>{{hero.name | initCaps}} is {{userName}}'s current super hero!</h2>
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
The code for `InitCapsPipe` in `init-caps-pipe.ts` is quite brief:
|
The code for `InitCapsPipe` in `init-caps-pipe.ts` is quite brief:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -35,36 +35,36 @@ code-example(format="linenums" language="html" escape="html").
|
||||||
|
|
||||||
.callout.is-helpful
|
.callout.is-helpful
|
||||||
header Prior Knowledge
|
header Prior Knowledge
|
||||||
:markdown
|
:marked
|
||||||
The Unit Testing chapters build upon each other. We recommend reading them in order.
|
The Unit Testing chapters build upon each other. We recommend reading them in order.
|
||||||
We're also assuming that you're already comfortable with basic Angular 2 concepts and the tools
|
We're also assuming that you're already comfortable with basic Angular 2 concepts and the tools
|
||||||
we introduced in the [QuickStart](../quickstart.html) and
|
we introduced in the [QuickStart](../quickstart.html) and
|
||||||
the [Tour of Heroes](../tutorial/) tutorial
|
the [Tour of Heroes](../tutorial/) tutorial
|
||||||
such as <code>npm</code>, <code>gulp</code>, and <code>live-server</code>.
|
such as <code>npm</code>, <code>gulp</code>, and <code>live-server</code>.
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
## Add the Angular library
|
## Add the Angular library
|
||||||
Looking back at `unit-tests.html` we realize that we have not loaded the Angular library.
|
Looking back at `unit-tests.html` we realize that we have not loaded the Angular library.
|
||||||
Yet we were able to load and test the application’s `Hero` class.
|
Yet we were able to load and test the application’s `Hero` class.
|
||||||
|
|
||||||
**We were lucky!** The `Hero` class has no dependence on Angular.
|
**We were lucky!** The `Hero` class has no dependence on Angular.
|
||||||
If it had depended on Angular, we’d still be staring at the Jasmine “big-time fail” screen:
|
If it had depended on Angular, we’d still be staring at the Jasmine “big-time fail” screen:
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/testing-an-angular-pipe/big-time-fail-screen.png'
|
img(src='/resources/images/devguide/testing-an-angular-pipe/big-time-fail-screen.png'
|
||||||
style="width:400px;" alt="Jasmine's' big time fail screen")
|
style="width:400px;" alt="Jasmine's' big time fail screen")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
If we then opened the browser’s Developer Tools (F12, Ctrl-Shift-I) and looked
|
If we then opened the browser’s Developer Tools (F12, Ctrl-Shift-I) and looked
|
||||||
in the console window, we would see that SystemJS
|
in the console window, we would see that SystemJS
|
||||||
tried to load Angular and couldn't find it.
|
tried to load Angular and couldn't find it.
|
||||||
|
|
||||||
code-example(format="" language="html" escape="html").
|
code-example(format="" language="html" escape="html").
|
||||||
GET http://127.0.0.1:8080/src/angular2/angular2 404 (Not Found)
|
GET http://127.0.0.1:8080/src/angular2/angular2 404 (Not Found)
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
We are writing an Angular application afterall and
|
We are writing an Angular application afterall and
|
||||||
we were going to need Angular sooner or later. That time has come.
|
we were going to need Angular sooner or later. That time has come.
|
||||||
The `InitCapsPiep` clearly depends on Angular as is clear in the first few lines:
|
The `InitCapsPiep` clearly depends on Angular as is clear in the first few lines:
|
||||||
```
|
```
|
||||||
import {Pipe} from 'angular2/angular2';
|
import {Pipe} from 'angular2/angular2';
|
||||||
|
@ -112,11 +112,11 @@ code-example(format="" language="html" escape="html").
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
Note that each test is short (one line in our case).
|
Note that each test is short (one line in our case).
|
||||||
It has a clear label that accurately describes the test. And it makes exactly one expectation.
|
It has a clear label that accurately describes the test. And it makes exactly one expectation.
|
||||||
|
|
||||||
Anyone can read these tests and understand quickly what the test does and what the pipe does.
|
Anyone can read these tests and understand quickly what the test does and what the pipe does.
|
||||||
If one of the tests fails, we know which expected behavior is no longer true.
|
If one of the tests fails, we know which expected behavior is no longer true.
|
||||||
We’ll have little trouble maintaining these tests and adding more like them as we encounter new conditions to explore.
|
We’ll have little trouble maintaining these tests and adding more like them as we encounter new conditions to explore.
|
||||||
|
|
||||||
That’s the way we like our tests!
|
That’s the way we like our tests!
|
||||||
|
@ -129,10 +129,10 @@ code-example(format="" language="html" escape="html").
|
||||||
|
|
||||||
Hmm. We can’t just add `System.import('app/init-caps-pipe.spec')`.
|
Hmm. We can’t just add `System.import('app/init-caps-pipe.spec')`.
|
||||||
|
|
||||||
The first `System.import` returns a promise as does this second import.
|
The first `System.import` returns a promise as does this second import.
|
||||||
We can’t run any of the Jasmine tests until **both imports are finished**.
|
We can’t run any of the Jasmine tests until **both imports are finished**.
|
||||||
|
|
||||||
Fortunately, we can create a new `Promise` that wraps both import promises and waits
|
Fortunately, we can create a new `Promise` that wraps both import promises and waits
|
||||||
for both to finish loading.
|
for both to finish loading.
|
||||||
```
|
```
|
||||||
// #3. Import the spec files explicitly
|
// #3. Import the spec files explicitly
|
||||||
|
@ -144,13 +144,13 @@ code-example(format="" language="html" escape="html").
|
||||||
Try it. The browser should refresh and show
|
Try it. The browser should refresh and show
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/testing-an-angular-pipe/5-specs-0-failures.png'
|
img(src='/resources/images/devguide/testing-an-angular-pipe/5-specs-0-failures.png'
|
||||||
style="width:400px;" alt="import promises 5 specs, 0 failures")
|
style="width:400px;" alt="import promises 5 specs, 0 failures")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
We have a pattern for adding new tests.
|
We have a pattern for adding new tests.
|
||||||
|
|
||||||
In future, when we add a new spec, we add another `System.import('app/some.spec')` to
|
In future, when we add a new spec, we add another `System.import('app/some.spec')` to
|
||||||
the array argument passed to `Promise.all`.
|
the array argument passed to `Promise.all`.
|
||||||
|
|
||||||
## What’s Next?
|
## What’s Next?
|
||||||
|
@ -159,5 +159,5 @@ figure.image-display
|
||||||
|
|
||||||
What about testing parts that *are themselves asynchronous*?
|
What about testing parts that *are themselves asynchronous*?
|
||||||
|
|
||||||
In the next chapter we’ll test a service with a public asynchronous method that fetches heroes
|
In the next chapter we’ll test a service with a public asynchronous method that fetches heroes
|
||||||
from a remote server.
|
from a remote server.
|
|
@ -1,6 +1,6 @@
|
||||||
include ../../../../_includes/_util-fns
|
include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
# Tour of Heroes: the vision
|
# Tour of Heroes: the vision
|
||||||
|
|
||||||
Our grand plan is to build an app to help a staffing agency manage its stable of heroes.
|
Our grand plan is to build an app to help a staffing agency manage its stable of heroes.
|
||||||
|
@ -23,9 +23,9 @@ include ../../../../_includes/_util-fns
|
||||||
Angular can do whatever we need it to do.
|
Angular can do whatever we need it to do.
|
||||||
We'll be covering a lot of ground at an introductory level but we’ll find plenty of links
|
We'll be covering a lot of ground at an introductory level but we’ll find plenty of links
|
||||||
to chapters with greater depth.
|
to chapters with greater depth.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## The End Game
|
## The End Game
|
||||||
|
|
||||||
Here's a visual idea of where we're going in this tour, beginning with the "Dashboard"
|
Here's a visual idea of where we're going in this tour, beginning with the "Dashboard"
|
||||||
|
@ -34,17 +34,17 @@ include ../../../../_includes/_util-fns
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/toh/heroes-dashboard-1.png' alt="Output of heroes dashboard")
|
img(src='/resources/images/devguide/toh/heroes-dashboard-1.png' alt="Output of heroes dashboard")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Above the dashboard are two links ("Dashboard" and "Heroes").
|
Above the dashboard are two links ("Dashboard" and "Heroes").
|
||||||
We could click them to navigate between this Dashboard and a Heroes view.
|
We could click them to navigate between this Dashboard and a Heroes view.
|
||||||
|
|
||||||
Instead we click the dashboard hero named "Magneta" and the router takes us to a "Hero Details" view
|
Instead we click the dashboard hero named "Magneta" and the router takes us to a "Hero Details" view
|
||||||
of that hero where we can change the hero's name.
|
of that hero where we can change the hero's name.
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/toh/hero-details-1.png' alt="Details of hero in app")
|
img(src='/resources/images/devguide/toh/hero-details-1.png' alt="Details of hero in app")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Links at the top can take us to either of the main views.
|
Links at the top can take us to either of the main views.
|
||||||
We'll click the "Back" button which sends us to the "Heroes" master list view with
|
We'll click the "Back" button which sends us to the "Heroes" master list view with
|
||||||
"Magneta" as the selected hero.
|
"Magneta" as the selected hero.
|
||||||
|
@ -52,25 +52,25 @@ figure.image-display
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/toh/heroes-list-1.png' alt="Output of heroes list app")
|
img(src='/resources/images/devguide/toh/heroes-list-1.png' alt="Output of heroes list app")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
We click a different hero and the readonly mini-detail beneath the list reflects our new choice.
|
We click a different hero and the readonly mini-detail beneath the list reflects our new choice.
|
||||||
|
|
||||||
We click the "View Details" button to drill into the
|
We click the "View Details" button to drill into the
|
||||||
editable details of our selected hero.
|
editable details of our selected hero.
|
||||||
|
|
||||||
The following diagram captures all of our navigation options.
|
The following diagram captures all of our navigation options.
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/toh/nav-diagram.png' alt="View navigations")
|
img(src='/resources/images/devguide/toh/nav-diagram.png' alt="View navigations")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Here's our app in action
|
Here's our app in action
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/toh/toh-anim.gif' alt="Tour of Heroes in Action")
|
img(src='/resources/images/devguide/toh/toh-anim.gif' alt="Tour of Heroes in Action")
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## How We Roll
|
## How We Roll
|
||||||
|
|
||||||
We’ll build this Tour of Heroes together, step by step.
|
We’ll build this Tour of Heroes together, step by step.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
include ../../../../_includes/_util-fns
|
include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
# Once Upon a Time
|
# Once Upon a Time
|
||||||
|
|
||||||
Every story starts somewhere. Our story starts where the [QuickStart](../quickstart.html) ends.
|
Every story starts somewhere. Our story starts where the [QuickStart](../quickstart.html) ends.
|
||||||
|
@ -22,32 +22,32 @@ include ../../../../_includes/_util-fns
|
||||||
| └── tsconfig.json
|
| └── tsconfig.json
|
||||||
└── package.json
|
└── package.json
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
### Keep the app running
|
### Keep the app running
|
||||||
Start the TypeScript compiler and have it watch for changes in one terminal window by typing
|
Start the TypeScript compiler and have it watch for changes in one terminal window by typing
|
||||||
|
|
||||||
pre.prettyprint.lang-bash
|
pre.prettyprint.lang-bash
|
||||||
code npm run tsc
|
code npm run tsc
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Now open another terminal window and start the server by typing
|
Now open another terminal window and start the server by typing
|
||||||
|
|
||||||
pre.prettyprint.lang-bash
|
pre.prettyprint.lang-bash
|
||||||
code npm start
|
code npm start
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
This command starts the server, launches the app in a browser,
|
This command starts the server, launches the app in a browser,
|
||||||
and keeps the app running while we continue to build the Tour of Heroes.
|
and keeps the app running while we continue to build the Tour of Heroes.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
These two steps watch all project files. They recompile TypeScript files and re-run
|
These two steps watch all project files. They recompile TypeScript files and re-run
|
||||||
the app when any file changes.
|
the app when any file changes.
|
||||||
If the watchers fail to detect renamed or new files,
|
If the watchers fail to detect renamed or new files,
|
||||||
stop these commands in each terminal by typing `CTRL+C` and then re-run them.
|
stop these commands in each terminal by typing `CTRL+C` and then re-run them.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Show our Hero
|
## Show our Hero
|
||||||
We want to display Hero data in our app
|
We want to display Hero data in our app
|
||||||
|
|
||||||
|
@ -61,12 +61,12 @@ include ../../../../_includes/_util-fns
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Now we update the template in the `@Component` decoration with data bindings to these new properties.
|
Now we update the template in the `@Component` decoration with data bindings to these new properties.
|
||||||
|
|
||||||
code-example(format="").
|
code-example(format="").
|
||||||
template: '<h1>{{title}}</h1><h2>{{hero}} details!</h2>'
|
template: '<h1>{{title}}</h1><h2>{{hero}} details!</h2>'
|
||||||
:markdown
|
:marked
|
||||||
The browser should refresh and display our title and hero.
|
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.
|
The double curly braces tell our app to read the `title` and `hero` properties from the component and render them.
|
||||||
|
@ -103,7 +103,7 @@ include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
code-example(format="").
|
code-example(format="").
|
||||||
template: '<h1>{{title}}</h1><h2>{{hero.name}} details!</h2>'
|
template: '<h1>{{title}}</h1><h2>{{hero.name}} details!</h2>'
|
||||||
:markdown
|
:marked
|
||||||
The browser refreshes and continues to display our hero’s name.
|
The browser refreshes and continues to display our hero’s name.
|
||||||
|
|
||||||
### Adding more HTML
|
### Adding more HTML
|
||||||
|
@ -112,7 +112,7 @@ include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
code-example(format="linenums").
|
code-example(format="linenums").
|
||||||
template: '<h1>{{title}}</h1><h2>{{hero.name}} details!</h2><div><label>id: </label>{{hero.id}}</div><div><label>name: </label>{{hero.name}}</div>'
|
template: '<h1>{{title}}</h1><h2>{{hero.name}} details!</h2><div><label>id: </label>{{hero.id}}</div><div><label>name: </label>{{hero.name}}</div>'
|
||||||
:markdown
|
: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.
|
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
|
### Multi-line template strings
|
||||||
|
@ -136,7 +136,7 @@ include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
.callout.is-important
|
.callout.is-important
|
||||||
header A back-tick is not a single quote
|
header A back-tick is not a single quote
|
||||||
:markdown
|
:marked
|
||||||
**Be careful!** A back-tick (`) looks a lot like a single quote (').
|
**Be careful!** A back-tick (`) looks a lot like a single quote (').
|
||||||
It's actually a completely different character.
|
It's actually a completely different character.
|
||||||
Back-ticks can do more than demarcate a string.
|
Back-ticks can do more than demarcate a string.
|
||||||
|
@ -145,7 +145,7 @@ include ../../../../_includes/_util-fns
|
||||||
is part of a single template string.
|
is part of a single template string.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Editing Our Hero
|
## Editing Our Hero
|
||||||
|
|
||||||
We want to be able to edit the hero name in a textbox.
|
We want to be able to edit the hero name in a textbox.
|
||||||
|
@ -161,7 +161,7 @@ include ../../../../_includes/_util-fns
|
||||||
<div><input value="{{hero.name}}" placeholder="name"></div>
|
<div><input value="{{hero.name}}" placeholder="name"></div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
:markdown
|
:marked
|
||||||
We see in the browser that the hero’s name does appear in the `<input>` textbox.
|
We see in the browser that the hero’s name does appear in the `<input>` textbox.
|
||||||
But something doesn’t feel right.
|
But something doesn’t feel right.
|
||||||
When we change the name, we notice that our change
|
When we change the name, we notice that our change
|
||||||
|
@ -175,22 +175,22 @@ include ../../../../_includes/_util-fns
|
||||||
In short, we want two-way data binding.
|
In short, we want two-way data binding.
|
||||||
|
|
||||||
Let’s update the template to use the **`ng-model`** built-in directive for two-way binding.
|
Let’s update the template to use the **`ng-model`** built-in directive for two-way binding.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
Learn more about `ng-model` in the [Template Syntax](../guide/template-syntax.html#ng-model)
|
Learn more about `ng-model` in the [Template Syntax](../guide/template-syntax.html#ng-model)
|
||||||
:markdown
|
:marked
|
||||||
Replace the `<input>` with the following HTML
|
Replace the `<input>` with the following HTML
|
||||||
|
|
||||||
code-example(language="html").
|
code-example(language="html").
|
||||||
<input [(ng-model)]="hero.name" placeholder="name">
|
<input [(ng-model)]="hero.name" placeholder="name">
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Unfortunately, that change broke our application and we're no longer displaying the hero in the browser.
|
Unfortunately, that change broke our application and we're no longer displaying the hero in the browser.
|
||||||
Let’s fix that next.
|
Let’s fix that next.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Declaring Template Directives
|
## Declaring Template Directives
|
||||||
|
|
||||||
We added the `ng-model` directive but we didn't tell Angular about it.
|
We added the `ng-model` directive but we didn't tell Angular about it.
|
||||||
|
@ -217,8 +217,8 @@ include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
code-example(language="html").
|
code-example(language="html").
|
||||||
EXCEPTION: No value accessor for ' ' in [null]
|
EXCEPTION: No value accessor for ' ' in [null]
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Apparently declaring the `NgModel` is not quite enough.
|
Apparently declaring the `NgModel` is not quite enough.
|
||||||
|
|
||||||
## Declare Multiple Form Directives
|
## Declare Multiple Form Directives
|
||||||
|
@ -234,9 +234,9 @@ include ../../../../_includes/_util-fns
|
||||||
bundled in a convenient array called `FORM_DIRECTIVES`.
|
bundled in a convenient array called `FORM_DIRECTIVES`.
|
||||||
<!-- TODO
|
<!-- TODO
|
||||||
.alert.is-helpful
|
.alert.is-helpful
|
||||||
:markdown
|
:marked
|
||||||
Learn more about Angular Forms in the [Forms chapter]()
|
Learn more about Angular Forms in the [Forms chapter]()
|
||||||
:markdown
|
:marked
|
||||||
-->
|
-->
|
||||||
Let’s forget about importing `NgModel` and import the `FORM_DIRECTIVES` array instead:
|
Let’s forget about importing `NgModel` and import the `FORM_DIRECTIVES` array instead:
|
||||||
```
|
```
|
||||||
|
@ -259,7 +259,7 @@ include ../../../../_includes/_util-fns
|
||||||
and plug that array into the `directives` property.
|
and plug that array into the `directives` property.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## The Road We’ve Travelled
|
## The Road We’ve Travelled
|
||||||
Let’s take stock of what we’ve built.
|
Let’s take stock of what we’ve built.
|
||||||
|
|
||||||
|
@ -304,12 +304,12 @@ include ../../../../_includes/_util-fns
|
||||||
}
|
}
|
||||||
|
|
||||||
bootstrap(AppComponent);
|
bootstrap(AppComponent);
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## The Road Ahead
|
## The Road Ahead
|
||||||
Our Tour of Heroes only displays one hero and we really want to display a list of heroes.
|
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.
|
We also want to allow the user to select a hero and display their details.
|
||||||
We’ll learn more about how to retrieve lists, bind them to the
|
We’ll learn more about how to retrieve lists, bind them to the
|
||||||
template, and allow a user to select it in the
|
template, and allow a user to select it in the
|
||||||
[next tutorial chapter](./toh-pt2.html).
|
[next tutorial chapter](./toh-pt2.html).
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
include ../../../../_includes/_util-fns
|
include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
# It Takes Many Heroes
|
# It Takes Many Heroes
|
||||||
Our story needs more heroes.
|
Our story needs more heroes.
|
||||||
We’ll expand our Tour of Heroes app to display a list of heroes,
|
We’ll expand our Tour of Heroes app to display a list of heroes,
|
||||||
allow the user to select a hero, and display the hero’s details.
|
allow the user to select a hero, and display the hero’s details.
|
||||||
|
|
||||||
Let’s take stock of what we’ll need to display a list of heroes.
|
Let’s take stock of what we’ll need to display a list of heroes.
|
||||||
First, we need a list of heroes. We want to display those heroes in the view’s template,
|
First, we need a list of heroes. We want to display those heroes in the view’s template,
|
||||||
so we’ll need a way to do that.
|
so we’ll need a way to do that.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Where We Left Off
|
## Where We Left Off
|
||||||
Before we continue with Part 2 of the Tour of Heroes,
|
Before we continue with Part 2 of the Tour of Heroes,
|
||||||
let’s verify we have the following structure after [Part 1](./toh-pt1.html).
|
let’s verify we have the following structure after [Part 1](./toh-pt1.html).
|
||||||
If not, we’ll need to go back to Part 1 and figure out what we missed.
|
If not, we’ll need to go back to Part 1 and figure out what we missed.
|
||||||
|
|
||||||
code-example.
|
code-example.
|
||||||
angular2-tour-of-heroes
|
angular2-tour-of-heroes
|
||||||
├── node_modules
|
├── node_modules
|
||||||
|
@ -26,25 +26,25 @@ include ../../../../_includes/_util-fns
|
||||||
| | └── app.ts
|
| | └── app.ts
|
||||||
| ├── index.html
|
| ├── index.html
|
||||||
| └── tsconfig.json
|
| └── tsconfig.json
|
||||||
└── package.json
|
└── package.json
|
||||||
:markdown
|
:marked
|
||||||
### Keep the app running
|
### Keep the app running
|
||||||
Start the TypeScript compiler and have it watch for changes in one terminal window by typing
|
Start the TypeScript compiler and have it watch for changes in one terminal window by typing
|
||||||
|
|
||||||
pre.prettyprint.lang-bash
|
pre.prettyprint.lang-bash
|
||||||
code npm run tsc
|
code npm run tsc
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Now open another terminal window and start the server by typing
|
Now open another terminal window and start the server by typing
|
||||||
|
|
||||||
pre.prettyprint.lang-bash
|
pre.prettyprint.lang-bash
|
||||||
code npm start
|
code npm start
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
This will keep the application running while we continue to build the Tour of Heroes.
|
This will keep the application running while we continue to build the Tour of Heroes.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Displaying Our Heroes
|
## Displaying Our Heroes
|
||||||
### Creating heroes
|
### Creating heroes
|
||||||
Let’s create an array of ten heroes at the bottom of `app.ts`.
|
Let’s create an array of ten heroes at the bottom of `app.ts`.
|
||||||
|
@ -62,11 +62,11 @@ include ../../../../_includes/_util-fns
|
||||||
{ "id": 20, "name": "Tornado" }
|
{ "id": 20, "name": "Tornado" }
|
||||||
];
|
];
|
||||||
```
|
```
|
||||||
The `HEROES` array is of type `Hero`.
|
The `HEROES` array is of type `Hero`.
|
||||||
We are taking advantage of the `Hero` class we coded previously to create an array of our heroes.
|
We are taking advantage of the `Hero` class we coded previously to create an array of our heroes.
|
||||||
We aspire to get this list of heroes from a web service, but let’s take small steps
|
We aspire to get this list of heroes from a web service, but let’s take small steps
|
||||||
on this road and start by displaying these mock heroes in the browser.
|
on this road and start by displaying these mock heroes in the browser.
|
||||||
|
|
||||||
### Exposing heroes
|
### Exposing heroes
|
||||||
Let’s create a public property in `AppComponent` that exposes the heroes for binding.
|
Let’s create a public property in `AppComponent` that exposes the heroes for binding.
|
||||||
```
|
```
|
||||||
|
@ -74,14 +74,14 @@ include ../../../../_includes/_util-fns
|
||||||
```
|
```
|
||||||
We did not have to define the `heroes` type. TypeScript can infer it from the `HEROES` array.
|
We did not have to define the `heroes` type. TypeScript can infer it from the `HEROES` array.
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
We could have defined the heroes list here in this component class.
|
We could have defined the heroes list here in this component class.
|
||||||
But we know that we’ll get the heroes from a data service.
|
But we know that we’ll get the heroes from a data service.
|
||||||
Because we know where we are heading, it makes sense to separate the hero data
|
Because we know where we are heading, it makes sense to separate the hero data
|
||||||
from the class implementation from the start.
|
from the class implementation from the start.
|
||||||
:markdown
|
:marked
|
||||||
### Displaying heroes in a template
|
### Displaying heroes in a template
|
||||||
Our component has`heroes`. Let’s create an unordered list in our template to display them.
|
Our component has`heroes`. Let’s create an unordered list in our template to display them.
|
||||||
We’ll insert the following chunk of HTML below the title and above the hero details.
|
We’ll insert the following chunk of HTML below the title and above the hero details.
|
||||||
```
|
```
|
||||||
<h2>My Heroes</h2>
|
<h2>My Heroes</h2>
|
||||||
|
@ -94,79 +94,79 @@ include ../../../../_includes/_util-fns
|
||||||
Now we have a template that we can fill with our heroes.
|
Now we have a template that we can fill with our heroes.
|
||||||
|
|
||||||
### Listing heroes with ng-for
|
### Listing heroes with ng-for
|
||||||
|
|
||||||
We want to bind the array of `heroes` in our component to our template, iterate over them,
|
We want to bind the array of `heroes` in our component to our template, iterate over them,
|
||||||
and display them individually.
|
and display them individually.
|
||||||
We’ll need some help from Angular to do this. Let’s do this step by step.
|
We’ll need some help from Angular to do this. Let’s do this step by step.
|
||||||
|
|
||||||
First modify the `<li>` tag by adding the built-in directive `*ng-for`.
|
First modify the `<li>` tag by adding the built-in directive `*ng-for`.
|
||||||
```
|
```
|
||||||
<li *ng-for="#hero of heroes">
|
<li *ng-for="#hero of heroes">
|
||||||
```
|
```
|
||||||
.alert.is-critical
|
.alert.is-critical
|
||||||
:markdown
|
:marked
|
||||||
The leading asterisk (`*`) in front of `ng-for` is a critical part of this syntax.
|
The leading asterisk (`*`) in front of `ng-for` is a critical part of this syntax.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
The (`*`) prefix to `ng-for` indicates that the `<li>` element and its children
|
The (`*`) prefix to `ng-for` indicates that the `<li>` element and its children
|
||||||
constitute a master template.
|
constitute a master template.
|
||||||
|
|
||||||
The `ng-for` directive iterates over the `heroes` array returned by the `AppComponent.heroes` property
|
The `ng-for` directive iterates over the `heroes` array returned by the `AppComponent.heroes` property
|
||||||
and stamps out instances of this template.
|
and stamps out instances of this template.
|
||||||
|
|
||||||
The quoted text assigned to `ng-for` means
|
The quoted text assigned to `ng-for` means
|
||||||
“*take each hero in the `heroes` array, store it in the local `hero` variable,
|
“*take each hero in the `heroes` array, store it in the local `hero` variable,
|
||||||
and make it available to the corresponding template instance*”.
|
and make it available to the corresponding template instance*”.
|
||||||
|
|
||||||
The `#` prefix before "hero" identifies the `hero` as a local template variable.
|
The `#` prefix before "hero" identifies the `hero` as a local template variable.
|
||||||
We can reference this variable within the template to access a hero’s properties.
|
We can reference this variable within the template to access a hero’s properties.
|
||||||
|
|
||||||
Learn more about `ng-for` and local template variables in the
|
Learn more about `ng-for` and local template variables in the
|
||||||
[Template Syntax](../guide/template-syntax.html#ng-for) chapter.
|
[Template Syntax](../guide/template-syntax.html#ng-for) chapter.
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
With this background in mind, we now insert some content between the `<li>` tags
|
With this background in mind, we now insert some content between the `<li>` tags
|
||||||
that uses the `hero` template variable to display the hero’s properties.
|
that uses the `hero` template variable to display the hero’s properties.
|
||||||
|
|
||||||
code-example(format="linenums" language="html").
|
code-example(format="linenums" language="html").
|
||||||
<li *ng-for="#hero of heroes">
|
<li *ng-for="#hero of heroes">
|
||||||
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
### Declaring ng-for
|
### Declaring ng-for
|
||||||
When we view the running app in the browser we see nothing … no heroes.
|
When we view the running app in the browser we see nothing … no heroes.
|
||||||
We open the developer tools and see an error in the console.
|
We open the developer tools and see an error in the console.
|
||||||
|
|
||||||
code-example(language="html" ).
|
code-example(language="html" ).
|
||||||
EXCEPTION:
|
EXCEPTION:
|
||||||
Can't bind to 'ngForOf' since it isn't a known property of the '<template>' element and
|
Can't bind to 'ngForOf' since it isn't a known property of the '<template>' element and
|
||||||
there are no matching directives with a corresponding property
|
there are no matching directives with a corresponding property
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Thankfully we have a clear error message that indicates where we went wrong.
|
Thankfully we have a clear error message that indicates where we went wrong.
|
||||||
We used `ng-for` in the template but we didn’t tell the component about it.
|
We used `ng-for` in the template but we didn’t tell the component about it.
|
||||||
From Angular's perspective, `ng-for` is a meaningless attribute.
|
From Angular's perspective, `ng-for` is a meaningless attribute.
|
||||||
When it tries to render the view, it doesn’t recognize `ng-for` and gives up.
|
When it tries to render the view, it doesn’t recognize `ng-for` and gives up.
|
||||||
|
|
||||||
We need to say “*hey component, I’m going to use this NgFor directive. OK?*”
|
We need to say “*hey component, I’m going to use this NgFor directive. OK?*”
|
||||||
|
|
||||||
To that end, we first import the `NgFor` symbol
|
To that end, we first import the `NgFor` symbol
|
||||||
```
|
```
|
||||||
import {bootstrap, Component, FORM_DIRECTIVES, NgFor} from 'angular2/angular2';
|
import {bootstrap, Component, FORM_DIRECTIVES, NgFor} from 'angular2/angular2';
|
||||||
```
|
```
|
||||||
and then declare `NgFor` to be one of the view’s directives in the `@Component` decorator.
|
and then declare `NgFor` to be one of the view’s directives in the `@Component` decorator.
|
||||||
```
|
```
|
||||||
directives: [FORM_DIRECTIVES, NgFor]
|
directives: [FORM_DIRECTIVES, NgFor]
|
||||||
```
|
```
|
||||||
After the browser refreshes, we see a list of heroes!
|
After the browser refreshes, we see a list of heroes!
|
||||||
|
|
||||||
### Styling our heroes
|
### Styling our heroes
|
||||||
Our list of heroes looks pretty bland.
|
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.
|
We want to make it visually obvious to a user which hero we are hovering over and which hero is selected.
|
||||||
|
|
||||||
Let’s add some styles to our component by setting the `styles` property on the `@Component` decorator
|
Let’s add some styles to our component by setting the `styles` property on the `@Component` decorator
|
||||||
to the following CSS classes:
|
to the following CSS classes:
|
||||||
```
|
```
|
||||||
styles:[`
|
styles:[`
|
||||||
|
@ -187,109 +187,109 @@ include ../../../../_includes/_util-fns
|
||||||
`],
|
`],
|
||||||
```
|
```
|
||||||
Notice that we again use the back-tick notation for multi-line strings.
|
Notice that we again use the back-tick notation for multi-line strings.
|
||||||
|
|
||||||
When we assign styles to a component they are scoped to that specific component.
|
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 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:
|
Our template for displaying the heroes should now look like this:
|
||||||
|
|
||||||
code-example(format="linenums").
|
code-example(format="linenums").
|
||||||
<h2>My Heroes</h2>
|
<h2>My Heroes</h2>
|
||||||
<ul class="heroes">
|
<ul class="heroes">
|
||||||
<li *ng-for="#hero of heroes">
|
<li *ng-for="#hero of heroes">
|
||||||
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
:markdown
|
:marked
|
||||||
Our styled list of heroes should look like this:
|
Our styled list of heroes should look like this:
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/toh/heroes-list-2.png' alt="Output of heroes list app (no selection color)")
|
img(src='/resources/images/devguide/toh/heroes-list-2.png' alt="Output of heroes list app (no selection color)")
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Selecting a Hero
|
## Selecting a Hero
|
||||||
We have a list of heroes and we have a single hero displayed in our app.
|
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.
|
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.
|
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”.
|
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.
|
In our case, the master is the heroes list and the detail is the selected hero.
|
||||||
|
|
||||||
Let’s connect the master to the detail through a `selectedHero` component property bound to a click event.
|
Let’s connect the master to the detail through a `selectedHero` component property bound to a click event.
|
||||||
|
|
||||||
### Click event
|
### Click event
|
||||||
We modify the `<li>` by inserting an Angular event binding to its click event.
|
We modify the `<li>` by inserting an Angular event binding to its click event.
|
||||||
|
|
||||||
code-example(format="linenums").
|
code-example(format="linenums").
|
||||||
<li *ng-for="#hero of heroes" (click)="onSelect(hero)">
|
<li *ng-for="#hero of heroes" (click)="onSelect(hero)">
|
||||||
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
||||||
</li>
|
</li>
|
||||||
:markdown
|
:marked
|
||||||
Focus on the event binding
|
Focus on the event binding
|
||||||
pre.prettyprint.lang-bash
|
pre.prettyprint.lang-bash
|
||||||
code (click)="onSelect(hero)">
|
code (click)="onSelect(hero)">
|
||||||
:markdown
|
:marked
|
||||||
The parenthesis identify the `<li>` element’s `click` event as the target.
|
The parenthesis identify the `<li>` element’s `click` event as the target.
|
||||||
The expression to the right of the equal sign calls the `AppComponent` method, `onSelect()`,
|
The expression to the right of the equal sign calls the `AppComponent` method, `onSelect()`,
|
||||||
passing the local template variable `hero` as an argument.
|
passing the local template variable `hero` as an argument.
|
||||||
That’s the same `hero` variable we defined previously in the `ng-for`.
|
That’s the same `hero` variable we defined previously in the `ng-for`.
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
Learn more about Event Binding in the [Templating Syntax](../guide/template-syntax.html#event-binding) chapter.
|
Learn more about Event Binding in the [Templating Syntax](../guide/template-syntax.html#event-binding) chapter.
|
||||||
:markdown
|
:marked
|
||||||
### Add the click handler
|
### Add the click handler
|
||||||
Our event binding refers to an `onSelect` method that doesn’t exist yet.
|
Our event binding refers to an `onSelect` method that doesn’t exist yet.
|
||||||
We’ll add that method to our component now.
|
We’ll add that method to our component now.
|
||||||
|
|
||||||
What should that method do? It should set the component’s selected hero to the hero that the user clicked.
|
What should that method do? It should set the component’s selected hero to the hero that the user clicked.
|
||||||
|
|
||||||
Our component doesn’t have a “selected hero” yet either. We’ll start there.
|
Our component doesn’t have a “selected hero” yet either. We’ll start there.
|
||||||
|
|
||||||
### Expose the selected hero
|
### Expose the selected hero
|
||||||
We no longer need the static `hero` property of the `AppComponent`.
|
We no longer need the static `hero` property of the `AppComponent`.
|
||||||
**Replace** it with this simple `selectedHero` property:
|
**Replace** it with this simple `selectedHero` property:
|
||||||
```
|
```
|
||||||
public selectedHero: Hero;
|
public selectedHero: Hero;
|
||||||
```
|
```
|
||||||
We’ve decided that none of the heroes should be selected before the user picks a hero so
|
We’ve decided that none of the heroes should be selected before the user picks a hero so
|
||||||
we won’t initialize the `selectedHero` as we were doing with `hero`.
|
we won’t 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.
|
Now **add an `onSelect` method** that sets the `selectedHero` property to the `hero` the user clicked.
|
||||||
```
|
```
|
||||||
onSelect(hero: Hero) { this.selectedHero = hero; }
|
onSelect(hero: Hero) { this.selectedHero = hero; }
|
||||||
```
|
```
|
||||||
|
|
||||||
We will be showing the selected hero's details in our template.
|
We will be showing the selected hero's details in our template.
|
||||||
At the moment, it is still referring to the old `hero` property.
|
At the moment, it is still referring to the old `hero` property.
|
||||||
Let’s fix the template to bind to the new `selectedHero` property.
|
Let’s fix the template to bind to the new `selectedHero` property.
|
||||||
|
|
||||||
code-example(format="linenums").
|
code-example(format="linenums").
|
||||||
<h2>{{selectedHero.name}} details!</h2>
|
<h2>{{selectedHero.name}} details!</h2>
|
||||||
<div><label>id: </label>{{selectedHero.id}}</div>
|
<div><label>id: </label>{{selectedHero.id}}</div>
|
||||||
<div>
|
<div>
|
||||||
<label>name: </label>
|
<label>name: </label>
|
||||||
<input [(ng-model)]="selectedHero.name" placeholder="name"></input>
|
<input [(ng-model)]="selectedHero.name" placeholder="name"></input>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
### Hide the empty detail with ng-if
|
### Hide the empty detail with ng-if
|
||||||
|
|
||||||
When our app loads we see a list of heroes, but a hero is not selected.
|
When our app loads we see a list of heroes, but a hero is not selected.
|
||||||
The `selectedHero` is `undefined`.
|
The `selectedHero` is `undefined`.
|
||||||
That’s why we'll see the following error in the browser’s console:
|
That’s why we'll see the following error in the browser’s console:
|
||||||
|
|
||||||
code-example(language="html").
|
code-example(language="html").
|
||||||
EXCEPTION: TypeError: Cannot read property 'name' of undefined in [null]
|
EXCEPTION: TypeError: Cannot read property 'name' of undefined in [null]
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Remember that we are displaying `selectedHero.name` in the template.
|
Remember that we are displaying `selectedHero.name` in the template.
|
||||||
This name property does not exist because `selectedHero`itself is undefined.
|
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'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>`.
|
We wrap the HTML hero detail content of our template with a `<div>`.
|
||||||
Then we add the `ng-if` built-in directive and set it to the `selectedHero` property of our component.
|
Then we add the `ng-if` built-in directive and set it to the `selectedHero` property of our component.
|
||||||
|
|
||||||
code-example(format="linenums").
|
code-example(format="linenums").
|
||||||
<div *ng-if="selectedHero">
|
<div *ng-if="selectedHero">
|
||||||
<h2>{{selectedHero.name}} details!</h2>
|
<h2>{{selectedHero.name}} details!</h2>
|
||||||
|
@ -300,58 +300,58 @@ include ../../../../_includes/_util-fns
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
.alert.is-critical
|
.alert.is-critical
|
||||||
:markdown
|
:marked
|
||||||
Remember that the leading asterisk (`*`) in front of `ng-if` is
|
Remember that the leading asterisk (`*`) in front of `ng-if` is
|
||||||
a critical part of this syntax.
|
a critical part of this syntax.
|
||||||
:markdown
|
:marked
|
||||||
When there is no `selectedHero`, the `ng-if` directive removes the hero detail HTML from the DOM.
|
When there is no `selectedHero`, the `ng-if` directive removes the hero detail HTML from the DOM.
|
||||||
There will be no hero detail elements and no bindings to worry about.
|
There will be no hero detail elements and no bindings to worry about.
|
||||||
|
|
||||||
When the user picks a hero, `selectedHero` becomes "truthy" and
|
When the user picks a hero, `selectedHero` becomes "truthy" and
|
||||||
`ng-if` puts the hero detail content into the DOM and evaluates the nested bindings.
|
`ng-if` puts the hero detail content into the DOM and evaluates the nested bindings.
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
`ng-if` and `ng-for` are called “structural directives” because they can change the
|
`ng-if` and `ng-for` are called “structural directives” because they can change the
|
||||||
structure of portions of the DOM.
|
structure of portions of the DOM.
|
||||||
In other words, they give structure to the way Angular displays content in the DOM.
|
In other words, they give structure to the way Angular displays content in the DOM.
|
||||||
|
|
||||||
Learn more about `ng-if`, `ng-for` and other structural directives in the
|
Learn more about `ng-if`, `ng-for` and other structural directives in the
|
||||||
[Template Syntax](../guide/template-syntax.html#directives) chapter
|
[Template Syntax](../guide/template-syntax.html#directives) chapter
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
We learned previously with `NgFor` that we must declare every directive we use in the component’s `@Component` decorator.
|
We learned previously with `NgFor` that we must declare every directive we use in the component’s `@Component` decorator.
|
||||||
Let’s do that again for `NgIf`.
|
Let’s do that again for `NgIf`.
|
||||||
|
|
||||||
Add the `NgIf` symbol to our imports at the top of our `app.ts` file, keeping them sorted
|
Add the `NgIf` symbol to our imports at the top of our `app.ts` file, keeping them sorted
|
||||||
alphabetically to make them easier to find:
|
alphabetically to make them easier to find:
|
||||||
```
|
```
|
||||||
import {bootstrap, Component, FORM_DIRECTIVES, NgFor, NgIf} from 'angular2/angular2';
|
import {bootstrap, Component, FORM_DIRECTIVES, NgFor, NgIf} from 'angular2/angular2';
|
||||||
```
|
```
|
||||||
:markdown
|
:marked
|
||||||
Now add `NgIf` to the directives array in the `@Component` decorator:
|
Now add `NgIf` to the directives array in the `@Component` decorator:
|
||||||
```
|
```
|
||||||
directives: [FORM_DIRECTIVES, NgFor, NgIf]
|
directives: [FORM_DIRECTIVES, NgFor, NgIf]
|
||||||
```
|
```
|
||||||
The browser refreshes and we see the list of heroes but not the selected hero detail.
|
The browser refreshes and we see the list of heroes but not the selected hero detail.
|
||||||
The `ng-if` keeps it out of the DOM as long as the `selectedHero` is undefined.
|
The `ng-if` 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.
|
When we click on a hero in the list, the selected hero displays in the hero details.
|
||||||
Everything is working as we expect.
|
Everything is working as we expect.
|
||||||
|
|
||||||
### Styling the selection
|
### Styling the selection
|
||||||
|
|
||||||
We see the selected hero in the details area below but we can’t quickly locate that hero in the list above.
|
We see the selected hero in the details area below but we can’t 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.
|
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,
|
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.
|
we can make it pop out visually by giving it a subtle background color as shown here.
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/toh/heroes-list-selected.png' alt="Selected hero")
|
img(src='/resources/images/devguide/toh/heroes-list-selected.png' alt="Selected hero")
|
||||||
:markdown
|
:marked
|
||||||
First we’ll add a `getSelectedClass` method to the component that compares the current `selectedHero` to a hero parameter
|
First we’ll add a `getSelectedClass` method to the component that compares the current `selectedHero` to a hero parameter
|
||||||
and returns an object with a single key/value pair.
|
and returns an object with a single key/value pair.
|
||||||
|
|
||||||
The key is the name of the CSS class (`selected`). The value is `true` if the two heroes match and `false` otherwise.
|
The key is the name of the CSS class (`selected`). The value is `true` if the two heroes match and `false` otherwise.
|
||||||
We’re saying “*apply the `selected` class if the heroes match, remove it if they don’t*”.
|
We’re saying “*apply the `selected` class if the heroes match, remove it if they don’t*”.
|
||||||
Here is that method.
|
Here is that method.
|
||||||
```
|
```
|
||||||
getSelectedClass(hero: Hero) {
|
getSelectedClass(hero: Hero) {
|
||||||
|
@ -359,62 +359,62 @@ include ../../../../_includes/_util-fns
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
What do we do with this method and its peculiar result?
|
What do we do with this method and its peculiar result?
|
||||||
|
|
||||||
### ng-class
|
### ng-class
|
||||||
We’ll add the `ng-class`built-in directive to the `<li>` element in our template and bind it to `getSelectedClass`.
|
We’ll add the `ng-class`built-in directive to the `<li>` element in our template and bind it to `getSelectedClass`.
|
||||||
It’s no coincidence that the value returned by `getSelectedClass` is exactly what the `ng-class` requires
|
It’s no coincidence that the value returned by `getSelectedClass` is exactly what the `ng-class` requires
|
||||||
to add or remove the `selected` class to each hero’s display.
|
to add or remove the `selected` class to each hero’s display.
|
||||||
code-example(format="linenums").
|
code-example(format="linenums").
|
||||||
<li *ng-for="#hero of heroes"
|
<li *ng-for="#hero of heroes"
|
||||||
[ng-class]="getSelectedClass(hero)"
|
[ng-class]="getSelectedClass(hero)"
|
||||||
(click)="onSelect(hero)">
|
(click)="onSelect(hero)">
|
||||||
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
Notice in the template that the `ng-class` name is surrounded in square brackets (`[]`).
|
Notice in the template that the `ng-class` name is surrounded in square brackets (`[]`).
|
||||||
This is the syntax for a Property Binding, a binding in which data flows one way
|
This is the syntax for a Property Binding, a binding in which data flows one way
|
||||||
from the data source (the `getSelectedClass`) to a property of the `ng-class` directive.
|
from the data source (the `getSelectedClass`) to a property of the `ng-class` directive.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
Learn more about [ng-class](../guide/template-syntax.html#ng-class)
|
Learn more about [ng-class](../guide/template-syntax.html#ng-class)
|
||||||
and [Property Binding](../guide/template-syntax.html#property-binding)
|
and [Property Binding](../guide/template-syntax.html#property-binding)
|
||||||
in the Template Syntax chapter
|
in the Template Syntax chapter
|
||||||
:markdown
|
:marked
|
||||||
We've added yet another new directive to our template that we have to import and declare
|
We've added yet another new directive to our template that we have to import and declare
|
||||||
in the component’s `directives` array as we’ve done twice before.
|
in the component’s `directives` array as we’ve done twice before.
|
||||||
```
|
```
|
||||||
import {bootstrap, Component,
|
import {bootstrap, Component,
|
||||||
FORM_DIRECTIVES, NgClass, NgFor, NgIf} from 'angular2/angular2';
|
FORM_DIRECTIVES, NgClass, NgFor, NgIf} from 'angular2/angular2';
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
directives: [FORM_DIRECTIVES, NgClass, NgFor, NgIf]
|
directives: [FORM_DIRECTIVES, NgClass, NgFor, NgIf]
|
||||||
```
|
```
|
||||||
The browser reloads our app.
|
The browser reloads our app.
|
||||||
We select a hero and the selection is clearly identified by the background color.
|
We select a hero and the selection is clearly identified by the background color.
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/toh/heroes-list-1.png' alt="Output of heroes list app")
|
img(src='/resources/images/devguide/toh/heroes-list-1.png' alt="Output of heroes list app")
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
We select a different hero and the tell-tale color switches to that hero.
|
We select a different hero and the tell-tale color switches to that hero.
|
||||||
|
|
||||||
## Declaring Built-In Directives
|
## Declaring Built-In Directives
|
||||||
|
|
||||||
Every time we used a directive, we imported it and declared it in the component.
|
Every time we used a directive, we imported it and declared it in the component.
|
||||||
We only used three directives but we can easily envision a component that uses many more.
|
We only used three directives but we can easily envision a component that uses many more.
|
||||||
The `directives` array grows quickly and the process of importing the directive and adding it to the array is tedious.
|
The `directives` array grows quickly and the process of importing the directive and adding it to the array is tedious.
|
||||||
We can make this easier.
|
We can make this easier.
|
||||||
|
|
||||||
Remember how we imported the `FORM_DIRECTIVES` array to help us apply `ng-model`to our template in the previous chapter?
|
Remember how we imported the `FORM_DIRECTIVES` array to help us apply `ng-model`to our template in the previous chapter?
|
||||||
The `FORM_DIRECTIVES` array held all the directives we needed for `ng-model` (and a few more).
|
The `FORM_DIRECTIVES` array held all the directives we needed for `ng-model` (and a few more).
|
||||||
We didn’t have to list them. We simply added the `FORM_DIRECTIVES` array to the component’s `directives` array.
|
We didn’t have to list them. We simply added the `FORM_DIRECTIVES` array to the component’s `directives` array.
|
||||||
|
|
||||||
The `NgClass`, `NgFor`, and `NgIf` are extremely common directives used by many components in many applications.
|
The `NgClass`, `NgFor`, and `NgIf` are extremely common directives used by many components in many applications.
|
||||||
Fortunately they are all exported from Angular as part of the `CORE_DIRECTIVES` array.
|
Fortunately they are all exported from Angular as part of the `CORE_DIRECTIVES` array.
|
||||||
|
|
||||||
Let’s replace all of those separate import variables with `CORE_DIRECTIVES`:
|
Let’s replace all of those separate import variables with `CORE_DIRECTIVES`:
|
||||||
```
|
```
|
||||||
import {bootstrap, Component, CORE_DIRECTIVES, FORM_DIRECTIVES} from 'angular2/angular2';
|
import {bootstrap, Component, CORE_DIRECTIVES, FORM_DIRECTIVES} from 'angular2/angular2';
|
||||||
|
@ -423,20 +423,19 @@ include ../../../../_includes/_util-fns
|
||||||
```
|
```
|
||||||
directives: [CORE_DIRECTIVES, FORM_DIRECTIVES]
|
directives: [CORE_DIRECTIVES, FORM_DIRECTIVES]
|
||||||
```
|
```
|
||||||
Everything still works and we have a convenient way to import and declare the most commonly used directives.
|
Everything still works and we have a convenient way to import and declare the most commonly used directives.
|
||||||
Cleaner code for the win!
|
Cleaner code for the win!
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## The Road We’ve Travelled
|
## The Road We’ve Travelled
|
||||||
Here’s what we achieved in this chapter:
|
Here’s what we achieved in this chapter:
|
||||||
|
|
||||||
* Our Tour of Heroes now displays a list of selectable heroes
|
* Our Tour of Heroes now displays a list of selectable heroes
|
||||||
* We added the ability to select a hero and show the hero’s details
|
* We added the ability to select a hero and show the hero’s details
|
||||||
* We learned how to use the built-in directives `ng-if`, `ng-for` and `ng-class` in a component’s template
|
* We learned how to use the built-in directives `ng-if`, `ng-for` and `ng-class` in a component’s template
|
||||||
|
|
||||||
### The Road Ahead
|
### The Road Ahead
|
||||||
Our Tour of Heroes has grown, but it’s far from complete.
|
Our Tour of Heroes has grown, but it’s far from complete.
|
||||||
We want to get data from an asynchronous source using promises, use shared services, and create reusable components.
|
We want to get data from an asynchronous source using promises, use shared services, and create reusable components.
|
||||||
We’ll learn more about these tasks in the coming tutorial chapters.
|
We’ll learn more about these tasks in the coming tutorial chapters.
|
||||||
|
|
|
@ -22,14 +22,14 @@ module.exports = function(encodeCodeBlock) {
|
||||||
newLines.pop();
|
newLines.pop();
|
||||||
} else {
|
} else {
|
||||||
// wierd case - first expression in str is an @example
|
// wierd case - first expression in str is an @example
|
||||||
// in this case the :markdown appear above the str passed in,
|
// in this case the :marked appear above the str passed in,
|
||||||
// so we need to put 'something' into the markdown tag.
|
// so we need to put 'something' into the markdown tag.
|
||||||
newLines.push(sp + "."); // '.' is a dummy char
|
newLines.push(sp + "."); // '.' is a dummy char
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newLines.push(spMixin + line);
|
newLines.push(spMixin + line);
|
||||||
// after a mixin line we need to reenter markdown.
|
// after a mixin line we need to reenter markdown.
|
||||||
newLines.push(spMixin + ':markdown');
|
newLines.push(spMixin + ':marked');
|
||||||
isAfterMarkdownTag = true;
|
isAfterMarkdownTag = true;
|
||||||
} else {
|
} else {
|
||||||
if ((!isAfterMarkdownTag) || (line.trim().length > 0)) {
|
if ((!isAfterMarkdownTag) || (line.trim().length > 0)) {
|
||||||
|
|
|
@ -14,7 +14,7 @@ p.location-badge.
|
||||||
exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }
|
exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }
|
||||||
defined in {$ githubViewLink(doc) $}
|
defined in {$ githubViewLink(doc) $}
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
{%- if doc.notYetDocumented %}
|
{%- if doc.notYetDocumented %}
|
||||||
*Not Yet Documented*
|
*Not Yet Documented*
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -43,7 +43,7 @@ p.location-badge.
|
||||||
pre.prettyprint
|
pre.prettyprint
|
||||||
code.
|
code.
|
||||||
{$ doc.constructorDoc.name $}{$ paramList(doc.constructorDoc.parameters) | indent(8, false) | trim $}
|
{$ doc.constructorDoc.name $}{$ paramList(doc.constructorDoc.parameters) | indent(8, false) | trim $}
|
||||||
:markdown
|
:marked
|
||||||
{$ doc.constructorDoc.description | indentForMarkdown(6) | replace('## Example', '') | replace('# Example', '') | trimBlankLines $}
|
{$ doc.constructorDoc.description | indentForMarkdown(6) | replace('## Example', '') | replace('# Example', '') | trimBlankLines $}
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ p.location-badge.
|
||||||
code.
|
code.
|
||||||
{$ member.name $}{$ paramList(member.parameters) | indent(8, false) | trim $}{$ returnType(member.returnType) $}
|
{$ member.name $}{$ paramList(member.parameters) | indent(8, false) | trim $}{$ returnType(member.returnType) $}
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
{$ member.description | indentForMarkdown(6) | replace('## Example', '') | replace('# Example', '') | trimBlankLines $}
|
{$ member.description | indentForMarkdown(6) | replace('## Example', '') | replace('# Example', '') | trimBlankLines $}
|
||||||
|
|
||||||
{% endif %}{% endfor %}
|
{% endif %}{% endfor %}
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
code {$ property.bindingName | dashCase $}
|
code {$ property.bindingName | dashCase $}
|
||||||
| bound to
|
| bound to
|
||||||
code {$ property.memberDoc.classDoc.name $}.{$ property.propertyName $}
|
code {$ property.memberDoc.classDoc.name $}.{$ property.propertyName $}
|
||||||
:markdown
|
:marked
|
||||||
{$ property.memberDoc.description | indentForMarkdown(2) | trimBlankLines $}{% endfor %}
|
{$ property.memberDoc.description | indentForMarkdown(2) | trimBlankLines $}{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
code {$ property.bindingName | dashCase $}
|
code {$ property.bindingName | dashCase $}
|
||||||
| bound to
|
| bound to
|
||||||
code {$ property.memberDoc.classDoc.name $}.{$ property.propertyName $}
|
code {$ property.memberDoc.classDoc.name $}.{$ property.propertyName $}
|
||||||
:markdown
|
:marked
|
||||||
{$ event.memberDoc.description | indentForMarkdown(2) | trimBlankLines $}
|
{$ event.memberDoc.description | indentForMarkdown(2) | trimBlankLines $}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -14,7 +14,7 @@ include ../../_util-fns
|
||||||
exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }
|
exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }
|
||||||
defined in {$ githubViewLink(doc) $}
|
defined in {$ githubViewLink(doc) $}
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
{$ doc.description | indentForMarkdown(4) | trimBlankLines $}
|
{$ doc.description | indentForMarkdown(4) | trimBlankLines $}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -14,7 +14,7 @@ include ../../_util-fns
|
||||||
exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }
|
exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }
|
||||||
defined in {$ githubViewLink(doc) $}
|
defined in {$ githubViewLink(doc) $}
|
||||||
|
|
||||||
:markdown
|
:marked
|
||||||
{%- if doc.notYetDocumented %}
|
{%- if doc.notYetDocumented %}
|
||||||
### *Not Yet Documented*
|
### *Not Yet Documented*
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
|
@ -49,7 +49,7 @@ module.exports = function convertBackticksToCodeBlocks() {
|
||||||
// modulo op in next line insures that pad is always a multiple of 2 ( jade whitespace).
|
// modulo op in next line insures that pad is always a multiple of 2 ( jade whitespace).
|
||||||
postPad = postPad.substr(2 + (postPad.length % 2)); // exdent
|
postPad = postPad.substr(2 + (postPad.length % 2)); // exdent
|
||||||
}
|
}
|
||||||
replaceVal = replaceVal + postPad + ':markdown\n';
|
replaceVal = replaceVal + postPad + ':marked\n';
|
||||||
}
|
}
|
||||||
doc.renderedContent = doc.renderedContent.replace(entireBlock, replaceVal);
|
doc.renderedContent = doc.renderedContent.replace(entireBlock, replaceVal);
|
||||||
captures = BACKTICK_CAPTURE.exec(doc.renderedContent);
|
captures = BACKTICK_CAPTURE.exec(doc.renderedContent);
|
||||||
|
|
|
@ -30,7 +30,7 @@ describe('convertBackticksToCodeBlocks', function() {
|
||||||
'export class TypeScriptClass {\n' +
|
'export class TypeScriptClass {\n' +
|
||||||
'}\n' +
|
'}\n' +
|
||||||
'\n' +
|
'\n' +
|
||||||
':markdown\n' +
|
':marked\n' +
|
||||||
'postamble\n'
|
'postamble\n'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
include ../../../../_includes/_util-fns
|
include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Install Angular2
|
## Install Angular2
|
||||||
There are four steps to create any Angular app:
|
There are four steps to create any Angular app:
|
||||||
|
|
||||||
|
@ -19,33 +19,33 @@ include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
.callout.is-helpful
|
.callout.is-helpful
|
||||||
header Typescript vs ES5
|
header Typescript vs ES5
|
||||||
:markdown
|
:marked
|
||||||
Although we work through the examples in TypeScript, you can also use
|
Although we work through the examples in TypeScript, you can also use
|
||||||
regular ES5. Click the ES5 link in any code box to see the ES5 JavaScript
|
regular ES5. Click the ES5 link in any code box to see the ES5 JavaScript
|
||||||
version. Note that in ES5, you'd want to name your files `.js` rather than
|
version. Note that in ES5, you'd want to name your files `.js` rather than
|
||||||
`.ts`.
|
`.ts`.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Create an entry point
|
## Create an entry point
|
||||||
Create an `index.html` file and add the Angular library tags and a `main.ts` file where
|
Create an `index.html` file and add the Angular library tags and a `main.ts` file where
|
||||||
you'll build your first component.
|
you'll build your first component.
|
||||||
|
|
||||||
In the `<body>`, add an element called `<my-app>` that will be the root of your
|
In the `<body>`, add an element called `<my-app>` that will be the root of your
|
||||||
application.
|
application.
|
||||||
|
|
||||||
The TypeScript setup includes System.js, a third-party open-source library that adds ES6 module loading functionality to browsers. This step isn't needed for the ES5 version.
|
The TypeScript setup includes System.js, a third-party open-source library that adds ES6 module loading functionality to browsers. This step isn't needed for the ES5 version.
|
||||||
|
|
||||||
+makeTabs('gettingstarted', 'ts/index.html,js/index.html', 'TypeScript, JavaScript')
|
+makeTabs('gettingstarted', 'ts/index.html,js/index.html', 'TypeScript, JavaScript')
|
||||||
|
|
||||||
.callout.is-helpful
|
.callout.is-helpful
|
||||||
header Don't use code.angularjs.org in a live app
|
header Don't use code.angularjs.org in a live app
|
||||||
:markdown
|
:marked
|
||||||
This example serves the Angular library from <a href="http://code.angularjs.org">code.angularjs.org</a>. This is
|
This example serves the Angular library from <a href="http://code.angularjs.org">code.angularjs.org</a>. This is
|
||||||
fine for examples, but you'd want to serve it yourself or use a CDN for real deployment.
|
fine for examples, but you'd want to serve it yourself or use a CDN for real deployment.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Set up the starting component
|
## Set up the starting component
|
||||||
|
|
||||||
In `main.ts`, create a class called `AppComponent`, configure it to bind to the
|
In `main.ts`, create a class called `AppComponent`, configure it to bind to the
|
||||||
|
@ -56,14 +56,14 @@ include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
.callout.is-helpful
|
.callout.is-helpful
|
||||||
header Annotations vs Decorators
|
header Annotations vs Decorators
|
||||||
:markdown
|
:marked
|
||||||
If you are transpiling using a tool that translates the `@` symbols to
|
If you are transpiling using a tool that translates the `@` symbols to
|
||||||
annotations (for example Traceur), you will need to import the annotation versions of
|
annotations (for example Traceur), you will need to import the annotation versions of
|
||||||
Component and View. That can be easily achieved using
|
Component and View. That can be easily achieved using
|
||||||
`import {ComponentAnnotation as Component, ViewAnnotation as View}`.
|
`import {ComponentAnnotation as Component, ViewAnnotation as View}`.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Run it!
|
## Run it!
|
||||||
|
|
||||||
Open `index.html` through your web server and you should see:
|
Open `index.html` through your web server and you should see:
|
||||||
|
@ -72,13 +72,13 @@ include ../../../../_includes/_util-fns
|
||||||
img(src='/resources/images/examples/setup-example1.png' alt="Example of Todo App")
|
img(src='/resources/images/examples/setup-example1.png' alt="Example of Todo App")
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Explanations
|
## Explanations
|
||||||
|
|
||||||
This basic Angular app contains the structure for any app you'll build.
|
This basic Angular app contains the structure for any app you'll build.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
### It's all a tree
|
### It's all a tree
|
||||||
|
|
||||||
You can think of Angular apps as a tree of components. This root component we've been talking about acts as the top
|
You can think of Angular apps as a tree of components. This root component we've been talking about acts as the top
|
||||||
|
@ -94,7 +94,7 @@ include ../../../../_includes/_util-fns
|
||||||
these in the following pages.
|
these in the following pages.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
### @Component and @View annotations
|
### @Component and @View annotations
|
||||||
|
|
||||||
A component annotation describes details about the component. An annotation can be identified by its at-sign (`@`).
|
A component annotation describes details about the component. An annotation can be identified by its at-sign (`@`).
|
||||||
|
@ -104,7 +104,7 @@ include ../../../../_includes/_util-fns
|
||||||
The `@View` annotation defines the HTML that represents the component. The component you wrote uses an inline template, but you can also have an external template. To use an external template, specify a <code>templateUrl</code> property and give it the path to the HTML file.
|
The `@View` annotation defines the HTML that represents the component. The component you wrote uses an inline template, but you can also have an external template. To use an external template, specify a <code>templateUrl</code> property and give it the path to the HTML file.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
### import vs. window.angular
|
### import vs. window.angular
|
||||||
|
|
||||||
The main difference between the ES5 and TypeScript versions is the loading of modules.
|
The main difference between the ES5 and TypeScript versions is the loading of modules.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
include ../../../../_includes/_util-fns
|
include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Install Angular2
|
## Install Angular2
|
||||||
There are four steps to create any Angular app:
|
There are four steps to create any Angular app:
|
||||||
|
|
||||||
|
@ -19,14 +19,14 @@ include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
.callout.is-helpful
|
.callout.is-helpful
|
||||||
header Typescript vs ES5
|
header Typescript vs ES5
|
||||||
:markdown
|
:marked
|
||||||
Although we work through the examples in TypeScript, you can also use
|
Although we work through the examples in TypeScript, you can also use
|
||||||
regular ES5. Click the ES5 link in any code box to see the ES5 JavaScript
|
regular ES5. Click the ES5 link in any code box to see the ES5 JavaScript
|
||||||
version. Note that in ES5, you'd want to name your files `.js` rather than
|
version. Note that in ES5, you'd want to name your files `.js` rather than
|
||||||
`.ts`.
|
`.ts`.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Create an entry point
|
## Create an entry point
|
||||||
Create an `index.html` file and add the Angular library tags and a `main.ts` file where
|
Create an `index.html` file and add the Angular library tags and a `main.ts` file where
|
||||||
you'll build your first component.
|
you'll build your first component.
|
||||||
|
@ -40,12 +40,12 @@ include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
.callout.is-helpful
|
.callout.is-helpful
|
||||||
header Don't use code.angularjs.org in a live app
|
header Don't use code.angularjs.org in a live app
|
||||||
:markdown
|
:marked
|
||||||
This example serves the Angular library from <a href="http://code.angularjs.org">code.angularjs.org</a>. This is
|
This example serves the Angular library from <a href="http://code.angularjs.org">code.angularjs.org</a>. This is
|
||||||
fine for examples, but you'd want to serve it yourself or use a CDN for real deployment.
|
fine for examples, but you'd want to serve it yourself or use a CDN for real deployment.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Set up the starting component
|
## Set up the starting component
|
||||||
|
|
||||||
In `main.ts`, create a class called `AppComponent`, configure it to bind to the
|
In `main.ts`, create a class called `AppComponent`, configure it to bind to the
|
||||||
|
@ -56,14 +56,14 @@ include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
.callout.is-helpful
|
.callout.is-helpful
|
||||||
header Annotations vs Decorators
|
header Annotations vs Decorators
|
||||||
:markdown
|
:marked
|
||||||
If you are transpiling using a tool that translates the `@` symbols to
|
If you are transpiling using a tool that translates the `@` symbols to
|
||||||
annotations (for example Traceur), you will need to import the annotation versions of
|
annotations (for example Traceur), you will need to import the annotation versions of
|
||||||
Component and View. That can be easily achieved using
|
Component and View. That can be easily achieved using
|
||||||
`import {ComponentAnnotation as Component, ViewAnnotation as View}`.
|
`import {ComponentAnnotation as Component, ViewAnnotation as View}`.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Run it!
|
## Run it!
|
||||||
|
|
||||||
Open `index.html` through your web server and you should see:
|
Open `index.html` through your web server and you should see:
|
||||||
|
@ -72,13 +72,13 @@ include ../../../../_includes/_util-fns
|
||||||
img(src='/resources/images/examples/setup-example1.png' alt="Example of Todo App")
|
img(src='/resources/images/examples/setup-example1.png' alt="Example of Todo App")
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:markdown
|
:marked
|
||||||
## Explanations
|
## Explanations
|
||||||
|
|
||||||
This basic Angular app contains the structure for any app you'll build.
|
This basic Angular app contains the structure for any app you'll build.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
### It's all a tree
|
### It's all a tree
|
||||||
|
|
||||||
You can think of Angular apps as a tree of components. This root component we've been talking about acts as the top
|
You can think of Angular apps as a tree of components. This root component we've been talking about acts as the top
|
||||||
|
@ -94,7 +94,7 @@ include ../../../../_includes/_util-fns
|
||||||
these in the following pages.
|
these in the following pages.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
### @Component and @View annotations
|
### @Component and @View annotations
|
||||||
|
|
||||||
A component annotation describes details about the component. An annotation can be identified by its at-sign (`@`).
|
A component annotation describes details about the component. An annotation can be identified by its at-sign (`@`).
|
||||||
|
@ -104,7 +104,7 @@ include ../../../../_includes/_util-fns
|
||||||
The `@View` annotation defines the HTML that represents the component. The component you wrote uses an inline template, but you can also have an external template. To use an external template, specify a <code>templateUrl</code> property and give it the path to the HTML file.
|
The `@View` annotation defines the HTML that represents the component. The component you wrote uses an inline template, but you can also have an external template. To use an external template, specify a <code>templateUrl</code> property and give it the path to the HTML file.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:markdown
|
:marked
|
||||||
### import vs. window.angular
|
### import vs. window.angular
|
||||||
|
|
||||||
The main difference between the ES5 and TypeScript versions is the loading of modules.
|
The main difference between the ES5 and TypeScript versions is the loading of modules.
|
||||||
|
|
Loading…
Reference in New Issue