docs(ts-to-js): add separate template files for some components

This commit is contained in:
Ward Bell 2016-11-11 19:44:00 -08:00
parent 33b61977b2
commit b11438f211
18 changed files with 148 additions and 96 deletions

View File

@ -0,0 +1,6 @@
<button (click)="onOkClick()">
{{okMsg}}
</button>
<button (click)="onNotOkClick()">
{{notOkMsg}}
</button>

View File

@ -8,14 +8,13 @@ import {
import { BrowserModule } from '@angular/platform-browser';
// #docregion
// #docregion metadata
@Component({
moduleId: module.id,
selector: 'hero-title',
template: `
<h1>{{titlePrefix}} {{title}}</h1>
<button (click)="ok()">OK</button>
<p>{{ msg }}</p>
`
templateUrl: 'title.component.html'
})
// #enddocregion metadata
class TitleComponent {
msg = '';
constructor(

View File

@ -9,15 +9,9 @@ import { BrowserModule } from '@angular/platform-browser';
// #docregion
@Component({
moduleId: module.id,
selector: 'my-confirm',
template: `
<button (click)="onOkClick()">
{{okMsg}}
</button>
<button (click)="onNotOkClick()">
{{notOkMsg}}
</button>
`
templateUrl: 'confirm.component.html'
})
class ConfirmComponent {
@Input() okMsg;

View File

@ -0,0 +1,3 @@
<h1>{{titlePrefix}} {{title}}</h1>
<button (click)="ok()">OK</button>
<p>{{ msg }}</p>

View File

@ -0,0 +1,6 @@
<button (click)="onOkClick()">
{{okMsg}}
</button>
<button (click)="onNotOkClick()">
{{notOkMsg}}
</button>

View File

@ -20,16 +20,15 @@ class TitleComponent {
}
}
// #docregion metadata
TitleComponent.annotations = [
new Component({
moduleId: module.id,
selector: 'hero-title',
template: `
<h1>{{titlePrefix}} {{title}}</h1>
<button (click)="ok()">OK</button>
<p>{{ msg }}</p>
`
templateUrl: 'title.component.html'
})
];
// #enddocregion metadata
TitleComponent.parameters = [
[new Optional(), new Inject('titlePrefix')],

View File

@ -23,15 +23,9 @@ class ConfirmComponent {
ConfirmComponent.annotations = [
new Component({
moduleId: module.id,
selector: 'my-confirm',
template: `
<button (click)="onOkClick()">
{{okMsg}}
</button>
<button (click)="onNotOkClick()">
{{notOkMsg}}
</button>
`,
templateUrl: 'confirm.component.html',
inputs: [
'okMsg',
'notOkMsg: cancelMsg'

View File

@ -0,0 +1,3 @@
<h1>{{titlePrefix}} {{title}}</h1>
<button (click)="ok()">OK</button>
<p>{{ msg }}</p>

View File

@ -0,0 +1,6 @@
<button (click)="onOkClick()">
{{okMsg}}
</button>
<button (click)="onNotOkClick()">
{{notOkMsg}}
</button>

View File

@ -1,13 +1,13 @@
(function(app) {
// #docregion
// #docregion metadata
var TitleComponent = ng.core.Component({
selector: 'hero-title',
template:
'<h1>{{titlePrefix}} {{title}}</h1>' +
'<button (click)="ok()">OK</button>' +
'<p>{{ msg }}</p>'
}).Class({
templateUrl: 'app/title.component.html'
})
// #enddocregion metadata
.Class({
constructor: [
[ new ng.core.Optional(), new ng.core.Inject('titlePrefix') ],
new ng.core.Attribute('title'),

View File

@ -2,6 +2,7 @@
// #docregion
var ConfirmComponent = ng.core.Component({
selector: 'my-confirm',
templateUrl: 'app/confirm.component.html',
inputs: [
'okMsg',
'notOkMsg: cancelMsg'
@ -9,14 +10,7 @@
outputs: [
'ok',
'notOk: cancel'
],
template:
'<button (click)="onOkClick()">' +
'{{okMsg}}' +
'</button>' +
'<button (click)="onNotOkClick()">' +
'{{notOkMsg}}' +
'</button>'
]
}).Class({
constructor: function() {
this.ok = new ng.core.EventEmitter();

View File

@ -0,0 +1,3 @@
<h1>{{titlePrefix}} {{title}}</h1>
<button (click)="ok()">OK</button>
<p>{{ msg }}</p>

View File

@ -16,7 +16,6 @@
<script src="node_modules/@angular/compiler/bundles/compiler.umd.js"></script>
<script src="node_modules/@angular/platform-browser/bundles/platform-browser.umd.js"></script>
<script src="node_modules/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js"></script>
<script src="node_modules/@angular/router-deprecated/bundles/router-deprecated.umd.js"></script>
<script src="app/data.service.js"></script>
<script src="app/hero.component.js"></script>

View File

@ -0,0 +1,6 @@
<button (click)="onOkClick()">
{{okMsg}}
</button>
<button (click)="onNotOkClick()">
{{notOkMsg}}
</button>

View File

@ -8,14 +8,13 @@ import {
import { BrowserModule } from '@angular/platform-browser';
// #docregion
// #docregion metadata
@Component({
moduleId: module.id,
selector: 'hero-title',
template: `
<h1>{{titlePrefix}} {{title}}</h1>
<button (click)="ok()">OK</button>
<p>{{ msg }}</p>
`
templateUrl: 'title.component.html'
})
// #enddocregion metadata
class TitleComponent {
private msg: string = '';
constructor(

View File

@ -9,15 +9,9 @@ import { BrowserModule } from '@angular/platform-browser';
// #docregion
@Component({
moduleId: module.id,
selector: 'my-confirm',
template: `
<button (click)="onOkClick()">
{{okMsg}}
</button>
<button (click)="onNotOkClick()">
{{notOkMsg}}
</button>
`
templateUrl: 'confirm.component.html'
})
class ConfirmComponent {
@Input() okMsg: string;

View File

@ -0,0 +1,4 @@
<!-- #docregion -->
<h1>{{titlePrefix}} {{title}}</h1>
<button (click)="ok()">OK</button>
<p>{{ msg }}</p>

View File

@ -80,7 +80,7 @@ a#modularity
In both _TypeScript_ and _ES6_, you import Angular classes, functions, and other members with _ES6_ `import` statements.
In _ES5_, you access the Angular entities of the [the Angular packages](../glossary.html#!#scoped-package),
In _ES5_, you access the Angular entities of the [the Angular packages](../glossary.html#!#scoped-package)
through the global `ng` object.
Everything you would have imported from `@angular` is a nested member of this `ng` object:
@ -102,23 +102,29 @@ a#modularity
`)
:marked
### Importing and Exporting Application Code
### Exporting Application Code
Each file in a _TypeScript_ or _ES6_ Angular application constitutes an _ES6_ module.
When you want to make something available to other modules, you `export` it.
_ES5_ lacks native support for modules.
In an Angular _ES5_ application, you load each file manually by adding a `<script>` tag to the host web page, `index.html`.
In an Angular _ES5_ application, you load each file manually by adding a `<script>` tag to `index.html`.
.alert.is-important
:marked
The order of `<script>` tags is often significant.
You must load a file that defines a public JavaScript entity before a file that references that entity.
:marked
The best practice in _ES5_ is to create a form of modularity that avoids polluting the global scope.
Add one application namespace object such as `app` to the global `document`.
Then each code file "exports" public entities by attaching them to that namespace object, e.g., `app.HeroComponent`.
You could factor a large application into several sub-namespaces
which leads to "exports" along the lines of `app.heroes.HeroComponent`.
Each code file can make selected entities available to other files via the shared global `window` scope.
But this is widely thought to be a bad practice.
Instead, you should add one application namespace object (such as `app`) to `window`.
Then attach everything you need to share to that namespace object.
Every file should wrap _ES5_ code in an
[Immediately Invoked Function Expression (IIFE)](https://en.wikipedia.org/wiki/Immediately-invoked_function_expression)
to limit unintentional leaking of private symbols into the global scope.
You should also wrap your code in an
[Immediately Invoked Function Expression (IIFE)](https://en.wikipedia.org/wiki/Immediately-invoked_function_expression).
These practices together help you avoid polluting the global scope.
Here is a `HeroComponent` as it might be defined and "exported" in each of the four language variants.
+makeTabs(`
cb-ts-to-js/ts/app/hero.component.ts,
@ -138,11 +144,11 @@ a#modularity
`)
:marked
### Importing Application Code
In _TypeScript_ and _ES6_ apps, you `import` things that have been exported from other modules.
In _ES5_ you use the shared namespace object to access "exported" entities in other files.
Note that the order of `<script>` tags on the page is often significant.
You must load a file that defines a shared member before a file that uses that member.
In _ES5_ you use the shared namespace object to access "exported" entities from other files.
+makeTabs(`
cb-ts-to-js/ts/app/main.ts,
@ -180,8 +186,7 @@ a#class-metadata
In _ES6-without-decorators_, properties of classes must be assigned inside the constructor.
_ES5_ JavaScript has no classes.
Use the constructor function pattern instead adding methods to the prototype.
This works with Angular as well as classes do.
Use the constructor function pattern instead, adding methods to the prototype.
+makeTabs(`
cb-ts-to-js/ts/app/hero.component.ts,
@ -206,12 +211,13 @@ a#class-metadata
When writing in _TypeScript_ or _ES6-with-decorators_,
provide configuration and metadata by adorning a class with one or more *decorators*.
For example, you supply metadata to a component class by preceding its definition with a
[`@Component`](../api/core/index/Component-decorator.html) decorator.
[`@Component`](../api/core/index/Component-decorator.html) decorator function whose
argument is an object literal with metadata properties.
In _plain ES6_, you provide metadata by attaching an `annotations` array to the _class_.
Each item in the array corresponds to a decorator property value.
Each item in the array is a new instance of a metadata decorator created with a similar metadata object literal.
In _ES5_, you also provide an `annotations` array but you attach it to the _constructor function_ rather than a class.
In _ES5_, you also provide an `annotations` array but you attach it to the _constructor function_ rather than to a class.
See these variations side-by-side:
@ -233,11 +239,41 @@ a#class-metadata
`)
:marked
***External Template file***
A large component template is often kept in a separate template file.
+makeExample('cb-ts-to-js/ts/app/title.component.html', '', 'app/title.component.html')(format='.')
:marked
The component (`TitleComponent` in this case) then references the template file in its metadata `templateUrl` property:
+makeTabs(`
cb-ts-to-js/ts/app/hero-di-inject-additional.component.ts,
cb-ts-to-js/js-es6-decorators/app/hero-di-inject-additional.component.es6,
cb-ts-to-js/js-es6/app/hero-di-inject-additional.component.es6,
cb-ts-to-js/js/app/hero-di-inject-additional.component.js`,
'metadata, metadata, metadata, metadata',
`TypeScript,
ES6 JavaScript with decorators,
ES6 JavaScript,
ES5 JavaScript`)(format='.')
:marked
Note that the _TypeScript_ and both _ES6_ file `templateUrl` properties identify the location of the template file _relative to the component module_.
All three metadata configurations specify the `moduleId` property
so that Angular can calculate the proper module address.
.l-sub-section
:marked
The `moduleId` may not be needed with certain tooling but it's safest to provide it anyway.
:marked
The _ES5_ approach shown here does not support modules and therefore there is no way to calculate a _module-relative URL_.
The `templateUrl` for the _ES5_ code must specify the _path from the project root_ and
omits the irrelevant `moduleId` property.
***Angular class API***
This _ES5_ pattern of creating a constructor and annotating it with metadata is so common that Angular
provides a convenience API to make it a little more compact and put the metadata first,
as it would be if you wrote in _TypeScript_ or _ES6-with-decorators_.
provides a convenience API to make it a little more compact and locates the metadata above the constructor,
as you would if you wrote in _TypeScript_ or _ES6-with-decorators_.
Set a component variable to the result of an `ng.core.Component` function call.
Pass the same metadata object to `ng.core.Component` as you did before.
@ -278,14 +314,14 @@ code-example.
:marked
### Interfaces
When defining classes that need to implement a certain method, it is common to use _TypeScript_
interfaces that enforce that the method signature is correct.
Component lifecycle methods like `ngOnInit` are one example of this pattern.
`ngOnInit` is defined in the `OnInit` interface.
A _TypeScript_ interface helps ensure that a class implements the interface's members correctly.
We strongly recommend Angular interfaces where appropriate.
For example, a component that implements the `ngOnInit` lifecycle hook method
should implement the `OnInit` interface.
_TypeScript_ interfaces are purely for developer convenience and are not used by Angular at runtime.
This means that in _ES5_/_ES6_ JavaScript code you don't need to substitute anything for interfaces.
Just implement the methods.
_TypeScript_ interfaces exist for developer convenience and are not used by Angular at runtime.
They have no physical manifestation in the generated JavaScript code.
Just implement the methods and ignore interfaces when translating code samples from _TypeScript_ to JavaScript.
+makeTabs(`
cb-ts-to-js/ts/app/hero-lifecycle.component.ts,
@ -307,21 +343,19 @@ a#property-metadata
### Input and Output Decorators
In _TypeScript_ and _ES6_ with decorators, property decorators are often used to provide additional
metadata for components and directives.
For [inputs and outputs](../guide/template-syntax.html#inputs-outputs), you use `@Input` and
`@Output` property decorators.
Specify input and output binding names if you want the public-facing names to differ
from the class property names.
In _TypeScript_ and _ES6-with-decorators_, you often add metadata to class _properties_ with _property decorators_.
For example, you apply [`@Input` and `@Output` property decorators](../guide/template-syntax.html#inputs-outputs)
to public class properties that will be the target of data binding expressions in parent components.
There is no equivalent of a property decorator in _ES5_/_ES6_ JavaScript.
Instead, you add comparable information to the `Component` (or `Directive`) metadata.
In this example, you add `inputs` and `outputs` array attributes containing the input and
output property names.
If you need a binding name that is different from the property itself, use the
`propertyName: bindingName` syntax.
There is no equivalent of a property decorator in _ES5_ or _plain ES6_.
Fortunately, every property decorator has an equivalent representation in a class decorator metadata property.
A _TypeScript_ `@Input` property decorator can be represented by an item in the `Component` metadata's `inputs` array.
You already know how to add `Component` or `Directive` class metadata in _any_ JavaScript dialect so
there's nothing fundamentally new about adding another property.
But note that what would have been _separate_ `@Input` and `@Output` property decorators for each class property are
combined in the metadata `inputs` and `outputs` _arrays_.
+makeTabs(`
cb-ts-to-js/ts/app/hero-io.component.ts,
cb-ts-to-js/js-es6-decorators/app/hero-io.component.es6,
@ -334,7 +368,16 @@ a#property-metadata
ES6 JavaScript,
ES5 JavaScript
`)
:marked
In the previous example, one of the public-facing binding names (`cancelMsg`)
differs from the corresponding class property name (`notOkMsg`).
That's OK but you must tell Angular about it so that it can map an external binding of `cancelMsg`
to the component's `notOkMsg` property.
In _TypeScript_ and _ES6-with-decorators_,
you specify the special binding name in the argument to the property decorator.
In _ES5_ and _plain ES6_ code, convey this pairing with the `propertyName: bindingName` syntax in the class metadata.
.l-main-section
:marked
## Dependency Injection
@ -342,7 +385,7 @@ a#property-metadata
### Injection by Type
Angular can often use _TypeScript_ type information to determine what needs to be injected.
_ES6_ with decorators can also make use of type information.
_ES6-with-decorators_ can also make use of type information.
Since no type information is available in _ES5_/_ES6_ JavaScript, you must identify "injectables" in
some other way.