Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
7e94405271 | |||
6076a8d7bb | |||
a1624f217c | |||
b2f4d53bf0 | |||
7662cefe6f | |||
1cb607697a | |||
1990c3c722 | |||
b589d85d6f | |||
03ec3a2169 | |||
a5baed6b97 | |||
259fc91305 | |||
a618d6e4ce | |||
b315a84ba0 | |||
972538be7a | |||
d7be4f12b5 | |||
b9c1c913c1 |
17
CHANGELOG.md
17
CHANGELOG.md
@ -1,3 +1,20 @@
|
||||
<a name="4.3.5"></a>
|
||||
## [4.3.5](https://github.com/angular/angular/compare/4.3.4...4.3.5) (2017-08-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **aio:** skip PWA test when redeploying non-public commit ([b9c1c91](https://github.com/angular/angular/commit/b9c1c91))
|
||||
* **core:** forbid destroyed views to be inserted or moved in VC ([972538b](https://github.com/angular/angular/commit/972538b)), closes [#18615](https://github.com/angular/angular/issues/18615)
|
||||
* **forms:** re-assigning options should not clear select ([a1624f2](https://github.com/angular/angular/commit/a1624f2)), closes [#18330](https://github.com/angular/angular/issues/18330)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **aio:** update to new version of build-optimizer ([d7be4f1](https://github.com/angular/angular/commit/d7be4f1))
|
||||
|
||||
|
||||
|
||||
<a name="4.3.4"></a>
|
||||
## [4.3.4](https://github.com/angular/angular/compare/4.3.3...4.3.4) (2017-08-10)
|
||||
|
||||
|
@ -32,7 +32,8 @@ export class BuildCreator extends EventEmitter {
|
||||
then(() => Promise.all([this.exists(prDir), this.exists(shaDir)])).
|
||||
then(([prDirExisted, shaDirExisted]) => {
|
||||
if (shaDirExisted) {
|
||||
throw new UploadError(409, `Request to overwrite existing directory: ${shaDir}`);
|
||||
const publicOrNot = isPublic ? 'public' : 'non-public';
|
||||
throw new UploadError(409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`);
|
||||
}
|
||||
|
||||
dirToRemoveOnError = prDirExisted ? shaDir : prDir;
|
||||
|
@ -110,6 +110,7 @@ describe('upload-server (on HTTP)', () => {
|
||||
const authorizationHeader2 = isPublic ?
|
||||
authorizationHeader : `--header "Authorization: ${c.BV_verify_verifiedNotTrusted}"`;
|
||||
const cmdPrefix = curl('', `${authorizationHeader2} ${xFileHeader}`);
|
||||
const overwriteRe = RegExp(`^Request to overwrite existing ${isPublic ? 'public' : 'non-public'} directory`);
|
||||
|
||||
|
||||
it('should not overwrite existing builds', done => {
|
||||
@ -120,7 +121,7 @@ describe('upload-server (on HTTP)', () => {
|
||||
expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toBe('My content');
|
||||
|
||||
h.runCmd(`${cmdPrefix} http://${host}/create-build/${pr}/${sha9}`).
|
||||
then(h.verifyResponse(409, /^Request to overwrite existing directory/)).
|
||||
then(h.verifyResponse(409, overwriteRe)).
|
||||
then(() => expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toBe('My content')).
|
||||
then(done);
|
||||
});
|
||||
@ -141,7 +142,7 @@ describe('upload-server (on HTTP)', () => {
|
||||
expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toBe('My content');
|
||||
|
||||
h.runCmd(`${cmdPrefix} http://${host}/create-build/${pr}/${sha9Almost}`).
|
||||
then(h.verifyResponse(409, /^Request to overwrite existing directory/)).
|
||||
then(h.verifyResponse(409, overwriteRe)).
|
||||
then(() => expect(h.readBuildFile(pr, sha9, 'index.html', isPublic)).toBe('My content')).
|
||||
then(done);
|
||||
});
|
||||
@ -310,7 +311,7 @@ describe('upload-server (on HTTP)', () => {
|
||||
expect(h.buildExists(pr, sha0, isPublic)).toBe(false);
|
||||
|
||||
uploadBuild(sha0).
|
||||
then(h.verifyResponse(409, /^Request to overwrite existing directory/)).
|
||||
then(h.verifyResponse(409, overwriteRe)).
|
||||
then(() => {
|
||||
checkPrVisibility(isPublic);
|
||||
expect(h.readBuildFile(pr, sha0, 'index.html', isPublic)).toContain(pr);
|
||||
|
@ -153,7 +153,8 @@ describe('BuildCreator', () => {
|
||||
it('should abort and skip further operations if the build does already exist', done => {
|
||||
existsValues[shaDir] = true;
|
||||
bc.create(pr, sha, archive, isPublic).catch(err => {
|
||||
expectToBeUploadError(err, 409, `Request to overwrite existing directory: ${shaDir}`);
|
||||
const publicOrNot = isPublic ? 'public' : 'non-public';
|
||||
expectToBeUploadError(err, 409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`);
|
||||
expect(shellMkdirSpy).not.toHaveBeenCalled();
|
||||
expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
|
||||
expect(bcEmitSpy).not.toHaveBeenCalled();
|
||||
@ -169,7 +170,8 @@ describe('BuildCreator', () => {
|
||||
expect(bcExistsSpy(shaDir)).toBe(false);
|
||||
|
||||
bc.create(pr, sha, archive, isPublic).catch(err => {
|
||||
expectToBeUploadError(err, 409, `Request to overwrite existing directory: ${shaDir}`);
|
||||
const publicOrNot = isPublic ? 'public' : 'non-public';
|
||||
expectToBeUploadError(err, 409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`);
|
||||
expect(shellMkdirSpy).not.toHaveBeenCalled();
|
||||
expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
|
||||
expect(bcEmitSpy).not.toHaveBeenCalled();
|
||||
|
@ -6,7 +6,7 @@
|
||||
"interface-name": [true, "never-prefix"],
|
||||
"max-classes-per-file": [true, 4],
|
||||
"no-consecutive-blank-lines": [true, 2],
|
||||
"no-console": false,
|
||||
"no-console": [false],
|
||||
"no-namespace": [true, "allow-declarations"],
|
||||
"no-string-literal": false,
|
||||
"quotemark": [true, "single"],
|
||||
|
@ -46,8 +46,8 @@ with a bried explanation of what they mean:
|
||||
Request method other than POST.
|
||||
|
||||
- **409 (Conflict)**:
|
||||
Request to overwrite existing directory (e.g. deploy existing build or change PR visibility when
|
||||
the destination directory does already exist).
|
||||
Request to overwrite existing (public or non-public) directory (e.g. deploy existing build or
|
||||
change PR visibility when the destination directory does already exist).
|
||||
|
||||
- **413 (Payload Too Large)**:
|
||||
Payload larger than size specified in `AIO_UPLOAD_MAX_SIZE`.
|
||||
@ -71,7 +71,8 @@ with a bried explanation of what they mean:
|
||||
Request method other than POST.
|
||||
|
||||
- **409 (Conflict)**:
|
||||
Request to overwrite existing directory (i.e. directories for both visibilities exist).
|
||||
Request to overwrite existing (public or non-public) directory (i.e. directories for both
|
||||
visibilities exist).
|
||||
(Normally, this should not happen.)
|
||||
|
||||
|
||||
|
4
aio/content/examples/.gitignore
vendored
4
aio/content/examples/.gitignore
vendored
@ -55,10 +55,6 @@ dist/
|
||||
!testing/src/browser-test-shim.js
|
||||
!testing/karma*.js
|
||||
|
||||
# TS to JS
|
||||
!ts-to-js/js*/**/*.js
|
||||
ts-to-js/js*/**/system*.js
|
||||
|
||||
# webpack
|
||||
!webpack/**/config/*.js
|
||||
!webpack/**/*webpack*.js
|
||||
|
@ -1,77 +0,0 @@
|
||||
'use strict'; // necessary for es6 output in node
|
||||
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('TypeScript to Javascript tests', function () {
|
||||
|
||||
beforeAll(function () {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
it('should display the basic component example', function () {
|
||||
testTag('hero-view', 'Hero Detail: Windstorm');
|
||||
});
|
||||
|
||||
it('should display the component example with lifecycle methods', function () {
|
||||
testTag('hero-lifecycle', 'Hero: Windstorm');
|
||||
});
|
||||
|
||||
it('should display component with DI example', function () {
|
||||
testTag('hero-di', 'Hero: Windstorm');
|
||||
});
|
||||
|
||||
it('should display component with DI using @Inject example', function () {
|
||||
testTag('hero-di-inject', 'Hero: Windstorm');
|
||||
});
|
||||
|
||||
it('should support optional, attribute, and query injections', function () {
|
||||
let app = element(by.css('hero-di-inject-additional'));
|
||||
let h1 = app.element(by.css('h1'));
|
||||
let okMsg = app.element(by.css('p'));
|
||||
|
||||
expect(h1.getText()).toBe('Tour of Heroes');
|
||||
app.element(by.buttonText('OK')).click();
|
||||
expect(okMsg.getText()).toBe('OK!');
|
||||
});
|
||||
|
||||
it('should support component with inputs and outputs', function () {
|
||||
let app = element(by.css('hero-io'));
|
||||
let confirmComponent = app.element(by.css('app-confirm'));
|
||||
|
||||
confirmComponent.element(by.buttonText('OK')).click();
|
||||
expect(app.element(by.cssContainingText('span', 'OK clicked')).isPresent()).toBe(true);
|
||||
|
||||
confirmComponent.element(by.buttonText('Cancel')).click();
|
||||
expect(app.element(by.cssContainingText('span', 'Cancel clicked')).isPresent()).toBe(true);
|
||||
});
|
||||
|
||||
it('should support host bindings and host listeners', function() {
|
||||
let app = element(by.css('hero-host'));
|
||||
let h1 = app.element(by.css('h1'));
|
||||
|
||||
expect(app.getAttribute('class')).toBe('heading');
|
||||
expect(app.getAttribute('title')).toContain('Tooltip');
|
||||
|
||||
h1.click();
|
||||
expect(h1.getAttribute('class')).toBe('active');
|
||||
|
||||
h1.click();
|
||||
browser.actions().doubleClick(h1.getWebElement()).perform();
|
||||
expect(h1.getAttribute('class')).toBe('active');
|
||||
});
|
||||
|
||||
it('should support content and view queries', function() {
|
||||
let app = element(by.css('hero-queries'));
|
||||
let windstorm = app.element(by.css('view-child:first-child'));
|
||||
|
||||
app.element(by.css('button')).click();
|
||||
expect(windstorm.element(by.css('h2')).getAttribute('class')).toBe('active');
|
||||
expect(windstorm.element(by.css('content-child')).getText()).toBe('Active');
|
||||
});
|
||||
|
||||
function testTag(selector: string, expectedText: string) {
|
||||
let component = element(by.css(selector));
|
||||
expect(component.getText()).toBe(expectedText);
|
||||
}
|
||||
|
||||
});
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"build": "build:babel"
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"description": "TypeScript to JavaScript",
|
||||
"basePath": "src/",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js"
|
||||
],
|
||||
"tags":["cookbook"]
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
"es2015",
|
||||
"angular2"
|
||||
]
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
templateUrl: './app.component.html',
|
||||
styles: [
|
||||
// See hero-di-inject-additional.component
|
||||
'hero-host, hero-host-meta { border: 1px dashed black; display: block; padding: 4px;}',
|
||||
'.heading {font-style: italic}'
|
||||
]
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'ES6 JavaScript with Decorators';
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
<a id="toc"></a>
|
||||
<h1>{{title}}</h1>
|
||||
<a href="#class-metadata">Classes and Class Metadata</a><br>
|
||||
<a href="#io-metadata">Input and Output Decorators</a><br>
|
||||
<a href="#dependency-injection">Dependency Injection</a><br>
|
||||
<a href="#host-metadata">Host Metadata</a><br>
|
||||
<a href="#view-child-metadata">View and Child Metadata</a><br>
|
||||
|
||||
<hr>
|
||||
<h4 id="class-metadata">Classes and Class Metadata</h4>
|
||||
<hero-view></hero-view>
|
||||
<hero-lifecycle></hero-lifecycle>
|
||||
|
||||
<hr>
|
||||
<h4 id="io-metadata">Input and Output Metadata</h4>
|
||||
<hero-io></hero-io>
|
||||
|
||||
<hr>
|
||||
<h4 id="dependency-injection">Dependency Injection</h4>
|
||||
<hero-di></hero-di>
|
||||
<hero-di-inject></hero-di-inject>
|
||||
<hero-di-inject-additional></hero-di-inject-additional>
|
||||
|
||||
<hr>
|
||||
<h4 id="host-metadata">Host Metadata</h4>
|
||||
<hero-host></hero-host>
|
||||
<hero-host-meta></hero-host-meta>
|
||||
|
||||
<hr>
|
||||
<h4 id="view-child-metadata">View and Child Metadata</h4>
|
||||
<hero-queries></hero-queries>
|
@ -1,55 +0,0 @@
|
||||
import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { ConfirmComponent } from './confirm.component';
|
||||
// #docregion appimport
|
||||
import { HeroComponent } from './hero.component';
|
||||
// #enddocregion appimport
|
||||
import { HeroComponent as HeroDIComponent } from './hero-di.component';
|
||||
import { HeroComponent as HeroDIInjectComponent } from './hero-di-inject.component';
|
||||
import { HeroComponent as HeroDIInjectAdditionalComponent } from './hero-di-inject-additional.component';
|
||||
import { HeroHostComponent } from './hero-host.component';
|
||||
import { HeroHostMetaComponent } from './hero-host-meta.component';
|
||||
import { HeroIOComponent } from './hero-io.component';
|
||||
import { HeroComponent as HeroLifecycleComponent } from './hero-lifecycle.component';
|
||||
import { HeroQueriesComponent, ViewChildComponent, ContentChildComponent } from './hero-queries.component';
|
||||
import { HeroTitleComponent } from './hero-title.component';
|
||||
|
||||
import { DataService } from './data.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
ConfirmComponent,
|
||||
HeroComponent,
|
||||
HeroDIComponent,
|
||||
HeroDIInjectComponent,
|
||||
HeroDIInjectAdditionalComponent,
|
||||
HeroHostComponent, HeroHostMetaComponent,
|
||||
HeroIOComponent,
|
||||
HeroLifecycleComponent,
|
||||
HeroQueriesComponent, ViewChildComponent, ContentChildComponent,
|
||||
HeroTitleComponent
|
||||
],
|
||||
providers: [
|
||||
DataService,
|
||||
{ provide: 'heroName', useValue: 'Windstorm' }
|
||||
],
|
||||
bootstrap: [ AppComponent ],
|
||||
|
||||
// schemas: [ NO_ERRORS_SCHEMA ] // helpful for debugging
|
||||
})
|
||||
export class AppModule { }
|
||||
|
||||
/* tslint:disable no-unused-variable */
|
||||
// #docregion ng2import
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import {
|
||||
LocationStrategy,
|
||||
HashLocationStrategy
|
||||
} from '@angular/common';
|
||||
// #enddocregion ng2import
|
@ -1,21 +0,0 @@
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
// #docregion
|
||||
@Component({
|
||||
selector: 'app-confirm',
|
||||
templateUrl: './confirm.component.html'
|
||||
})
|
||||
export class ConfirmComponent {
|
||||
@Input() okMsg = '';
|
||||
@Input('cancelMsg') notOkMsg = '';
|
||||
@Output() ok = new EventEmitter();
|
||||
@Output('cancel') notOk = new EventEmitter();
|
||||
|
||||
onOkClick() {
|
||||
this.ok.emit(true);
|
||||
}
|
||||
onNotOkClick() {
|
||||
this.notOk.emit(true);
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
@ -1,6 +0,0 @@
|
||||
<button (click)="onOkClick()">
|
||||
{{okMsg}}
|
||||
</button>
|
||||
<button (click)="onNotOkClick()">
|
||||
{{notOkMsg}}
|
||||
</button>
|
@ -1,10 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class DataService {
|
||||
constructor() { }
|
||||
|
||||
getHeroName() {
|
||||
return 'Windstorm';
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-di-inject-additional',
|
||||
template: `<hero-title title="Tour of Heroes"></hero-title>`
|
||||
})
|
||||
export class HeroComponent { }
|
@ -1,13 +0,0 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
|
||||
// #docregion
|
||||
@Component({
|
||||
selector: 'hero-di-inject',
|
||||
template: `<h1>Hero: {{name}}</h1>`
|
||||
})
|
||||
export class HeroComponent {
|
||||
constructor(@Inject('heroName') name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
@ -1,15 +0,0 @@
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
import { DataService } from './data.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-di',
|
||||
template: `<h1>Hero: {{name}}</h1>`
|
||||
})
|
||||
export class HeroComponent {
|
||||
name = '';
|
||||
constructor(dataService: DataService) {
|
||||
this.name = dataService.getHeroName();
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
@ -1,44 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
// #docregion
|
||||
@Component({
|
||||
selector: 'hero-host-meta',
|
||||
template: `
|
||||
<h1 [class.active]="active">Hero Host in Metadata</h1>
|
||||
<div>Heading clicks: {{clicks}}</div>
|
||||
`,
|
||||
host: {
|
||||
// HostBindings to the <hero-host-meta> element
|
||||
'[title]': 'title',
|
||||
'[class.heading]': 'headingClass',
|
||||
|
||||
// HostListeners on the entire <hero-host-meta> element
|
||||
'(click)': 'clicked()',
|
||||
'(mouseenter)': 'enter($event)',
|
||||
'(mouseleave)': 'leave($event)'
|
||||
},
|
||||
// Styles within (but excluding) the <hero-host-meta> element
|
||||
styles: ['.active {background-color: coral;}']
|
||||
})
|
||||
export class HeroHostMetaComponent {
|
||||
title = 'Hero Host in Metadata Tooltip';
|
||||
headingClass = true;
|
||||
|
||||
active = false;
|
||||
clicks = 0;
|
||||
|
||||
clicked() {
|
||||
this.clicks += 1;
|
||||
}
|
||||
|
||||
enter(event: Event) {
|
||||
this.active = true;
|
||||
this.headingClass = false;
|
||||
}
|
||||
|
||||
leave(event: Event) {
|
||||
this.active = false;
|
||||
this.headingClass = true;
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
@ -1,39 +0,0 @@
|
||||
import { Component, HostBinding, HostListener } from '@angular/core';
|
||||
|
||||
// #docregion
|
||||
@Component({
|
||||
selector: 'hero-host',
|
||||
template: `
|
||||
<h1 [class.active]="active">Hero Host in Decorators</h1>
|
||||
<div>Heading clicks: {{clicks}}</div>
|
||||
`,
|
||||
// Styles within (but excluding) the <hero-host> element
|
||||
styles: ['.active {background-color: yellow;}']
|
||||
})
|
||||
export class HeroHostComponent {
|
||||
// HostBindings to the <hero-host> element
|
||||
@HostBinding() title = 'Hero Host in Decorators Tooltip';
|
||||
@HostBinding('class.heading') headingClass = true;
|
||||
|
||||
active = false;
|
||||
clicks = 0;
|
||||
|
||||
// HostListeners on the entire <hero-host> element
|
||||
@HostListener('click')
|
||||
clicked() {
|
||||
this.clicks += 1;
|
||||
}
|
||||
|
||||
@HostListener('mouseenter', ['$event'])
|
||||
enter(event: Event) {
|
||||
this.active = true;
|
||||
this.headingClass = false;
|
||||
}
|
||||
|
||||
@HostListener('mouseleave', ['$event'])
|
||||
leave(event: Event) {
|
||||
this.active = false;
|
||||
this.headingClass = true;
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
@ -1,26 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-io',
|
||||
template: `
|
||||
<app-confirm [okMsg]="'OK'"
|
||||
[cancelMsg]="'Cancel'"
|
||||
(ok)="onOk()"
|
||||
(cancel)="onCancel()">
|
||||
</app-confirm>
|
||||
<span *ngIf="okClicked">OK clicked</span>
|
||||
<span *ngIf="cancelClicked">Cancel clicked</span>
|
||||
`
|
||||
})
|
||||
export class HeroIOComponent {
|
||||
okClicked = false;
|
||||
cancelClicked = false;
|
||||
|
||||
onOk() {
|
||||
this.okClicked = true;
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
this.cancelClicked = true;
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-lifecycle',
|
||||
template: `<h1>Hero: {{name}}</h1>`
|
||||
})
|
||||
export class HeroComponent {
|
||||
name = '';
|
||||
ngOnInit() {
|
||||
// todo: fetch from server async
|
||||
setTimeout(() => this.name = 'Windstorm', 0);
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
import {
|
||||
Component,
|
||||
ContentChild,
|
||||
Input,
|
||||
QueryList,
|
||||
ViewChildren
|
||||
} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'content-child',
|
||||
template: `
|
||||
<span class="content-child" *ngIf="active">
|
||||
Active
|
||||
</span>`
|
||||
})
|
||||
export class ContentChildComponent {
|
||||
active = false;
|
||||
|
||||
activate() {
|
||||
this.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////
|
||||
|
||||
// #docregion content
|
||||
@Component({
|
||||
selector: 'view-child',
|
||||
template: `
|
||||
<h2 [class.active]=active>
|
||||
{{hero.name}}
|
||||
<ng-content></ng-content>
|
||||
</h2>`,
|
||||
styles: ['.active {font-weight: bold; background-color: skyblue;}']
|
||||
})
|
||||
export class ViewChildComponent {
|
||||
@Input() hero;
|
||||
active = false;
|
||||
|
||||
@ContentChild(ContentChildComponent) content;
|
||||
|
||||
activate() {
|
||||
this.active = !this.active;
|
||||
this.content.activate();
|
||||
}
|
||||
}
|
||||
// #enddocregion content
|
||||
|
||||
////////////////////
|
||||
|
||||
// #docregion view
|
||||
@Component({
|
||||
selector: 'hero-queries',
|
||||
template: `
|
||||
<view-child *ngFor="let hero of heroData" [hero]="hero">
|
||||
<content-child></content-child>
|
||||
</view-child>
|
||||
<button (click)="activate()">{{buttonLabel}} All</button>
|
||||
`
|
||||
})
|
||||
export class HeroQueriesComponent {
|
||||
active = false;
|
||||
heroData = [
|
||||
{id: 1, name: 'Windstorm'},
|
||||
{id: 2, name: 'LaughingGas'}
|
||||
];
|
||||
|
||||
@ViewChildren(ViewChildComponent) views;
|
||||
|
||||
activate() {
|
||||
this.active = !this.active;
|
||||
this.views.forEach(
|
||||
view => view.activate()
|
||||
);
|
||||
}
|
||||
|
||||
get buttonLabel() {
|
||||
return this.active ? 'Deactivate' : 'Activate';
|
||||
}
|
||||
}
|
||||
// #enddocregion view
|
@ -1,25 +0,0 @@
|
||||
import { Attribute, Component, Inject, Optional } from '@angular/core';
|
||||
|
||||
// #docregion
|
||||
// #docregion templateUrl
|
||||
@Component({
|
||||
selector: 'hero-title',
|
||||
templateUrl: './hero-title.component.html'
|
||||
})
|
||||
// #enddocregion templateUrl
|
||||
export class HeroTitleComponent {
|
||||
msg = '';
|
||||
constructor(
|
||||
@Inject('titlePrefix') @Optional() titlePrefix,
|
||||
@Attribute('title') title
|
||||
) {
|
||||
this.titlePrefix = titlePrefix;
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
ok() {
|
||||
this.msg = 'OK!';
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
||||
|
@ -1,4 +0,0 @@
|
||||
<!-- #docregion -->
|
||||
<h1>{{titlePrefix}} {{title}}</h1>
|
||||
<button (click)="ok()">OK</button>
|
||||
<p>{{ msg }}</p>
|
@ -1,15 +0,0 @@
|
||||
// #docregion
|
||||
// #docregion metadata
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-view',
|
||||
template: '<h1>{{title}}: {{getName()}}</h1>'
|
||||
})
|
||||
// #docregion appexport, class
|
||||
export class HeroComponent {
|
||||
title = 'Hero Detail';
|
||||
getName() {return 'Windstorm'; }
|
||||
}
|
||||
// #enddocregion appexport, class
|
||||
// #enddocregion metadata
|
@ -1,26 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<base href="/">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<title>TypeScript to JavaScript</title>
|
||||
|
||||
<!-- Polyfill(s) for older browsers -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('main.js').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<my-app>Loading...</my-app>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,4 +0,0 @@
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { AppModule } from './app/app.module';
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"build": "build:babel"
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"description": "TypeScript to JavaScript",
|
||||
"basePath": "src/",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js"
|
||||
],
|
||||
"tags":["cookbook"]
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
"es2015"
|
||||
]
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
export class AppComponent {
|
||||
constructor() {
|
||||
this.title = 'Plain ES6 JavaScript';
|
||||
}
|
||||
}
|
||||
|
||||
AppComponent.annotations = [
|
||||
new Component({
|
||||
selector: 'my-app',
|
||||
templateUrl: './app.component.html',
|
||||
styles: [
|
||||
// See hero-di-inject-additional.component
|
||||
'hero-host { border: 1px dashed black; display: block; padding: 4px;}',
|
||||
'.heading {font-style: italic}'
|
||||
]
|
||||
})
|
||||
];
|
@ -1,30 +0,0 @@
|
||||
<a id="toc"></a>
|
||||
<h1>{{title}}</h1>
|
||||
<a href="#class-metadata">Classes and Class Metadata</a><br>
|
||||
<a href="#io-metadata">Input and Output Metadata</a><br>
|
||||
<a href="#dependency-injection">Dependency Injection</a><br>
|
||||
<a href="#host-metadata">Host Metadata</a><br>
|
||||
<a href="#view-child-metadata">View and Child Metadata</a><br>
|
||||
|
||||
<hr>
|
||||
<h4 id="class-metadata">Classes and Class Metadata</h4>
|
||||
<hero-view></hero-view>
|
||||
<hero-lifecycle></hero-lifecycle>
|
||||
|
||||
<hr>
|
||||
<h4 id="io-metadata">Input and Output Metadata</h4>
|
||||
<hero-io></hero-io>
|
||||
|
||||
<hr>
|
||||
<h4 id="dependency-injection">Dependency Injection</h4>
|
||||
<hero-di></hero-di>
|
||||
<hero-di-inject></hero-di-inject>
|
||||
<hero-di-inject-additional></hero-di-inject-additional>
|
||||
|
||||
<hr>
|
||||
<h4 id="host-metadata">Host Metadata</h4>
|
||||
<hero-host></hero-host>
|
||||
|
||||
<hr>
|
||||
<h4 id="view-child-metadata">View and Child Metadata</h4>
|
||||
<hero-queries></hero-queries>
|
@ -1,56 +0,0 @@
|
||||
import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { ConfirmComponent } from './confirm.component';
|
||||
// #docregion appimport
|
||||
import { HeroComponent } from './hero.component';
|
||||
|
||||
// #enddocregion appimport
|
||||
import { HeroComponent as HeroDIComponent } from './hero-di.component';
|
||||
import { HeroComponent as HeroDIInjectComponent } from './hero-di-inject.component';
|
||||
import { HeroComponent as HeroDIInjectAdditionalComponent } from './hero-di-inject-additional.component';
|
||||
import { HeroHostComponent } from './hero-host.component';
|
||||
import { HeroIOComponent } from './hero-io.component';
|
||||
import { HeroComponent as HeroLifecycleComponent } from './hero-lifecycle.component';
|
||||
import { HeroQueriesComponent, ViewChildComponent, ContentChildComponent } from './hero-queries.component';
|
||||
import { HeroTitleComponent } from './hero-title.component';
|
||||
|
||||
import { DataService } from './data.service';
|
||||
|
||||
export class AppModule { }
|
||||
|
||||
AppModule.annotations = [
|
||||
new NgModule({
|
||||
imports: [ BrowserModule],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
ConfirmComponent,
|
||||
HeroComponent,
|
||||
HeroDIComponent,
|
||||
HeroDIInjectComponent,
|
||||
HeroDIInjectAdditionalComponent,
|
||||
HeroHostComponent,
|
||||
HeroIOComponent,
|
||||
HeroLifecycleComponent,
|
||||
HeroQueriesComponent, ViewChildComponent, ContentChildComponent,
|
||||
HeroTitleComponent
|
||||
],
|
||||
providers: [
|
||||
DataService,
|
||||
{ provide: 'heroName', useValue: 'Windstorm' }
|
||||
],
|
||||
bootstrap: [ AppComponent ],
|
||||
|
||||
// schemas: [ NO_ERRORS_SCHEMA ] // helpful for debugging
|
||||
})
|
||||
]
|
||||
|
||||
/* tslint:disable no-unused-variable */
|
||||
// #docregion ng2import
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import {
|
||||
LocationStrategy,
|
||||
HashLocationStrategy
|
||||
} from '@angular/common';
|
||||
// #enddocregion ng2import
|
@ -1,31 +0,0 @@
|
||||
import { Component, EventEmitter } from '@angular/core';
|
||||
|
||||
// #docregion
|
||||
export class ConfirmComponent {
|
||||
constructor(){
|
||||
this.ok = new EventEmitter();
|
||||
this.notOk = new EventEmitter();
|
||||
}
|
||||
onOkClick() {
|
||||
this.ok.emit(true);
|
||||
}
|
||||
onNotOkClick() {
|
||||
this.notOk.emit(true);
|
||||
}
|
||||
}
|
||||
|
||||
ConfirmComponent.annotations = [
|
||||
new Component({
|
||||
selector: 'app-confirm',
|
||||
templateUrl: './confirm.component.html',
|
||||
inputs: [
|
||||
'okMsg',
|
||||
'notOkMsg: cancelMsg'
|
||||
],
|
||||
outputs: [
|
||||
'ok',
|
||||
'notOk: cancel'
|
||||
]
|
||||
})
|
||||
];
|
||||
// #enddocregion
|
@ -1,6 +0,0 @@
|
||||
<button (click)="onOkClick()">
|
||||
{{okMsg}}
|
||||
</button>
|
||||
<button (click)="onNotOkClick()">
|
||||
{{notOkMsg}}
|
||||
</button>
|
@ -1,13 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
export class DataService {
|
||||
constructor() {
|
||||
}
|
||||
getHeroName() {
|
||||
return 'Windstorm';
|
||||
}
|
||||
}
|
||||
|
||||
DataService.annotations = [
|
||||
new Injectable()
|
||||
];
|
@ -1,10 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
export class HeroComponent { }
|
||||
|
||||
HeroComponent.annotations = [
|
||||
new Component({
|
||||
selector: 'hero-di-inject-additional',
|
||||
template: `<hero-title title="Tour of Heroes"></hero-title>`
|
||||
})
|
||||
];
|
@ -1,20 +0,0 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
|
||||
// #docregion
|
||||
export class HeroComponent {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
HeroComponent.annotations = [
|
||||
new Component({
|
||||
selector: 'hero-di-inject',
|
||||
template: `<h1>Hero: {{name}}</h1>`
|
||||
})
|
||||
];
|
||||
|
||||
HeroComponent.parameters = [
|
||||
[new Inject('heroName')]
|
||||
];
|
||||
// #enddocregion
|
@ -1,21 +0,0 @@
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
import { DataService } from './data.service';
|
||||
|
||||
export class HeroComponent {
|
||||
constructor(dataService) {
|
||||
this.name = dataService.getHeroName();
|
||||
}
|
||||
}
|
||||
|
||||
HeroComponent.annotations = [
|
||||
new Component({
|
||||
selector: 'hero-di',
|
||||
template: `<h1>Hero: {{name}}</h1>`
|
||||
})
|
||||
];
|
||||
|
||||
HeroComponent.parameters = [
|
||||
[DataService]
|
||||
];
|
||||
// #enddocregion
|
@ -1,50 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
// #docregion
|
||||
export class HeroHostComponent {
|
||||
constructor() {
|
||||
this.active = false;
|
||||
this.clicks = 0;
|
||||
this.headingClass = true;
|
||||
this.title = 'Hero Host Tooltip';
|
||||
}
|
||||
|
||||
clicked() {
|
||||
this.clicks += 1;
|
||||
}
|
||||
|
||||
enter(event) {
|
||||
this.active = true;
|
||||
this.headingClass = false;
|
||||
}
|
||||
|
||||
leave(event) {
|
||||
this.active = false;
|
||||
this.headingClass = true;
|
||||
}
|
||||
}
|
||||
|
||||
// #docregion metadata
|
||||
HeroHostComponent.annotations = [
|
||||
new Component({
|
||||
selector: 'hero-host',
|
||||
template: `
|
||||
<h1 [class.active]="active">Hero Host</h1>
|
||||
<div>Heading clicks: {{clicks}}</div>
|
||||
`,
|
||||
host: {
|
||||
// HostBindings to the <hero-host> element
|
||||
'[title]': 'title',
|
||||
'[class.heading]': 'headingClass',
|
||||
'(click)': 'clicked()',
|
||||
|
||||
// HostListeners on the entire <hero-host> element
|
||||
'(mouseenter)': 'enter($event)',
|
||||
'(mouseleave)': 'leave($event)'
|
||||
},
|
||||
// Styles within (but excluding) the <hero-host> element
|
||||
styles: ['.active {background-color: yellow;}']
|
||||
})
|
||||
];
|
||||
// #enddocregion metadata
|
||||
// #enddocregion
|
@ -1,31 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
export class HeroIOComponent {
|
||||
constructor() {
|
||||
this.okClicked = false;
|
||||
this.cancelClicked = false;
|
||||
}
|
||||
|
||||
onOk() {
|
||||
this.okClicked = true;
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
this.cancelClicked = true;
|
||||
}
|
||||
}
|
||||
|
||||
HeroIOComponent.annotations = [
|
||||
new Component({
|
||||
selector: 'hero-io',
|
||||
template: `
|
||||
<app-confirm [okMsg]="'OK'"
|
||||
[cancelMsg]="'Cancel'"
|
||||
(ok)="onOk()"
|
||||
(cancel)="onCancel()">
|
||||
</app-confirm>
|
||||
<span *ngIf="okClicked">OK clicked</span>
|
||||
<span *ngIf="cancelClicked">Cancel clicked</span>
|
||||
`
|
||||
})
|
||||
];
|
@ -1,15 +0,0 @@
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
export class HeroComponent {
|
||||
ngOnInit() {
|
||||
// todo: fetch from server async
|
||||
setTimeout(() => this.name = 'Windstorm', 0);
|
||||
}
|
||||
}
|
||||
|
||||
HeroComponent.annotations = [
|
||||
new Component({
|
||||
selector: 'hero-lifecycle',
|
||||
template: `<h1>Hero: {{name}}</h1>`
|
||||
})
|
||||
];
|
@ -1,97 +0,0 @@
|
||||
import {
|
||||
Component,
|
||||
ContentChild,
|
||||
Input,
|
||||
QueryList,
|
||||
ViewChildren
|
||||
} from '@angular/core';
|
||||
|
||||
export class ContentChildComponent {
|
||||
constructor() {
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
activate() {
|
||||
this.active = !this.active;
|
||||
}
|
||||
}
|
||||
|
||||
ContentChildComponent.annotations = [
|
||||
new Component({
|
||||
selector: 'content-child',
|
||||
template: `
|
||||
<span class="content-child" *ngIf="active">
|
||||
Active
|
||||
</span>`
|
||||
})
|
||||
];
|
||||
|
||||
////////////////////
|
||||
|
||||
// #docregion content
|
||||
export class ViewChildComponent {
|
||||
constructor() {
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
activate() {
|
||||
this.active = !this.active;
|
||||
this.content.activate();
|
||||
}
|
||||
}
|
||||
|
||||
ViewChildComponent.annotations = [
|
||||
new Component({
|
||||
selector: 'view-child',
|
||||
template: `<h2 [class.active]=active>
|
||||
{{hero.name}}
|
||||
<ng-content></ng-content>
|
||||
</h2>`,
|
||||
styles: ['.active {font-weight: bold; background-color: skyblue;}'],
|
||||
inputs: ['hero'],
|
||||
queries: {
|
||||
content: new ContentChild(ContentChildComponent)
|
||||
}
|
||||
})
|
||||
];
|
||||
// #enddocregion content
|
||||
|
||||
////////////////////
|
||||
|
||||
// #docregion view
|
||||
export class HeroQueriesComponent {
|
||||
constructor(){
|
||||
this.active = false;
|
||||
this.heroData = [
|
||||
{id: 1, name: 'Windstorm'},
|
||||
{id: 2, name: 'LaughingGas'}
|
||||
];
|
||||
}
|
||||
|
||||
activate() {
|
||||
this.active = !this.active;
|
||||
this.views.forEach(
|
||||
view => view.activate()
|
||||
);
|
||||
}
|
||||
|
||||
get buttonLabel() {
|
||||
return this.active ? 'Deactivate' : 'Activate';
|
||||
}
|
||||
}
|
||||
|
||||
HeroQueriesComponent.annotations = [
|
||||
new Component({
|
||||
selector: 'hero-queries',
|
||||
template: `
|
||||
<view-child *ngFor="let hero of heroData" [hero]="hero">
|
||||
<content-child></content-child>
|
||||
</view-child>
|
||||
<button (click)="activate()">{{buttonLabel}} All</button>
|
||||
`,
|
||||
queries: {
|
||||
views: new ViewChildren(ViewChildComponent)
|
||||
}
|
||||
})
|
||||
];
|
||||
// #enddocregion view
|
@ -1,28 +0,0 @@
|
||||
import { Attribute, Component, Inject, Optional } from '@angular/core';
|
||||
|
||||
// #docregion
|
||||
export class HeroTitleComponent {
|
||||
constructor(titlePrefix, title) {
|
||||
this.titlePrefix = titlePrefix;
|
||||
this.title = title;
|
||||
this.msg = '';
|
||||
}
|
||||
|
||||
ok() {
|
||||
this.msg = 'OK!';
|
||||
}
|
||||
}
|
||||
|
||||
// #docregion templateUrl
|
||||
HeroTitleComponent.annotations = [
|
||||
new Component({
|
||||
selector: 'hero-title',
|
||||
templateUrl: './hero-title.component.html'
|
||||
})
|
||||
];
|
||||
// #enddocregion templateUrl
|
||||
|
||||
HeroTitleComponent.parameters = [
|
||||
[new Optional(), new Inject('titlePrefix')],
|
||||
[new Attribute('title')]
|
||||
];
|
@ -1,4 +0,0 @@
|
||||
<!-- #docregion -->
|
||||
<h1>{{titlePrefix}} {{title}}</h1>
|
||||
<button (click)="ok()">OK</button>
|
||||
<p>{{ msg }}</p>
|
@ -1,21 +0,0 @@
|
||||
// #docplaster
|
||||
// #docregion
|
||||
// #docregion metadata
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
// #docregion appexport, class
|
||||
export class HeroComponent {
|
||||
constructor() {
|
||||
this.title = 'Hero Detail';
|
||||
}
|
||||
getName() {return 'Windstorm'; }
|
||||
}
|
||||
// #enddocregion appexport, class
|
||||
|
||||
HeroComponent.annotations = [
|
||||
new Component({
|
||||
selector: 'hero-view',
|
||||
template: '<h1>{{title}}: {{getName()}}</h1>'
|
||||
})
|
||||
];
|
||||
// #enddocregion metadata
|
@ -1,26 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<base href="/">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<title>TypeScript to JavaScript</title>
|
||||
|
||||
<!-- Polyfill(s) for older browsers -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('main.js').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<my-app>Loading...</my-app>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,4 +0,0 @@
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { AppModule } from './app/app.module';
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"build": "build:babel"
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"description": "TypeScript to JavaScript",
|
||||
"basePath": "src/",
|
||||
"files":[
|
||||
"!**/karma*.*"
|
||||
],
|
||||
"tags":["cookbook"]
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
<a id="toc"></a>
|
||||
<h1>{{title}}</h1>
|
||||
<a href="#class-metadata">Classes and Class Metadata</a><br>
|
||||
<a href="#interfaces">Interfaces</a><br>
|
||||
<a href="#io-metadata">Input and Output Metadata</a><br>
|
||||
<a href="#dependency-injection">Dependency Injection</a><br>
|
||||
<a href="#host-metadata">Host Metadata</a><br>
|
||||
<a href="#view-child-metadata">View and Child Metadata</a><br>
|
||||
|
||||
<hr>
|
||||
<h4 id="class-metadata">Classes and Class Metadata</h4>
|
||||
<hero-view></hero-view>
|
||||
<h4 id="class-metadata-dsl">Classes and Class Metadata (DSL)</h4>
|
||||
<hero-view-dsl></hero-view-dsl>
|
||||
|
||||
<hr>
|
||||
<h4 id="interfaces">Interfaces</h4>
|
||||
<hero-lifecycle></hero-lifecycle>
|
||||
<h4 id="interfaces-dsl">Interfaces (DSL)</h4>
|
||||
<hero-lifecycle-dsl></hero-lifecycle-dsl>
|
||||
|
||||
<hr>
|
||||
<h4 id="io-metadata">Input and Output Metadata</h4>
|
||||
<hero-io></hero-io>
|
||||
<h4 id="io-metadata-dsl">Input and Output Metadata (DSL)</h4>
|
||||
<hero-io-dsl></hero-io-dsl>
|
||||
|
||||
<hr>
|
||||
<h4 id="dependency-injection">Dependency Injection</h4>
|
||||
<hero-di></hero-di>
|
||||
<hero-di-inject></hero-di-inject>
|
||||
<hero-di-inject-additional></hero-di-inject-additional>
|
||||
|
||||
<h4 id="dependency-injection-dsl">Dependency Injection (DSL)</h4>
|
||||
<hero-di-dsl></hero-di-dsl>
|
||||
<hero-di-inject-dsl></hero-di-inject-dsl>
|
||||
<hero-di-inject-additional-dsl></hero-di-inject-additional-dsl>
|
||||
|
||||
<hr>
|
||||
<h4 id="host-metadata">Host Metadata</h4>
|
||||
<hero-host></hero-host>
|
||||
<h4 id="host-metadata-dsl">Host Metadata (DSL)</h4>
|
||||
<hero-host-dsl></hero-host-dsl>
|
||||
|
||||
<hr>
|
||||
<h4 id="view-child-metadata">View and Child Metadata (DSL)</h4>
|
||||
<hero-queries></hero-queries>
|
@ -1,20 +0,0 @@
|
||||
(function(app) {
|
||||
|
||||
app.AppComponent = AppComponent;
|
||||
function AppComponent() {
|
||||
this.title = 'ES5 JavaScript';
|
||||
}
|
||||
|
||||
AppComponent.annotations = [
|
||||
new ng.core.Component({
|
||||
selector: 'my-app',
|
||||
templateUrl: 'app/app.component.html',
|
||||
styles: [
|
||||
// See hero-di-inject-additional.component
|
||||
'hero-host, hero-host-dsl { border: 1px dashed black; display: block; padding: 4px;}',
|
||||
'.heading {font-style: italic}'
|
||||
]
|
||||
})
|
||||
];
|
||||
|
||||
})(window.app = window.app || {});
|
@ -1,46 +0,0 @@
|
||||
(function(app) {
|
||||
|
||||
app.AppModule = AppModule;
|
||||
function AppModule() { }
|
||||
|
||||
AppModule.annotations = [
|
||||
new ng.core.NgModule({
|
||||
imports: [ ng.platformBrowser.BrowserModule ],
|
||||
declarations: [
|
||||
app.AppComponent,
|
||||
app.ConfirmComponent, app.ConfirmDslComponent,
|
||||
app.HeroComponent, app.HeroDslComponent,
|
||||
app.HeroDIComponent, app.HeroDIDslComponent,
|
||||
app.HeroDIInjectComponent, app.HeroDIInjectDslComponent,
|
||||
app.HeroDIInjectAdditionalComponent, app.HeroDIInjectAdditionalDslComponent,
|
||||
app.HeroHostComponent, app.HeroHostDslComponent,
|
||||
app.HeroIOComponent, app.HeroIODslComponent,
|
||||
app.HeroLifecycleComponent, app.HeroLifecycleDslComponent,
|
||||
app.heroQueries.HeroQueriesComponent, app.heroQueries.ViewChildComponent, app.heroQueries.ContentChildComponent,
|
||||
app.HeroTitleComponent, app.HeroTitleDslComponent
|
||||
],
|
||||
providers: [
|
||||
app.DataService,
|
||||
{ provide: 'heroName', useValue: 'Windstorm' }
|
||||
],
|
||||
bootstrap: [ app.AppComponent ],
|
||||
|
||||
// schemas: [ ng.core.NO_ERRORS_SCHEMA ] // helpful for debugging!
|
||||
})
|
||||
]
|
||||
|
||||
})(window.app = window.app || {});
|
||||
|
||||
|
||||
///// For documentation only /////
|
||||
(function () {
|
||||
// #docregion appimport
|
||||
var HeroComponent = app.HeroComponent;
|
||||
// #enddocregion appimport
|
||||
|
||||
// #docregion ng2import
|
||||
var platformBrowserDynamic = ng.platformBrowserDynamic.platformBrowserDynamic;
|
||||
var LocationStrategy = ng.common.LocationStrategy;
|
||||
var HashLocationStrategy = ng.common.HashLocationStrategy;
|
||||
// #enddocregion ng2import
|
||||
})
|
@ -1,6 +0,0 @@
|
||||
<button (click)="onOkClick()">
|
||||
{{okMsg}}
|
||||
</button>
|
||||
<button (click)="onNotOkClick()">
|
||||
{{notOkMsg}}
|
||||
</button>
|
@ -1,75 +0,0 @@
|
||||
(function(app) {
|
||||
|
||||
// #docregion
|
||||
app.ConfirmComponent = ConfirmComponent;
|
||||
|
||||
ConfirmComponent.annotations = [
|
||||
new ng.core.Component({
|
||||
selector: 'app-confirm',
|
||||
templateUrl: 'app/confirm.component.html',
|
||||
inputs: [
|
||||
'okMsg',
|
||||
'notOkMsg: cancelMsg'
|
||||
],
|
||||
outputs: [
|
||||
'ok',
|
||||
'notOk: cancel'
|
||||
]
|
||||
})
|
||||
];
|
||||
|
||||
function ConfirmComponent() {
|
||||
this.ok = new ng.core.EventEmitter();
|
||||
this.notOk = new ng.core.EventEmitter();
|
||||
}
|
||||
|
||||
ConfirmComponent.prototype.onOkClick = function() {
|
||||
this.ok.emit(true);
|
||||
}
|
||||
|
||||
ConfirmComponent.prototype.onNotOkClick = function() {
|
||||
this.notOk.emit(true);
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
})(window.app = window.app || {});
|
||||
|
||||
/////// DSL version ////////
|
||||
|
||||
(function(app) {
|
||||
|
||||
var old = app.ConfirmComponent;
|
||||
|
||||
// #docregion dsl
|
||||
app.ConfirmComponent = ng.core.Component({
|
||||
selector: 'app-confirm-dsl',
|
||||
templateUrl: 'app/confirm.component.html',
|
||||
inputs: [
|
||||
'okMsg',
|
||||
'notOkMsg: cancelMsg'
|
||||
],
|
||||
outputs: [
|
||||
'ok',
|
||||
'notOk: cancel'
|
||||
]
|
||||
})
|
||||
.Class({
|
||||
constructor: function ConfirmComponent() {
|
||||
this.ok = new ng.core.EventEmitter();
|
||||
this.notOk = new ng.core.EventEmitter();
|
||||
},
|
||||
|
||||
onOkClick: function() {
|
||||
this.ok.emit(true);
|
||||
},
|
||||
|
||||
onNotOkClick: function() {
|
||||
this.notOk.emit(true);
|
||||
}
|
||||
});
|
||||
// #enddocregion dsl
|
||||
|
||||
app.ConfirmDslComponent = app.ConfirmComponent;
|
||||
app.ConfirmComponent = old;
|
||||
|
||||
})(window.app = window.app || {});
|
@ -1,10 +0,0 @@
|
||||
(function(app) {
|
||||
|
||||
app.DataService = DataService;
|
||||
function DataService() { }
|
||||
|
||||
DataService.prototype.getHeroName = function() {
|
||||
return 'Windstorm';
|
||||
};
|
||||
|
||||
})(window.app = window.app || {});
|
@ -1,36 +0,0 @@
|
||||
(function(app) {
|
||||
|
||||
var old = app.HeroComponent;
|
||||
|
||||
app.HeroComponent = HeroComponent;
|
||||
|
||||
HeroComponent.annotations = [
|
||||
new ng.core.Component({
|
||||
selector: 'hero-di-inject-additional',
|
||||
template: '<hero-title title="Tour of Heroes"></hero-title>'
|
||||
})
|
||||
];
|
||||
|
||||
function HeroComponent() {}
|
||||
|
||||
app.HeroDIInjectAdditionalComponent = app.HeroComponent;
|
||||
app.HeroComponent = old;
|
||||
|
||||
})(window.app = window.app || {});
|
||||
|
||||
////// DSL Version /////////
|
||||
(function(app) {
|
||||
|
||||
var old = app.HeroComponent;
|
||||
|
||||
app.HeroComponent = ng.core.Component({
|
||||
selector: 'hero-di-inject-additional-dsl',
|
||||
template: '<hero-title-dsl title="Tour of Heroes"></hero-title-dsl>'
|
||||
}).Class({
|
||||
constructor: function HeroComponent() { }
|
||||
});
|
||||
|
||||
app.HeroDIInjectAdditionalDslComponent = app.HeroComponent;
|
||||
app.HeroComponent = old;
|
||||
|
||||
})(window.app = window.app || {});
|
@ -1,51 +0,0 @@
|
||||
(function(app) {
|
||||
|
||||
var old = app.HeroComponent;
|
||||
|
||||
// #docregion
|
||||
app.HeroComponent = HeroComponent;
|
||||
|
||||
HeroComponent.annotations = [
|
||||
new ng.core.Component({
|
||||
selector: 'hero-di-inject',
|
||||
template: '<h1>Hero: {{name}}</h1>'
|
||||
})
|
||||
];
|
||||
|
||||
HeroComponent.parameters = [ 'heroName' ];
|
||||
|
||||
function HeroComponent(name) {
|
||||
this.name = name;
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
app.HeroDIInjectComponent = app.HeroComponent;
|
||||
app.HeroComponent = old;
|
||||
|
||||
})(window.app = window.app || {});
|
||||
|
||||
/////// DSL version ////////
|
||||
|
||||
(function(app) {
|
||||
|
||||
var old = app.HeroComponent;
|
||||
|
||||
// #docregion dsl
|
||||
app.HeroComponent = ng.core.Component({
|
||||
selector: 'hero-di-inject-dsl',
|
||||
template: '<h1>Hero: {{name}}</h1>'
|
||||
})
|
||||
.Class({
|
||||
constructor: [
|
||||
new ng.core.Inject('heroName'),
|
||||
function HeroComponent(name) {
|
||||
this.name = name;
|
||||
}
|
||||
]
|
||||
});
|
||||
// #enddocregion dsl
|
||||
|
||||
app.HeroDIInjectDslComponent = app.HeroComponent;
|
||||
app.HeroComponent = old;
|
||||
|
||||
})(window.app = window.app || {});
|
@ -1,51 +0,0 @@
|
||||
(function(app) {
|
||||
|
||||
var old = app.HeroComponent;
|
||||
|
||||
// #docregion
|
||||
app.HeroComponent = HeroComponent;
|
||||
|
||||
HeroComponent.annotations = [
|
||||
new ng.core.Component({
|
||||
selector: 'hero-di',
|
||||
template: '<h1>Hero: {{name}}</h1>'
|
||||
})
|
||||
];
|
||||
|
||||
HeroComponent.parameters = [ app.DataService ];
|
||||
|
||||
function HeroComponent(dataService) {
|
||||
this.name = dataService.getHeroName();
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
app.HeroDIComponent = app.HeroComponent;
|
||||
app.HeroComponent = old;
|
||||
|
||||
})(window.app = window.app || {});
|
||||
|
||||
////// DSL Version /////
|
||||
|
||||
(function(app) {
|
||||
|
||||
var old = app.HeroComponent;
|
||||
|
||||
// #docregion dsl
|
||||
app.HeroComponent = ng.core.Component({
|
||||
selector: 'hero-di-dsl',
|
||||
template: '<h1>Hero: {{name}}</h1>'
|
||||
})
|
||||
.Class({
|
||||
constructor: [
|
||||
app.DataService,
|
||||
function HeroComponent(service) {
|
||||
this.name = service.getHeroName();
|
||||
}
|
||||
]
|
||||
});
|
||||
// #enddocregion dsl
|
||||
|
||||
app.HeroDIDslComponent = app.HeroComponent;
|
||||
app.HeroComponent = old;
|
||||
|
||||
})(window.app = window.app || {});
|
@ -1,107 +0,0 @@
|
||||
(function(app) {
|
||||
|
||||
var old = app.HeroComponent
|
||||
|
||||
// #docregion
|
||||
app.HeroComponent = HeroComponent;
|
||||
|
||||
HeroComponent.annotations = [
|
||||
new ng.core.Component({
|
||||
selector: 'hero-host',
|
||||
template:
|
||||
'<h1 [class.active]="active">Hero Host</h1>' +
|
||||
'<div>Heading clicks: {{clicks}}</div>',
|
||||
host: {
|
||||
// HostBindings to the <hero-host> element
|
||||
'[title]': 'title',
|
||||
'[class.heading]': 'headingClass',
|
||||
'(click)': 'clicked()',
|
||||
|
||||
// HostListeners on the entire <hero-host> element
|
||||
'(mouseenter)': 'enter($event)',
|
||||
'(mouseleave)': 'leave($event)'
|
||||
},
|
||||
// Styles within (but excluding) the <hero-host> element
|
||||
styles: ['.active {background-color: yellow;}']
|
||||
})
|
||||
];
|
||||
|
||||
function HeroComponent() {
|
||||
this.clicks = 0;
|
||||
this.headingClass = true;
|
||||
this.title = 'Hero Host Tooltip content';
|
||||
}
|
||||
|
||||
HeroComponent.prototype.clicked = function() {
|
||||
this.clicks += 1;
|
||||
}
|
||||
|
||||
HeroComponent.prototype.enter = function(event) {
|
||||
this.active = true;
|
||||
this.headingClass = false;
|
||||
}
|
||||
|
||||
HeroComponent.prototype.leave = function(event) {
|
||||
this.active = false;
|
||||
this.headingClass = true;
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
app.HeroHostComponent = app.HeroComponent;
|
||||
app.HeroComponent = old;
|
||||
|
||||
})(window.app = window.app || {});
|
||||
|
||||
//// DSL Version ////
|
||||
|
||||
(function(app) {
|
||||
|
||||
var old = app.HeroComponent;
|
||||
|
||||
// #docregion dsl
|
||||
app.HeroComponent = ng.core.Component({
|
||||
selector: 'hero-host-dsl',
|
||||
template: `
|
||||
<h1 [class.active]="active">Hero Host (DSL)</h1>
|
||||
<div>Heading clicks: {{clicks}}</div>
|
||||
`,
|
||||
host: {
|
||||
// HostBindings to the <hero-host-dsl> element
|
||||
'[title]': 'title',
|
||||
'[class.heading]': 'headingClass',
|
||||
'(click)': 'clicked()',
|
||||
|
||||
// HostListeners on the entire <hero-host-dsl> element
|
||||
'(mouseenter)': 'enter($event)',
|
||||
'(mouseleave)': 'leave($event)'
|
||||
},
|
||||
// Styles within (but excluding) the <hero-host-dsl> element
|
||||
styles: ['.active {background-color: coral;}']
|
||||
})
|
||||
.Class({
|
||||
constructor: function HeroComponent() {
|
||||
this.clicks = 0;
|
||||
this.headingClass = true;
|
||||
this.title = 'Hero Host Tooltip DSL content';
|
||||
},
|
||||
|
||||
clicked() {
|
||||
this.clicks += 1;
|
||||
},
|
||||
|
||||
enter(event) {
|
||||
this.active = true;
|
||||
this.headingClass = false;
|
||||
},
|
||||
|
||||
leave(event) {
|
||||
this.active = false;
|
||||
this.headingClass = true;
|
||||
}
|
||||
});
|
||||
// #enddocregion dsl
|
||||
|
||||
app.HeroHostDslComponent = app.HeroComponent;
|
||||
app.HeroComponent = old;
|
||||
|
||||
})(window.app = window.app || {});
|
@ -1,7 +0,0 @@
|
||||
<app-confirm-dsl [okMsg]="'OK'"
|
||||
[cancelMsg]="'Cancel'"
|
||||
(ok)="onOk()"
|
||||
(cancel)="onCancel()">
|
||||
</app-confirm-dsl>
|
||||
<span *ngIf="okClicked">OK clicked</span>
|
||||
<span *ngIf="cancelClicked">Cancel clicked</span>
|
@ -1,7 +0,0 @@
|
||||
<app-confirm [okMsg]="'OK'"
|
||||
[cancelMsg]="'Cancel'"
|
||||
(ok)="onOk()"
|
||||
(cancel)="onCancel()">
|
||||
</app-confirm>
|
||||
<span *ngIf="okClicked">OK clicked</span>
|
||||
<span *ngIf="cancelClicked">Cancel clicked</span>
|
@ -1,52 +0,0 @@
|
||||
(function(app) {
|
||||
|
||||
var old = app.HeroComponent
|
||||
|
||||
app.HeroComponent = HeroComponent;
|
||||
|
||||
HeroComponent.annotations = [
|
||||
new ng.core.Component({
|
||||
selector: 'hero-io',
|
||||
templateUrl: 'app/hero-io.component.html'
|
||||
})
|
||||
];
|
||||
|
||||
function HeroComponent() { }
|
||||
|
||||
HeroComponent.prototype.onOk = function() {
|
||||
this.okClicked = true;
|
||||
}
|
||||
|
||||
HeroComponent.prototype.onCancel = function() {
|
||||
this.cancelClicked = true;
|
||||
}
|
||||
|
||||
app.HeroIOComponent = app.HeroComponent;
|
||||
app.HeroComponent = old;
|
||||
|
||||
})(window.app = window.app || {});
|
||||
|
||||
///// DSL Version ////
|
||||
|
||||
(function(app) {
|
||||
|
||||
var old = app.HeroComponent
|
||||
|
||||
app.HeroComponent = ng.core.Component({
|
||||
selector: 'hero-io-dsl',
|
||||
templateUrl: 'app/hero-io-dsl.component.html'
|
||||
})
|
||||
.Class({
|
||||
constructor: function HeroComponent() { },
|
||||
onOk: function() {
|
||||
this.okClicked = true;
|
||||
},
|
||||
onCancel: function() {
|
||||
this.cancelClicked = true;
|
||||
}
|
||||
});
|
||||
|
||||
app.HeroIODslComponent = app.HeroComponent;
|
||||
app.HeroComponent = old;
|
||||
|
||||
})(window.app = window.app || {});
|
@ -1,52 +0,0 @@
|
||||
// #docplaster
|
||||
(function(app) {
|
||||
|
||||
var old = app.HeroComponent;
|
||||
|
||||
// #docregion
|
||||
app.HeroComponent = HeroComponent;
|
||||
|
||||
HeroComponent.annotations = [
|
||||
new ng.core.Component({
|
||||
selector: 'hero-lifecycle',
|
||||
template: '<h1>Hero: {{name}}</h1>'
|
||||
})
|
||||
];
|
||||
|
||||
function HeroComponent() { }
|
||||
|
||||
HeroComponent.prototype.ngOnInit = function() {
|
||||
// todo: fetch from server async
|
||||
setTimeout(() => this.name = 'Windstorm', 0);
|
||||
};
|
||||
// #enddocregion
|
||||
|
||||
app.HeroLifecycleComponent = app.HeroComponent;
|
||||
app.HeroComponent = old;
|
||||
|
||||
})(window.app = window.app || {});
|
||||
|
||||
/////// DSL version ////
|
||||
|
||||
(function(app) {
|
||||
|
||||
var old = app.HeroComponent;
|
||||
|
||||
// #docregion dsl
|
||||
app.HeroComponent = ng.core.Component({
|
||||
selector: 'hero-lifecycle-dsl',
|
||||
template: '<h1>Hero: {{name}}</h1>'
|
||||
})
|
||||
.Class({
|
||||
constructor: function HeroComponent() { },
|
||||
ngOnInit: function() {
|
||||
// todo: fetch from server async
|
||||
setTimeout(() => this.name = 'Windstorm', 0);
|
||||
}
|
||||
});
|
||||
// #enddocregion dsl
|
||||
|
||||
app.HeroLifecycleDslComponent = app.HeroComponent;
|
||||
app.HeroComponent = old;
|
||||
|
||||
})(window.app = window.app || {});
|
@ -1,92 +0,0 @@
|
||||
(function(app) {
|
||||
|
||||
app.heroQueries = app.heroQueries || {};
|
||||
|
||||
app.heroQueries.ContentChildComponent = ng.core.Component({
|
||||
selector: 'content-child',
|
||||
template:
|
||||
'<span class="content-child" *ngIf="active">' +
|
||||
'Active' +
|
||||
'</span>'
|
||||
}).Class({
|
||||
constructor: function ContentChildComponent() {
|
||||
this.active = false;
|
||||
},
|
||||
|
||||
activate: function() {
|
||||
this.active = !this.active;
|
||||
}
|
||||
});
|
||||
|
||||
////////////////////
|
||||
|
||||
// #docregion content
|
||||
app.heroQueries.ViewChildComponent = ng.core.Component({
|
||||
selector: 'view-child',
|
||||
template:
|
||||
'<h2 [class.active]=active>' +
|
||||
'{{hero.name}} ' +
|
||||
'<ng-content></ng-content>' +
|
||||
'</h2>',
|
||||
styles: ['.active {font-weight: bold; background-color: skyblue;}'],
|
||||
inputs: ['hero'],
|
||||
queries: {
|
||||
content: new ng.core.ContentChild(app.heroQueries.ContentChildComponent)
|
||||
}
|
||||
})
|
||||
.Class({
|
||||
constructor: function HeroQueriesHeroComponent() {
|
||||
this.active = false;
|
||||
},
|
||||
|
||||
activate: function() {
|
||||
this.active = !this.active;
|
||||
this.content.activate();
|
||||
}
|
||||
});
|
||||
// #enddocregion content
|
||||
|
||||
////////////////////
|
||||
|
||||
// #docregion view
|
||||
app.heroQueries.HeroQueriesComponent = ng.core.Component({
|
||||
selector: 'hero-queries',
|
||||
template:
|
||||
'<view-child *ngFor="let hero of heroData" [hero]="hero">' +
|
||||
'<content-child></content-child>' +
|
||||
'</view-child>' +
|
||||
'<button (click)="activate()">{{buttonLabel}} All</button>',
|
||||
queries: {
|
||||
views: new ng.core.ViewChildren(app.heroQueries.ViewChildComponent)
|
||||
}
|
||||
})
|
||||
.Class({
|
||||
constructor: function HeroQueriesComponent() {
|
||||
this.active = false;
|
||||
this.heroData = [
|
||||
{id: 1, name: 'Windstorm'},
|
||||
{id: 2, name: 'LaughingGas'}
|
||||
];
|
||||
},
|
||||
|
||||
activate: function() {
|
||||
this.active = !this.active;
|
||||
this.views.forEach(function(view) {
|
||||
view.activate();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// #docregion defined-property
|
||||
// add prototype property w/ getter outside the DSL
|
||||
var proto = app.heroQueries.HeroQueriesComponent.prototype;
|
||||
Object.defineProperty(proto, "buttonLabel", {
|
||||
get: function () {
|
||||
return this.active ? 'Deactivate' : 'Activate';
|
||||
},
|
||||
enumerable: true
|
||||
});
|
||||
// #enddocregion defined-property
|
||||
// #enddocregion view
|
||||
|
||||
})(window.app = window.app || {});
|
@ -1,4 +0,0 @@
|
||||
<!-- #docregion -->
|
||||
<h1>{{titlePrefix}} {{title}}</h1>
|
||||
<button (click)="ok()">OK</button>
|
||||
<p>{{ msg }}</p>
|
@ -1,64 +0,0 @@
|
||||
(function(app) {
|
||||
|
||||
// #docregion
|
||||
app.HeroTitleComponent = HeroTitleComponent;
|
||||
|
||||
// #docregion templateUrl
|
||||
HeroTitleComponent.annotations = [
|
||||
new ng.core.Component({
|
||||
selector: 'hero-title',
|
||||
templateUrl: 'app/hero-title.component.html'
|
||||
})
|
||||
];
|
||||
// #enddocregion templateUrl
|
||||
|
||||
function HeroTitleComponent(titlePrefix, title) {
|
||||
this.titlePrefix = titlePrefix;
|
||||
this.title = title;
|
||||
this.msg = '';
|
||||
}
|
||||
|
||||
HeroTitleComponent.prototype.ok = function() {
|
||||
this.msg = 'OK!';
|
||||
}
|
||||
|
||||
HeroTitleComponent.parameters = [
|
||||
[new ng.core.Optional(), new ng.core.Inject('titlePrefix')],
|
||||
[new ng.core.Attribute('title')]
|
||||
];
|
||||
// #enddocregion
|
||||
|
||||
})(window.app = window.app || {});
|
||||
|
||||
////////// DSL version ////////////
|
||||
|
||||
(function(app) {
|
||||
|
||||
var old = app.HeroTitleComponent;
|
||||
|
||||
// #docregion dsl
|
||||
app.HeroTitleComponent = ng.core.Component({
|
||||
selector: 'hero-title-dsl',
|
||||
templateUrl: 'app/hero-title.component.html'
|
||||
})
|
||||
.Class({
|
||||
constructor: [
|
||||
[ new ng.core.Optional(), new ng.core.Inject('titlePrefix') ],
|
||||
new ng.core.Attribute('title'),
|
||||
function HeroTitleComponent(titlePrefix, title) {
|
||||
this.titlePrefix = titlePrefix;
|
||||
this.title = title;
|
||||
this.msg = '';
|
||||
}
|
||||
],
|
||||
|
||||
ok: function() {
|
||||
this.msg = 'OK!';
|
||||
}
|
||||
});
|
||||
// #enddocregion dsl
|
||||
|
||||
app.HeroTitleDslComponent = app.HeroTitleComponent;
|
||||
app.HeroTitleComponent = old;
|
||||
|
||||
})(window.app = window.app || {});
|
@ -1,53 +0,0 @@
|
||||
// #docplaster
|
||||
(function(app) {
|
||||
|
||||
// #docregion
|
||||
// #docregion appexport
|
||||
// #docregion metadata
|
||||
app.HeroComponent = HeroComponent; // "export"
|
||||
|
||||
HeroComponent.annotations = [
|
||||
new ng.core.Component({
|
||||
selector: 'hero-view',
|
||||
template: '<h1>{{title}}: {{getName()}}</h1>'
|
||||
})
|
||||
];
|
||||
|
||||
// #docregion constructorproto
|
||||
function HeroComponent() {
|
||||
this.title = "Hero Detail";
|
||||
}
|
||||
|
||||
HeroComponent.prototype.getName = function() { return 'Windstorm'; };
|
||||
// #enddocregion constructorproto
|
||||
|
||||
// #enddocregion metadata
|
||||
// #enddocregion appexport
|
||||
// #enddocregion
|
||||
|
||||
})(window.app = window.app || {});
|
||||
|
||||
//////////// DSL version ///////////
|
||||
|
||||
(function(app) {
|
||||
|
||||
var old = app.HeroComponent;
|
||||
|
||||
// #docregion dsl
|
||||
app.HeroComponent = ng.core.Component({
|
||||
selector: 'hero-view-dsl',
|
||||
template: '<h1>{{title}}: {{getName()}}</h1>',
|
||||
})
|
||||
.Class({
|
||||
constructor: function HeroComponent() {
|
||||
this.title = "Hero Detail";
|
||||
},
|
||||
|
||||
getName: function() { return 'Windstorm'; }
|
||||
});
|
||||
// #enddocregion dsl
|
||||
|
||||
app.HeroDslComponent = app.HeroComponent;
|
||||
app.HeroComponent = old;
|
||||
|
||||
})(window.app = window.app || {});
|
@ -1,43 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<title>TypeScript to JavaScript</title>
|
||||
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
|
||||
<!-- Angular and RxJS umd scripts -->
|
||||
<script src="node_modules/rxjs/bundles/Rx.js"></script>
|
||||
<script src="node_modules/@angular/core/bundles/core.umd.js"></script>
|
||||
<script src="node_modules/@angular/common/bundles/common.umd.js"></script>
|
||||
<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>
|
||||
|
||||
<!-- Application scripts -->
|
||||
<script src="app/app.component.js"></script>
|
||||
<script src="app/confirm.component.js"></script>
|
||||
<script src="app/data.service.js"></script>
|
||||
<script src="app/hero.component.js"></script>
|
||||
<script src="app/hero-io.component.js"></script>
|
||||
<script src="app/hero-di.component.js"></script>
|
||||
<script src="app/hero-di-inject.component.js"></script>
|
||||
<script src="app/hero-di-inject-additional.component.js"></script>
|
||||
<script src="app/hero-host.component.js"></script>
|
||||
<script src="app/hero-lifecycle.component.js"></script>
|
||||
<script src="app/hero-queries.component.js"></script>
|
||||
<script src="app/hero-title.component.js"></script>
|
||||
|
||||
<script src="app/app.module.js"></script>
|
||||
<script src="main.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<my-app>Loading...</my-app>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,9 +0,0 @@
|
||||
(function(app) {
|
||||
|
||||
var platformBrowserDynamic = ng.platformBrowserDynamic.platformBrowserDynamic;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
platformBrowserDynamic().bootstrapModule(app.AppModule);
|
||||
});
|
||||
|
||||
})(window.app = window.app || {});
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"description": "TypeScript to JavaScript",
|
||||
"basePath": "src/",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js"
|
||||
],
|
||||
"tags":["cookbook"]
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
<a id="toc"></a>
|
||||
<h1>{{title}}</h1>
|
||||
<a href="#class-metadata">Classes and Class Metadata</a><br>
|
||||
<a href="#io-metadata">Input and Output Decorators</a><br>
|
||||
<a href="#dependency-injection">Dependency Injection</a><br>
|
||||
<a href="#host-metadata">Host Metadata</a><br>
|
||||
<a href="#view-child-metadata">View and Child Metadata</a><br>
|
||||
|
||||
<hr>
|
||||
<h4 id="class-metadata">Classes and Class Metadata</h4>
|
||||
<hero-view></hero-view>
|
||||
<hero-lifecycle></hero-lifecycle>
|
||||
|
||||
<hr>
|
||||
<h4 id="io-metadata">Input and Output Metadata</h4>
|
||||
<hero-io></hero-io>
|
||||
|
||||
<hr>
|
||||
<h4 id="dependency-injection">Dependency Injection</h4>
|
||||
<hero-di></hero-di>
|
||||
<hero-di-inject></hero-di-inject>
|
||||
<hero-di-inject-additional></hero-di-inject-additional>
|
||||
|
||||
<hr>
|
||||
<h4 id="host-metadata">Host Metadata</h4>
|
||||
<hero-host></hero-host>
|
||||
<hero-host-meta></hero-host-meta>
|
||||
|
||||
<hr>
|
||||
<h4 id="view-child-metadata">View and Child Metadata</h4>
|
||||
<hero-queries></hero-queries>
|
@ -1,14 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
templateUrl: './app.component.html',
|
||||
styles: [
|
||||
// See hero-di-inject-additional.component
|
||||
'hero-host, hero-host-meta { border: 1px dashed black; display: block; padding: 4px;}',
|
||||
'.heading {font-style: italic}'
|
||||
]
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'TypeScript';
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
/* tslint:disable-next-line:no-unused-variable */
|
||||
import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { ConfirmComponent } from './confirm.component';
|
||||
// #docregion appimport
|
||||
import { HeroComponent } from './hero.component';
|
||||
// #enddocregion appimport
|
||||
import { HeroComponent as HeroDIComponent } from './hero-di.component';
|
||||
import { HeroComponent as HeroDIInjectComponent } from './hero-di-inject.component';
|
||||
import { HeroComponent as HeroDIInjectAdditionalComponent } from './hero-di-inject-additional.component';
|
||||
import { HeroHostComponent } from './hero-host.component';
|
||||
import { HeroHostMetaComponent } from './hero-host-meta.component';
|
||||
import { HeroIOComponent } from './hero-io.component';
|
||||
import { HeroComponent as HeroLifecycleComponent } from './hero-lifecycle.component';
|
||||
import { HeroQueriesComponent, ViewChildComponent, ContentChildComponent } from './hero-queries.component';
|
||||
import { HeroTitleComponent } from './hero-title.component';
|
||||
|
||||
import { DataService } from './data.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
ConfirmComponent,
|
||||
HeroComponent,
|
||||
HeroDIComponent,
|
||||
HeroDIInjectComponent,
|
||||
HeroDIInjectAdditionalComponent,
|
||||
HeroHostComponent, HeroHostMetaComponent,
|
||||
HeroIOComponent,
|
||||
HeroLifecycleComponent,
|
||||
HeroQueriesComponent, ViewChildComponent, ContentChildComponent,
|
||||
HeroTitleComponent
|
||||
],
|
||||
providers: [
|
||||
DataService,
|
||||
{ provide: 'heroName', useValue: 'Windstorm' }
|
||||
],
|
||||
bootstrap: [ AppComponent ],
|
||||
|
||||
// schemas: [ NO_ERRORS_SCHEMA ] // helpful for debugging!
|
||||
})
|
||||
export class AppModule { }
|
||||
|
||||
/* tslint:disable no-unused-variable */
|
||||
// #docregion ng2import
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import {
|
||||
LocationStrategy,
|
||||
HashLocationStrategy
|
||||
} from '@angular/common';
|
||||
// #enddocregion ng2import
|
@ -1,6 +0,0 @@
|
||||
<button (click)="onOkClick()">
|
||||
{{okMsg}}
|
||||
</button>
|
||||
<button (click)="onNotOkClick()">
|
||||
{{notOkMsg}}
|
||||
</button>
|
@ -1,21 +0,0 @@
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
// #docregion
|
||||
@Component({
|
||||
selector: 'app-confirm',
|
||||
templateUrl: './confirm.component.html'
|
||||
})
|
||||
export class ConfirmComponent {
|
||||
@Input() okMsg = '';
|
||||
@Input('cancelMsg') notOkMsg = '';
|
||||
@Output() ok = new EventEmitter();
|
||||
@Output('cancel') notOk = new EventEmitter();
|
||||
|
||||
onOkClick() {
|
||||
this.ok.emit(true);
|
||||
}
|
||||
onNotOkClick() {
|
||||
this.notOk.emit(true);
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
@ -1,10 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class DataService {
|
||||
constructor() { }
|
||||
|
||||
getHeroName() {
|
||||
return 'Windstorm';
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-di-inject-additional',
|
||||
template: `<hero-title title="Tour of Heroes"></hero-title>`
|
||||
})
|
||||
export class HeroComponent { }
|
@ -1,11 +0,0 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
|
||||
// #docregion
|
||||
@Component({
|
||||
selector: 'hero-di-inject',
|
||||
template: `<h1>Hero: {{name}}</h1>`
|
||||
})
|
||||
export class HeroComponent {
|
||||
constructor(@Inject('heroName') private name: string) { }
|
||||
}
|
||||
// #enddocregion
|
@ -1,15 +0,0 @@
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
import { DataService } from './data.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-di',
|
||||
template: `<h1>Hero: {{name}}</h1>`
|
||||
})
|
||||
export class HeroComponent {
|
||||
name = '';
|
||||
constructor(dataService: DataService) {
|
||||
this.name = dataService.getHeroName();
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
@ -1,44 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
// #docregion
|
||||
@Component({
|
||||
selector: 'hero-host-meta',
|
||||
template: `
|
||||
<h1 [class.active]="active">Hero Host in Metadata</h1>
|
||||
<div>Heading clicks: {{clicks}}</div>
|
||||
`,
|
||||
host: {
|
||||
// HostBindings to the <hero-host-meta> element
|
||||
'[title]': 'title',
|
||||
'[class.heading]': 'headingClass',
|
||||
|
||||
// HostListeners on the entire <hero-host-meta> element
|
||||
'(click)': 'clicked()',
|
||||
'(mouseenter)': 'enter($event)',
|
||||
'(mouseleave)': 'leave($event)'
|
||||
},
|
||||
// Styles within (but excluding) the <hero-host-meta> element
|
||||
styles: ['.active {background-color: coral;}']
|
||||
})
|
||||
export class HeroHostMetaComponent {
|
||||
title = 'Hero Host in Metadata Tooltip';
|
||||
headingClass = true;
|
||||
|
||||
active = false;
|
||||
clicks = 0;
|
||||
|
||||
clicked() {
|
||||
this.clicks += 1;
|
||||
}
|
||||
|
||||
enter(event: Event) {
|
||||
this.active = true;
|
||||
this.headingClass = false;
|
||||
}
|
||||
|
||||
leave(event: Event) {
|
||||
this.active = false;
|
||||
this.headingClass = true;
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
@ -1,39 +0,0 @@
|
||||
import { Component, HostBinding, HostListener } from '@angular/core';
|
||||
|
||||
// #docregion
|
||||
@Component({
|
||||
selector: 'hero-host',
|
||||
template: `
|
||||
<h1 [class.active]="active">Hero Host in Decorators</h1>
|
||||
<div>Heading clicks: {{clicks}}</div>
|
||||
`,
|
||||
// Styles within (but excluding) the <hero-host> element
|
||||
styles: ['.active {background-color: yellow;}']
|
||||
})
|
||||
export class HeroHostComponent {
|
||||
// HostBindings to the <hero-host> element
|
||||
@HostBinding() title = 'Hero Host in Decorators Tooltip';
|
||||
@HostBinding('class.heading') headingClass = true;
|
||||
|
||||
active = false;
|
||||
clicks = 0;
|
||||
|
||||
// HostListeners on the entire <hero-host> element
|
||||
@HostListener('click')
|
||||
clicked() {
|
||||
this.clicks += 1;
|
||||
}
|
||||
|
||||
@HostListener('mouseenter', ['$event'])
|
||||
enter(event: Event) {
|
||||
this.active = true;
|
||||
this.headingClass = false;
|
||||
}
|
||||
|
||||
@HostListener('mouseleave', ['$event'])
|
||||
leave(event: Event) {
|
||||
this.active = false;
|
||||
this.headingClass = true;
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
@ -1,26 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-io',
|
||||
template: `
|
||||
<app-confirm [okMsg]="'OK'"
|
||||
[cancelMsg]="'Cancel'"
|
||||
(ok)="onOk()"
|
||||
(cancel)="onCancel()">
|
||||
</app-confirm>
|
||||
<span *ngIf="okClicked">OK clicked</span>
|
||||
<span *ngIf="cancelClicked">Cancel clicked</span>
|
||||
`
|
||||
})
|
||||
export class HeroIOComponent {
|
||||
okClicked = false;
|
||||
cancelClicked = false;
|
||||
|
||||
onOk() {
|
||||
this.okClicked = true;
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
this.cancelClicked = true;
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
// #docregion
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-lifecycle',
|
||||
template: `<h1>Hero: {{name}}</h1>`
|
||||
})
|
||||
export class HeroComponent implements OnInit {
|
||||
name: string;
|
||||
ngOnInit() {
|
||||
// todo: fetch from server async
|
||||
setTimeout(() => this.name = 'Windstorm', 0);
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
import {
|
||||
Component,
|
||||
ContentChild,
|
||||
Input,
|
||||
QueryList,
|
||||
ViewChildren
|
||||
} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'content-child',
|
||||
template: `
|
||||
<span class="content-child" *ngIf="active">
|
||||
Active
|
||||
</span>`
|
||||
})
|
||||
export class ContentChildComponent {
|
||||
active = false;
|
||||
|
||||
activate() {
|
||||
this.active = !this.active;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////
|
||||
|
||||
// #docregion content
|
||||
@Component({
|
||||
selector: 'view-child',
|
||||
template: `
|
||||
<h2 [class.active]=active>
|
||||
{{hero.name}}
|
||||
<ng-content></ng-content>
|
||||
</h2>`,
|
||||
styles: ['.active {font-weight: bold; background-color: skyblue;}']
|
||||
})
|
||||
export class ViewChildComponent {
|
||||
@Input() hero: any;
|
||||
active = false;
|
||||
|
||||
@ContentChild(ContentChildComponent) content: ContentChildComponent;
|
||||
|
||||
activate() {
|
||||
this.active = !this.active;
|
||||
this.content.activate();
|
||||
}
|
||||
}
|
||||
// #enddocregion content
|
||||
|
||||
////////////////////
|
||||
|
||||
// #docregion view
|
||||
@Component({
|
||||
selector: 'hero-queries',
|
||||
template: `
|
||||
<view-child *ngFor="let hero of heroData" [hero]="hero">
|
||||
<content-child></content-child>
|
||||
</view-child>
|
||||
<button (click)="activate()">{{buttonLabel}} All</button>
|
||||
`
|
||||
})
|
||||
export class HeroQueriesComponent {
|
||||
active = false;
|
||||
heroData = [
|
||||
{id: 1, name: 'Windstorm'},
|
||||
{id: 2, name: 'LaughingGas'}
|
||||
];
|
||||
|
||||
@ViewChildren(ViewChildComponent) views: QueryList<ViewChildComponent>;
|
||||
|
||||
activate() {
|
||||
this.active = !this.active;
|
||||
this.views.forEach(
|
||||
view => view.activate()
|
||||
);
|
||||
}
|
||||
|
||||
// #docregion defined-property
|
||||
get buttonLabel() {
|
||||
return this.active ? 'Deactivate' : 'Activate';
|
||||
}
|
||||
// #enddocregion defined-property
|
||||
}
|
||||
// #enddocregion view
|
@ -1,4 +0,0 @@
|
||||
<!-- #docregion -->
|
||||
<h1>{{titlePrefix}} {{title}}</h1>
|
||||
<button (click)="ok()">OK</button>
|
||||
<p>{{ msg }}</p>
|
@ -1,21 +0,0 @@
|
||||
import { Attribute, Component, Inject, Optional } from '@angular/core';
|
||||
|
||||
// #docregion
|
||||
// #docregion templateUrl
|
||||
@Component({
|
||||
selector: 'hero-title',
|
||||
templateUrl: './hero-title.component.html'
|
||||
})
|
||||
// #enddocregion templateUrl
|
||||
export class HeroTitleComponent {
|
||||
msg = '';
|
||||
constructor(
|
||||
@Inject('titlePrefix') @Optional() private titlePrefix: string,
|
||||
@Attribute('title') private title: string
|
||||
) { }
|
||||
|
||||
ok() {
|
||||
this.msg = 'OK!';
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
@ -1,15 +0,0 @@
|
||||
// #docregion
|
||||
// #docregion metadata
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-view',
|
||||
template: '<h1>{{title}}: {{getName()}}</h1>'
|
||||
})
|
||||
// #docregion appexport, class
|
||||
export class HeroComponent {
|
||||
title = 'Hero Detail';
|
||||
getName() {return 'Windstorm'; }
|
||||
}
|
||||
// #enddocregion appexport, class
|
||||
// #enddocregion metadata
|
@ -1,26 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<base href="/">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<title>TypeScript to JavaScript</title>
|
||||
|
||||
<!-- Polyfills -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('main.js').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<my-app>Loading...</my-app>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,4 +0,0 @@
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { AppModule } from './app/app.module';
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
@ -120,8 +120,8 @@ The documentation for the version prior to v.2.2.0 has been removed.
|
||||
|
||||
## ES6 described in "TypeScript to JavaScript" (2016-11-14)
|
||||
|
||||
The updated [TypeScript to JavaScript](guide/ts-to-js) guide
|
||||
now explains how to write apps in ES6/7
|
||||
The updated TypeScript to JavaScript guide (removed August 2017, PR #18694)
|
||||
explains how to write apps in ES6/7
|
||||
by translating the common idioms in the TypeScript documentation examples
|
||||
(and elsewhere on the web) to ES6/7 and ES5.
|
||||
|
||||
|
874
aio/content/guide/metadata.md
Normal file
874
aio/content/guide/metadata.md
Normal file
@ -0,0 +1,874 @@
|
||||
# Angular Metadata and AOT
|
||||
|
||||
The Angular **AOT compiler** turns your TypeScript source code into runnable JavaScript.
|
||||
As part of that process, the compiler extracts and interprets **metadata** about the parts of the application that Angular is supposed to manage.
|
||||
|
||||
You write metadata in a _subset_ of TypeScript. This guide explains why a subset is necessary, describes the subset constraints, and what happens when you step outside of those constraints.
|
||||
|
||||
## Angular metadata
|
||||
Angular metadata tells Angular how to construct instances of your application classes and interact with them at runtime.
|
||||
|
||||
You specify the metadata with **decorators** such as `@Component()` and `@Input()`.
|
||||
You also specify metadata implicitly in the constructor declarations of these decorated classes.
|
||||
|
||||
In the following example, the `@Component()` metadata object and the class constructor tell Angular how to create and display an instance of `TypicalComponent`.
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
selector: 'app-typical',
|
||||
template: 'div>A typical component for {{data.name}}</div>
|
||||
)}
|
||||
export class TypicalComponent {
|
||||
@Input() data: TypicalData;
|
||||
constructor(private someService: SomeService) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
The Angular compiler extracts the metadata _once_ and generates a _factory_ for `TypicalComponent`.
|
||||
When it needs to create a `TypicalComponent` instance, Angular calls the factory, which produces a new visual element, bound to a new instance of the component class with its injected dependency.
|
||||
|
||||
## Compile ahead-of-time (AOT)
|
||||
|
||||
You should use AOT to compile an application that must launch quickly.
|
||||
With AOT, there is no runtime compile step.
|
||||
The client doesn't need the compiler library at all and excluding it significantly reduces the total payload.
|
||||
The browser downloads a smaller set of safely-compiled, application module(s) and libraries that it can parse quickly and run almost immediately.
|
||||
|
||||
The AOT compiler produces a number of files, including the application JavaScript that ultimately runs in the browser. It then statically analyzes your source code and interprets the Angular metadata without actually running the application.
|
||||
|
||||
To compile the app, run the `ngc` stand-alone tool as part of your build process.
|
||||
When using the CLI, run the `ng build` command.
|
||||
|
||||
For more information on AOT, see [Ahead-of-Time Compilation](guide/aot-compiler).
|
||||
|
||||
## Metadata restrictions
|
||||
|
||||
Angular metadata expressions must conform to the following general constraints:
|
||||
|
||||
1. Limit [expression syntax](#expression-syntax) to the supported subset of JavaScript.
|
||||
2. Only reference exported symbols after [code folding](#folding).
|
||||
3. Only call [functions supported](#supported-functions) by the compiler.
|
||||
4. Decorated and data-bound class members must be public.
|
||||
|
||||
The next sections elaborate on these points.
|
||||
|
||||
## How AOT works
|
||||
|
||||
It helps to think of the AOT compiler as having two phases: a code analysis phase in which it simply records a representation of the source; and a code generation phase in which the compiler's `StaticReflector` handles the interpretation as well as places restrictions on what it interprets.
|
||||
|
||||
## Phase 1: analysis
|
||||
|
||||
The TypeScript compiler does some of the analytic work of the first phase. It emits the `.d.ts` _type definition files_ with type information that the AOT compiler needs to generate application code.
|
||||
|
||||
At the same time, the AOT **_collector_** analyzes the metadata recorded in the Angular decorators and outputs metadata information in **`.metadata.json`** files, one per `.d.ts` file.
|
||||
|
||||
You can think of `.metadata.json` as a diagram of the overall structure of a decorator's metadata, represented as an [abstract syntax tree (AST)](https://en.wikipedia.org/wiki/Abstract_syntax_tree).
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
Angular's [schema.ts](https://github.com/angular/angular/blob/master/packages/tsc-wrapped/src/schema.ts)
|
||||
describes the JSON format as a collection of TypeScript interfaces.
|
||||
|
||||
</div>
|
||||
|
||||
{@a expression-syntax}
|
||||
### Expression syntax
|
||||
|
||||
The _collector_ only understands a subset of JavaScript.
|
||||
Define metadata objects with the following limited syntax:
|
||||
|
||||
Syntax | Example
|
||||
-----------------------------------|-----------------------------------
|
||||
Literal object | `{cherry: true, apple: true, mincemeat: false}`
|
||||
Literal array | `['cherries', 'flour', 'sugar']`
|
||||
Spread in literal array | `['apples', 'flour', ...the_rest]`
|
||||
Calls | `bake(ingredients)`
|
||||
New | `new Oven()`
|
||||
Property access | `pie.slice`
|
||||
Array index | `ingredients[0]`
|
||||
Identifier reference | `Component`
|
||||
A template string | <code>`pie is ${multiplier} times better than cake`</code>
|
||||
Literal string | `'pi'`
|
||||
Literal number | `3.14153265`
|
||||
Literal boolean | `true`
|
||||
Literal null | `null`
|
||||
Supported prefix operator | `!cake`
|
||||
Supported Binary operator | `a + b`
|
||||
Conditional operator | `a ? b : c`
|
||||
Parentheses | `(a + b)`
|
||||
|
||||
If an expression uses unsupported syntax, the _collector_ writes an error node to the `.metadata.json` file. The compiler later reports the error if it needs that
|
||||
piece of metadata to generate the application code.
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
If you want `ngc` to report syntax errors immediately rather than produce a `.metadata.json` file with errors, set the `strictMetadataEmit` option in `tsconfig`.
|
||||
|
||||
```
|
||||
"angularCompilerOptions": {
|
||||
...
|
||||
"strictMetadataEmit" : true
|
||||
}
|
||||
```
|
||||
|
||||
Angular libraries have this option to ensure that all Angular `.metadata.json` files are clean and it is a best practice to do the same when building your own libraries.
|
||||
|
||||
</div>
|
||||
|
||||
{@a function-expression}
|
||||
{@a arror-functions}
|
||||
### No arrow functions
|
||||
|
||||
The AOT compiler does not support [function expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function)
|
||||
and [arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions), also called _lambda_ functions.
|
||||
|
||||
Consider the following component decorator:
|
||||
|
||||
```ts
|
||||
@Component({
|
||||
...
|
||||
providers: [{provide: server, useFactory: () => new Server()}]
|
||||
})
|
||||
```
|
||||
|
||||
The AOT _collector_ does not support the arrow function, `() => new Server()`, in a metadata expression.
|
||||
It generates an error node in place of the function.
|
||||
|
||||
When the compiler later interprets this node, it reports an error that invites you to turn the arrow function into an _exported function_.
|
||||
|
||||
You can fix the error by converting to this:
|
||||
|
||||
```ts
|
||||
export function serverFactory() {
|
||||
return new Server();
|
||||
}
|
||||
|
||||
@Component({
|
||||
...
|
||||
providers: [{provide: server, useFactory: serverFactory}]
|
||||
})
|
||||
```
|
||||
|
||||
### Limited function calls
|
||||
|
||||
The _collector_ can represent a function call or object creation with `new` as long as the syntax is valid. The _collector_ only cares about proper syntax.
|
||||
|
||||
But beware. The compiler may later refuse to generate a call to a _particular_ function or creation of a _particular_ object.
|
||||
The compiler only supports calls to a small set of functions and will use `new` for only a few designated classes. These functions and classes are in a table of [below](#supported-functions).
|
||||
|
||||
|
||||
### Folding
|
||||
{@a exported-symbols}
|
||||
The compiler can only resolve references to **_exported_** symbols.
|
||||
Fortunately, the _collector_ enables limited use of non-exported symbols through _folding_.
|
||||
|
||||
The _collector_ may be able to evaluate an expression during collection and record the result in the `.metadata.json` instead of the original expression.
|
||||
|
||||
For example, the _collector_ can evaluate the expression `1 + 2 + 3 + 4` and replace it with the result, `10`.
|
||||
|
||||
This process is called _folding_. An expression that can be reduced in this manner is _foldable_.
|
||||
|
||||
{@a var-declaration}
|
||||
The collector can evaluate references to
|
||||
module-local `const` declarations and initialized `var` and `let` declarations, effectively removing them from the `.metadata.json` file.
|
||||
|
||||
Consider the following component definition:
|
||||
|
||||
```ts
|
||||
const template = '<div>{{hero.name}}</div>';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero',
|
||||
template: template
|
||||
})
|
||||
class HeroComponent {
|
||||
@Input() hero: Hero;
|
||||
}
|
||||
```
|
||||
|
||||
The compiler could not refer to the `template` constant because it isn't exported.
|
||||
|
||||
But the _collector_ can _fold_ the `template` constant into the metadata definition by inlining its contents.
|
||||
The effect is the same as if you had written:
|
||||
|
||||
```TypeScript
|
||||
@Component({
|
||||
selector: 'app-hero',
|
||||
template: '<div>{{hero.name}}</div>'
|
||||
})
|
||||
class HeroComponent {
|
||||
@Input() hero: Hero;
|
||||
}
|
||||
```
|
||||
|
||||
There is no longer a reference to `template` and, therefore, nothing to trouble the compiler when it later interprets the _collector's_ output in `.metadata.json`.
|
||||
|
||||
You can take this example a step further by including the `template` constant in another expression:
|
||||
|
||||
```TypeScript
|
||||
const template = '<div>{{hero.name}}</div>';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero',
|
||||
template: template + '<div>{{hero.title}}</div>'
|
||||
})
|
||||
class HeroComponent {
|
||||
@Input() hero: Hero;
|
||||
}
|
||||
```
|
||||
|
||||
The _collector_ reduces this expression to its equivalent _folded_ string:
|
||||
|
||||
`'<div>{{hero.name}}</div><div>{{hero.title}}</div>'`.
|
||||
|
||||
#### Foldable syntax
|
||||
|
||||
The following table describes which expressions the _collector_ can and cannot fold:
|
||||
|
||||
Syntax | Foldable
|
||||
-----------------------------------|-----------------------------------
|
||||
Literal object | yes
|
||||
Literal array | yes
|
||||
Spread in literal array | no
|
||||
Calls | no
|
||||
New | no
|
||||
Property access | yes, if target is foldable
|
||||
Array index | yes, if target and index are foldable
|
||||
Identifier reference | yes, if it is a reference to a local
|
||||
A template with no substitutions | yes
|
||||
A template with substitutions | yes, if the substitutions are foldable
|
||||
Literal string | yes
|
||||
Literal number | yes
|
||||
Literal boolean | yes
|
||||
Literal null | yes
|
||||
Supported prefix operator | yes, if operand is foldable
|
||||
Supported binary operator | yes, if both left and right are foldable
|
||||
Conditional operator | yes, if condition is foldable
|
||||
Parentheses | yes, if the expression is foldable
|
||||
|
||||
If an expression is not foldable, the collector writes it to `.metadata.json` as an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) for the compiler to resolve.
|
||||
|
||||
|
||||
## Phase 2: code generation
|
||||
|
||||
The _collector_ makes no attempt to understand the metadata that it collects and outputs to `.metadata.json`. It represents the metadata as best it can and records errors when it detects a metadata syntax violation.
|
||||
|
||||
It's the compiler's job to interpret the `.metadata.json` in the code generation phase.
|
||||
|
||||
The compiler understands all syntax forms that the _collector_ supports, but it may reject _syntactically_ correct metadata if the _semantics_ violate compiler rules.
|
||||
|
||||
The compiler can only reference _exported symbols_.
|
||||
|
||||
Decorated component class members must be public. You cannot make an `@Input()` property private or internal.
|
||||
|
||||
Data bound properties must also be public.
|
||||
|
||||
```TypeScript
|
||||
// BAD CODE - title is private
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: '<h1>{{title}}</h1>'
|
||||
})
|
||||
class AppComponent {
|
||||
private title = 'My App'; // Bad
|
||||
}
|
||||
```
|
||||
|
||||
{@a supported-functions}
|
||||
Most importantly, the compiler only generates code to create instances of certain classes, support certain decorators, and call certain functions from the following lists.
|
||||
|
||||
|
||||
### New instances
|
||||
|
||||
The compiler only allows metadata that create instances of these Angular classes.
|
||||
|
||||
Class | Module
|
||||
-----------------|--------------
|
||||
`OpaqueToken` | `@angular/core`
|
||||
`InjectionToken` | `@angular/core`
|
||||
|
||||
|
||||
### Annotations/Decorators
|
||||
|
||||
The compiler only supports metadata for these Angular decorators.
|
||||
|
||||
Decorator | Module
|
||||
------------------|--------------
|
||||
`Attribute` | `@angular/core`
|
||||
`Component` | `@angular/core`
|
||||
`ContentChild` | `@angular/core`
|
||||
`ContentChildren` | `@angular/core`
|
||||
`Directive` | `@angular/core`
|
||||
`Host` | `@angular/core`
|
||||
`HostBinding` | `@angular/core`
|
||||
`HostListener` | `@angular/core`
|
||||
`Inject` | `@angular/core`
|
||||
`Injectable` | `@angular/core`
|
||||
`Input` | `@angular/core`
|
||||
`NgModule` | `@angular/core`
|
||||
`Optional` | `@angular/core`
|
||||
`Output` | `@angular/core`
|
||||
`Pipe` | `@angular/core`
|
||||
`Self` | `@angular/core`
|
||||
`SkipSelf` | `@angular/core`
|
||||
`ViewChild` | `@angular/core`
|
||||
|
||||
|
||||
### Macro-functions and macro-static methods
|
||||
|
||||
The compiler also supports _macros_ in the form of functions or static
|
||||
methods that return an expression.
|
||||
|
||||
For example, consider the following function:
|
||||
|
||||
```TypeScript
|
||||
export function wrapInArray<T>(value: T): T[] {
|
||||
return [value];
|
||||
}
|
||||
```
|
||||
|
||||
You can call the `wrapInArray` in a metadata definition because it returns the value of an expression that conforms to the compiler's restrictive JavaScript subset.
|
||||
|
||||
You might use `wrapInArray()` like this:
|
||||
|
||||
```TypeScript
|
||||
@NgModule({
|
||||
declarations: wrapInArray(TypicalComponent)
|
||||
})
|
||||
class TypicalModule {}
|
||||
```
|
||||
|
||||
The compiler treats this usage as if you had written:
|
||||
|
||||
```TypeScript
|
||||
@NgModule({
|
||||
declarations: [TypicalComponent]
|
||||
})
|
||||
class TypicalModule {}
|
||||
```
|
||||
|
||||
The collector is simplistic in its determination of what qualifies as a macro
|
||||
function; it can only contain a single `return` statement.
|
||||
|
||||
The Angular [`RouterModule`](api/router/RouterModule) exports two macro static methods, `forRoot` and `forChild`, to help declare root and child routes.
|
||||
Review the [source code](https://github.com/angular/angular/blob/master/packages/router/src/router_module.ts#L139 "RouterModule.forRoot source code")
|
||||
for these methods to see how macros can simplify configuration of complex Angular modules.
|
||||
|
||||
## Metadata Errors
|
||||
|
||||
The following are metadata errors you may encounter, with explanations and suggested corrections.
|
||||
|
||||
[Expression form not supported](#expression-form-not-supported)<br>
|
||||
[Reference to a local (non-exported) symbol](#reference-to-a-local-symbol)<br>
|
||||
[Only initialized variables and constants](#only-initialized-variables)<br>
|
||||
[Reference to a non-exported class](#reference-to-a-non-exported-class)<br>
|
||||
[Reference to a non-exported function](#reference-to-a-non-exported-function)<br>
|
||||
[Function calls are not supported](#function-calls-not-supported)<br>
|
||||
[Destructured variable or constant not supported](#destructured-variable-not-supported)<br>
|
||||
[Could not resolve type](#could-not-resolve-type)<br>
|
||||
[Name expected](#name-expected)<br>
|
||||
[Unsupported enum member name](#unsupported-enum-member-name)<br>
|
||||
[Tagged template expressions are not supported](#tagged-template-expressions-not-supported)<br>
|
||||
[Symbol reference expected](#symbol-reference-expected)<br>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3 class="no-toc">Expression form not supported</h3>
|
||||
|
||||
The compiler encountered an expression it didn't understand while evalutating Angular metadata.
|
||||
|
||||
Language features outside of the compiler's [restricted expression syntax](#expression-syntax)
|
||||
can produce this error, as seen in the following example:
|
||||
|
||||
```
|
||||
// ERROR
|
||||
export class Fooish { ... }
|
||||
...
|
||||
const prop = typeof Fooish; // typeof is not valid in metadata
|
||||
...
|
||||
// bracket notation is not valid in metadata
|
||||
{ provide: 'token', useValue: { [prop]: 'value' } };
|
||||
...
|
||||
```
|
||||
|
||||
You can use `typeof` and bracket notation in normal application code.
|
||||
You just can't use those features within expressions that define Angular metadata.
|
||||
|
||||
Avoid this error by sticking to the compiler's [restricted expression syntax](#expression-syntax)
|
||||
when writing Angular metadata
|
||||
and be wary of new or unusual TypeScript features.
|
||||
|
||||
<hr>
|
||||
|
||||
{@a reference-to-a-local-symbol}
|
||||
<h3 class="no-toc">Reference to a local (non-exported) symbol</h3>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
_Reference to a local (non-exported) symbol 'symbol name'. Consider exporting the symbol._
|
||||
|
||||
</div>
|
||||
|
||||
The compiler encountered a referenced to a locally defined symbol that either wasn't exported or wasn't initialized.
|
||||
|
||||
Here's a `provider` example of the problem.
|
||||
|
||||
```
|
||||
// ERROR
|
||||
let foo: number; // neither exported nor initialized
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: ... ,
|
||||
providers: [
|
||||
{ provide: Foo, useValue: foo }
|
||||
]
|
||||
})
|
||||
export class MyComponent {}
|
||||
```
|
||||
The compiler generates the component factory, which includes the `useValue` provider code, in a separate module. _That_ factory module can't reach back to _this_ source module to access the local (non-exported) `foo` variable.
|
||||
|
||||
You could fix the problem by initializing `foo`.
|
||||
|
||||
```
|
||||
let foo = 42; // initialized
|
||||
```
|
||||
|
||||
The compiler will [fold](#folding) the expression into the provider as if you had written this.
|
||||
|
||||
```
|
||||
providers: [
|
||||
{ provide: Foo, useValue: 42 }
|
||||
]
|
||||
```
|
||||
|
||||
Alternatively, you can fix it by exporting `foo` with the expectation that `foo` will be assigned at runtime when you actually know its value.
|
||||
|
||||
```
|
||||
// CORRECTED
|
||||
export let foo: number; // exported
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: ... ,
|
||||
providers: [
|
||||
{ provide: Foo, useValue: foo }
|
||||
]
|
||||
})
|
||||
export class MyComponent {}
|
||||
```
|
||||
|
||||
Adding `export` often works for variables referenced in metadata such as `providers` and `animations` because the compiler can generate _references_ to the exported variables in these expressions. It doesn't need the _values_ of those variables.
|
||||
|
||||
Adding `export` doesn't work when the compiler needs the _actual value_
|
||||
in order to generate code.
|
||||
For example, it doesn't work for the `template` property.
|
||||
|
||||
```
|
||||
// ERROR
|
||||
export let someTemplate: string; // exported but not initialized
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: someTemplate
|
||||
})
|
||||
export class MyComponent {}
|
||||
```
|
||||
|
||||
The compiler needs the value of the `template` property _right now_ to generate the component factory.
|
||||
The variable reference alone is insufficient.
|
||||
Prefixing the declaration with `export` merely produces a new error, "[`Only initialized variables and constants can be referenced`](#only-initialized-variables)".
|
||||
|
||||
<hr>
|
||||
|
||||
{@a only-initialized-variables}
|
||||
<h3 class="no-toc">Only initialized variables and constants</h3>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
_Only initialized variables and constants can be referenced because the value of this variable is needed by the template compiler._
|
||||
|
||||
</div>
|
||||
|
||||
The compiler found a reference to an exported variable or static field that wasn't initialized.
|
||||
It needs the value of that variable to generate code.
|
||||
|
||||
The following example tries to set the component's `template` property to the value of
|
||||
the exported `someTemplate` variable which is declared but _unassigned_.
|
||||
|
||||
```
|
||||
// ERROR
|
||||
export let someTemplate: string;
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: someTemplate
|
||||
})
|
||||
export class MyComponent {}
|
||||
```
|
||||
|
||||
You'd also get this error if you imported `someTemplate` from some other module and neglected to initialize it there.
|
||||
|
||||
```
|
||||
// ERROR - not initialized there either
|
||||
import { someTemplate } from './config';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: someTemplate
|
||||
})
|
||||
export class MyComponent {}
|
||||
```
|
||||
|
||||
The compiler cannot wait until runtime to get the template information.
|
||||
It must statically derive the value of the `someTemplate` variable from the source code
|
||||
so that it can generate the component factory, which includes
|
||||
instructions for building the element based on the template.
|
||||
|
||||
To correct this error, provide the initial value of the variable in an initializer clause _on the same line_.
|
||||
|
||||
```
|
||||
// CORRECTED
|
||||
export let someTemplate = '<h1>Greetings from Angular</h1>';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: someTemplate
|
||||
})
|
||||
export class MyComponent {}
|
||||
```
|
||||
|
||||
<hr>
|
||||
|
||||
<h3 class="no-toc">Reference to a non-exported class</h3>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
_Reference to a non-exported class <class name>. Consider exporting the class._
|
||||
|
||||
</div>
|
||||
|
||||
Metadata referenced a class that wasn't exported.
|
||||
|
||||
For example, you may have defined a class and used it as an injection token in a providers array
|
||||
but neglected to export that class.
|
||||
|
||||
```
|
||||
// ERROR
|
||||
abstract class MyStrategy { }
|
||||
|
||||
...
|
||||
providers: [
|
||||
{ provide: MyStrategy, useValue: ... }
|
||||
]
|
||||
...
|
||||
```
|
||||
|
||||
Angular generates a class factory in a separate module and that
|
||||
factory [can only access exported classes](#exported-symbols).
|
||||
To correct this error, export the referenced class.
|
||||
|
||||
```
|
||||
// CORRECTED
|
||||
export abstract class MyStrategy { }
|
||||
|
||||
...
|
||||
providers: [
|
||||
{ provide: MyStrategy, useValue: ... }
|
||||
]
|
||||
...
|
||||
```
|
||||
<hr>
|
||||
|
||||
<h3 class="no-toc">Reference to a non-exported function</h3>
|
||||
|
||||
Metadata referenced a function that wasn't exported.
|
||||
|
||||
For example, you may have set a providers `useFactory` property to a locally defined function that you neglected to export.
|
||||
|
||||
```
|
||||
// ERROR
|
||||
function myStrategy() { ... }
|
||||
|
||||
...
|
||||
providers: [
|
||||
{ provide: MyStrategy, useFactory: myStrategy }
|
||||
]
|
||||
...
|
||||
```
|
||||
|
||||
Angular generates a class factory in a separate module and that
|
||||
factory [can only access exported functions](#exported-symbols).
|
||||
To correct this error, export the function.
|
||||
|
||||
```
|
||||
// CORRECTED
|
||||
export function myStrategy() { ... }
|
||||
|
||||
...
|
||||
providers: [
|
||||
{ provide: MyStrategy, useFactory: myStrategy }
|
||||
]
|
||||
...
|
||||
```
|
||||
<hr>
|
||||
|
||||
{@a function-calls-not-supported}
|
||||
<h3 class="no-toc">Function calls are not supported</h3>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
_Function calls are not supported. Consider replacing the function or lambda with a reference to an exported function._
|
||||
|
||||
</div>
|
||||
|
||||
The compiler does not currently support [function expressions or lambda functions](#function-expression).
|
||||
For example, you cannot set a provider's `useFactory` to an anonymous function or arrow function like this.
|
||||
|
||||
```
|
||||
// ERROR
|
||||
...
|
||||
providers: [
|
||||
{ provide: MyStrategy, useFactory: function() { ... } },
|
||||
{ provide: OtherStrategy, useFactory: () => { ... } }
|
||||
]
|
||||
...
|
||||
```
|
||||
You also get this error if you call a function or method in a provider's `useValue`.
|
||||
```
|
||||
// ERROR
|
||||
import { calculateValue } from './utilities';
|
||||
|
||||
...
|
||||
providers: [
|
||||
{ provide: SomeValue, useValue: calculateValue() }
|
||||
]
|
||||
...
|
||||
```
|
||||
|
||||
To correct this error, export a function from the module and refer to the function in a `useFactory` provider instead.
|
||||
|
||||
<code-example linenums="false">
|
||||
// CORRECTED
|
||||
import { calculateValue } from './utilities';
|
||||
|
||||
export function myStrategy() { ... }
|
||||
export function otherStrategy() { ... }
|
||||
export function someValueFactory() {
|
||||
return calculateValue();
|
||||
}
|
||||
...
|
||||
providers: [
|
||||
{ provide: MyStrategy, useFactory: myStrategy },
|
||||
{ provide: OtherStrategy, useFactory: otherStrategy },
|
||||
{ provide: SomeValue, useFactory: someValueFactory }
|
||||
]
|
||||
...
|
||||
</code-example>
|
||||
|
||||
<hr>
|
||||
|
||||
{@a destructured-variable-not-supported}
|
||||
<h3 class="no-toc">Destructured variable or constant not supported</h3>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
_Referencing an exported destructured variable or constant is not supported by the template compiler. Consider simplifying this to avoid destructuring._
|
||||
|
||||
</div>
|
||||
|
||||
The compiler does not support references to variables assigned by [destructuring](https://www.typescriptlang.org/docs/handbook/variable-declarations.html#destructuring).
|
||||
|
||||
For example, you cannot write something like this:
|
||||
|
||||
<code-example linenums="false">
|
||||
// ERROR
|
||||
import { configuration } from './configuration';
|
||||
|
||||
// destructured assignment to foo and bar
|
||||
const {foo, bar} = configuration;
|
||||
...
|
||||
providers: [
|
||||
{provide: Foo, useValue: foo},
|
||||
{provide: Bar, useValue: bar},
|
||||
]
|
||||
...
|
||||
</code-example>
|
||||
|
||||
To correct this error, refer to non-destructured values.
|
||||
|
||||
<code-example linenums="false">
|
||||
// CORRECTED
|
||||
import { configuration } from './configuration';
|
||||
...
|
||||
providers: [
|
||||
{provide: Foo, useValue: configuration.foo},
|
||||
{provide: Bar, useValue: configuration.bar},
|
||||
]
|
||||
...
|
||||
</code-example>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3 class="no-toc">Could not resolve type</h3>
|
||||
|
||||
The compiler encountered a type and can't determine which module exports that type.
|
||||
|
||||
This can happen if you refer to an ambient type.
|
||||
For example, the `Window` type is an ambiant type declared in the global `.d.ts` file.
|
||||
|
||||
You'll get an error if you reference it in the component constructor,
|
||||
which the compiler must statically analyze.
|
||||
|
||||
```
|
||||
// ERROR
|
||||
@Component({ })
|
||||
export class MyComponent {
|
||||
constructor (private win: Window) { ... }
|
||||
}
|
||||
```
|
||||
TypeScript understands ambiant types so you don't import them.
|
||||
The Angular compiler does not understand a type that you neglect to export or import.
|
||||
|
||||
In this case, the compiler doesn't understand how to inject something with the `Window` token.
|
||||
|
||||
Do not refer to ambient types in metadata expressions.
|
||||
|
||||
If you must inject an instance of an ambiant type,
|
||||
you can finesse the problem in four steps:
|
||||
|
||||
1. Create an injection token for an instance of the ambiant type.
|
||||
1. Create a factory function that returns that instance.
|
||||
1. Add a `useFactory` provider with that factory function.
|
||||
1. Use `@Inject` to inject the instance.
|
||||
|
||||
Here's an illustrative example.
|
||||
|
||||
<code-example linenums="false">
|
||||
// CORRECTED
|
||||
import { Inject } from '@angular/core';
|
||||
|
||||
export const WINDOW = new InjectionToken('Window');
|
||||
export function _window() { return window; }
|
||||
|
||||
@Component({
|
||||
...
|
||||
providers: [
|
||||
{ provide: WINDOW, useFactory: _window }
|
||||
]
|
||||
})
|
||||
export class MyComponent {
|
||||
constructor (@Inject(WINDOW) private win: Window) { ... }
|
||||
}
|
||||
</code-example>
|
||||
|
||||
The `Window` type in the constructor is no longer a problem for the compiler because it
|
||||
uses the `@Inject(WINDOW)` to generate the injection code.
|
||||
|
||||
Angular does something similar with the `DOCUMENT` token so you can inject the browser's `document` object (or an abstraction of it, depending upon the platform in which the application runs).
|
||||
|
||||
<code-example linenums="false">
|
||||
import { Inject } from '@angular/core';
|
||||
import { DOCUMENT } from '@angular/platform-browser';
|
||||
|
||||
@Component({ ... })
|
||||
export class MyComponent {
|
||||
constructor (@Inject(DOCUMENT) private doc: Document) { ... }
|
||||
}
|
||||
</code-example>
|
||||
<hr>
|
||||
|
||||
<h3 class="no-toc">Name expected</h3>
|
||||
|
||||
The compiler expected a name in an expression it was evaluating.
|
||||
This can happen if you use a number as a property name as in the following example.
|
||||
|
||||
```
|
||||
// ERROR
|
||||
provider: [{ provide: Foo, useValue: { 0: 'test' } }]
|
||||
```
|
||||
|
||||
Change the name of the property to something non-numeric.
|
||||
|
||||
```
|
||||
// CORRECTED
|
||||
provider: [{ provide: Foo, useValue: { '0': 'test' } }]
|
||||
```
|
||||
|
||||
<hr>
|
||||
|
||||
<h3 class="no-toc">Unsupported enum member name</h3>
|
||||
|
||||
Angular couldn't determine the value of the [enum member](https://www.typescriptlang.org/docs/handbook/enums.html)
|
||||
that you referenced in metadata.
|
||||
|
||||
The compiler can understand simple enum values but not complex values such as those derived from computed properties.
|
||||
|
||||
<code-example linenums="false">
|
||||
// ERROR
|
||||
enum Colors {
|
||||
Red = 1,
|
||||
White,
|
||||
Blue = "Blue".length // computed
|
||||
}
|
||||
|
||||
...
|
||||
providers: [
|
||||
{ provide: BaseColor, useValue: Colors.White } // ok
|
||||
{ provide: DangerColor, useValue: Colors.Red } // ok
|
||||
{ provide: StrongColor, useValue: Colors.Blue } // bad
|
||||
]
|
||||
...
|
||||
</code-example>
|
||||
|
||||
Avoid referring to enums with complicated initializers or computed properties.
|
||||
|
||||
<hr>
|
||||
|
||||
{@a tagged-template-expressions-not-supported}
|
||||
<h3 class="no-toc">Tagged template expressions are not supported</h3>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
_Tagged template expressions are not supported in metadata._
|
||||
|
||||
</div>
|
||||
|
||||
The compiler encountered a JavaScript ES2015 [tagged template expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals) such as,
|
||||
```
|
||||
// ERROR
|
||||
const expression = 'funky';
|
||||
const raw = String.raw`A tagged template ${expression} string`;
|
||||
...
|
||||
template: '<div>' + raw + '</div>'
|
||||
...
|
||||
```
|
||||
[`String.raw()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/raw)
|
||||
is a _tag function_ native to JavaScript ES2015.
|
||||
|
||||
The AOT compiler does not support tagged template expressions; avoid them in metadata expressions.
|
||||
|
||||
<hr>
|
||||
|
||||
<h3 class="no-toc">Symbol reference expected</h3>
|
||||
|
||||
The compiler expected a reference to a symbol at the location specified in the error message.
|
||||
|
||||
This error can occur if you use an expression in the `extends` clause of a class.
|
||||
|
||||
<!--
|
||||
|
||||
Chuck: After reviewing your PR comment I'm still at a loss. See [comment there](https://github.com/angular/angular/pull/17712#discussion_r132025495).
|
||||
|
||||
-->
|
||||
|
||||
## Conclusion
|
||||
|
||||
This page covered:
|
||||
|
||||
* What the AOT compiler does.
|
||||
* Why metadata must be written in a subset of JavaScript.
|
||||
* What that subset is.
|
||||
* Other restrictions on metadata definition.
|
||||
* Macro-functions and macro-static methods.
|
||||
* Compiler errors related to metadata.
|
@ -1134,7 +1134,7 @@ The reactive forms approach both emphasizes and facilitates this distinction.
|
||||
The default form displays a nameless hero with no addresses.
|
||||
|
||||
You need a method to populate (or repopulate) the _secretLairs_ with actual hero addresses whenever
|
||||
the parent `HeroListComponent` sets the `HeroListComponent.hero` input property to a new `Hero`.
|
||||
the parent `HeroListComponent` sets the `HeroDetailComponent.hero` input property to a new `Hero`.
|
||||
|
||||
The following `setAddresses` method replaces the _secretLairs_ `FormArray` with a new `FormArray`,
|
||||
initialized by an array of hero address `FormGroups`.
|
||||
|
@ -1,621 +0,0 @@
|
||||
# TypeScript to JavaScript
|
||||
|
||||
## Introduction
|
||||
|
||||
Anything you can do with Angular in _TypeScript_, you can also do
|
||||
in JavaScript. Translating from one language to the other is mostly a
|
||||
matter of changing the way you organize your code and access Angular APIs.
|
||||
|
||||
_TypeScript_ is a popular language option for Angular development.
|
||||
Most code examples on the Internet as well as on this site are written in _TypeScript_.
|
||||
This cookbook contains recipes for translating _TypeScript_
|
||||
code examples to _ES6_ and to _ES5_ so that JavaScript developers
|
||||
can read and write Angular apps in their preferred dialect.
|
||||
|
||||
Run and compare the live <live-example name="ts-to-js/ts">TypeScript</live-example> and <live-example name="ts-to-js/js">JavaScript</live-example>
|
||||
code shown in this cookbook.
|
||||
|
||||
|
||||
## _TypeScript_ to _ES6_ to _ES5_
|
||||
|
||||
_TypeScript_
|
||||
<a href="https://www.typescriptlang.org" title='"TypeScript is a typed, superset of JavaScript"'>is a typed superset of _ES6 JavaScript_</a>.
|
||||
_ES6 JavaScript_ is a superset of _ES5 JavaScript_. _ES5_ is the kind of JavaScript that runs natively in all modern browsers.
|
||||
The transformation of _TypeScript_ code all the way down to _ES5_ code can be seen as "shedding" features.
|
||||
|
||||
The downgrade progression is as follows:
|
||||
|
||||
* _TypeScript_ to _ES6-with-decorators_.
|
||||
* _ES6-with-decorators_ to _ES6-without-decorators_ ("_plain ES6_").
|
||||
* _ES6-without-decorators_ to _ES5_.
|
||||
|
||||
When translating from _TypeScript_ to _ES6-with-decorators_, remove
|
||||
[class property access modifiers](http://www.typescriptlang.org/docs/handbook/classes.html#public-private-and-protected-modifiers)
|
||||
such as `public` and `private`.
|
||||
Remove most of the
|
||||
[type declarations](https://www.typescriptlang.org/docs/handbook/basic-types.html),
|
||||
such as `:string` and `:boolean`
|
||||
but **keep the constructor parameter types, which are used for dependency injection**.
|
||||
|
||||
From _ES6-with-decorators_ to _plain ES6_, remove all
|
||||
[decorators](https://www.typescriptlang.org/docs/handbook/decorators.html)
|
||||
and the remaining types.
|
||||
You must declare properties in the class constructor (`this.title = '...'`) rather than in the body of the class.
|
||||
|
||||
Finally, from _plain ES6_ to _ES5_, the main missing features are `import`
|
||||
statements and `class` declarations.
|
||||
|
||||
For _plain ES6_ transpilation you can _start_ with a setup similar to the
|
||||
[_TypeScript_ quickstart](https://github.com/angular/quickstart) and adjust the application code accordingly.
|
||||
Transpile with [Babel](https://babeljs.io/) using the `es2015` preset.
|
||||
To use decorators and annotations with Babel, install the
|
||||
[`angular2`](https://github.com/shuhei/babel-plugin-angular2-annotations) preset as well.
|
||||
|
||||
{@a modularity}
|
||||
|
||||
## Importing and Exporting
|
||||
|
||||
### Importing Angular Code
|
||||
|
||||
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](guide/glossary#scoped-package)
|
||||
through the global `ng` object.
|
||||
Anything you can import from `@angular` is a nested member of this `ng` object:
|
||||
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/app.module.ts" region="ng2import">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/app.module.es6" region="ng2import">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/app.module.es6" region="ng2import">
|
||||
</code-pane>
|
||||
<code-pane title="ES5 JavaScript" path="ts-to-js/js/src/app/app.module.js" region="ng2import">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
### 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 `index.html`.
|
||||
|
||||
<div class="alert is-important">
|
||||
|
||||
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.
|
||||
|
||||
</div>
|
||||
|
||||
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, for example, `app.HeroComponent`.
|
||||
You could factor a large application into several sub-namespaces
|
||||
which leads to "exports" along the lines of `app.heroQueries.HeroComponent`.
|
||||
|
||||
Every _ES5_ file should wrap 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.
|
||||
|
||||
Here is a `HeroComponent` as it might be defined and "exported" in each of the four language variants.
|
||||
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero.component.ts" region="appexport">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero.component.es6" region="appexport">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero.component.es6" region="appexport">
|
||||
</code-pane>
|
||||
<code-pane title="ES5 JavaScript" path="ts-to-js/js/src/app/hero.component.js" region="appexport">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
### 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 from other files.
|
||||
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/app.module.ts" region="appimport">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/app.module.es6" region="appimport">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/app.module.es6" region="appimport">
|
||||
</code-pane>
|
||||
<code-pane title="ES5 JavaScript" path="ts-to-js/js/src/app/app.module.js" region="appimport">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Alternatively, you can use a module loader such as Webpack or
|
||||
Browserify in an Angular JavaScript project. In such a project, you would
|
||||
use _CommonJS_ modules and the `require` function to load Angular framework code.
|
||||
Then use `module.exports` and `require` to export and import application code.
|
||||
|
||||
</div>
|
||||
|
||||
{@a class-metadata}
|
||||
|
||||
## Classes and Class Metadata
|
||||
|
||||
### Classes
|
||||
|
||||
Most Angular _TypeScript_ and _ES6_ code is written as classes.
|
||||
|
||||
Properties and method parameters of _TypeScript_ classes may be marked with the access modifiers
|
||||
`private`, `internal`, and `public`.
|
||||
Remove these modifiers when translating to JavaScript.
|
||||
|
||||
Most type declarations, for example, `:string` and `:boolean`, should be removed when translating to JavaScript.
|
||||
When translating to _ES6-with-decorators_, ***do not remove types from constructor parameters!***
|
||||
|
||||
Look for types in _TypeScript_ property declarations.
|
||||
In general it is better to initialize such properties with default values because
|
||||
many browser JavaScript engines can generate more performant code.
|
||||
When _TypeScript_ code follows this same advice, it can infer the property types
|
||||
and there is nothing to remove during translation.
|
||||
|
||||
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.
|
||||
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero.component.ts" region="class">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero.component.es6" region="class">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero.component.es6" region="class">
|
||||
</code-pane>
|
||||
<code-pane title="ES5 JavaScript" path="ts-to-js/js/src/app/hero.component.js" region="constructorproto">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
### 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/Component) 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 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 to a class.
|
||||
|
||||
See these variations side-by-side:
|
||||
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero.component.ts" region="metadata">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero.component.es6" region="metadata">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero.component.es6" region="metadata">
|
||||
</code-pane>
|
||||
<code-pane title="ES5 JavaScript" path="ts-to-js/js/src/app/hero.component.js" region="metadata">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
## External template file
|
||||
|
||||
A large component template is often kept in a separate template file.
|
||||
|
||||
<code-example path="ts-to-js/ts/src/app/hero-title.component.html" title="src/app/hero-title.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
The component, `HeroTitleComponent` in this case, then references the template file in its metadata `templateUrl` property:
|
||||
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-title.component.ts" region="templateUrl">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-title.component.es6" region="templateUrl">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-title.component.es6" region="templateUrl">
|
||||
</code-pane>
|
||||
<code-pane title="ES5 JavaScript" path="ts-to-js/js/src/app/hero-title.component.js" region="templateUrl">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
Note that both the _TypeScript_ and _ES6_ `templateUrl` properties identify the location of the template file _relative to the component module_.
|
||||
|
||||
{@a dsl}
|
||||
|
||||
## _ES5_ DSL
|
||||
|
||||
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 locates the metadata above the constructor,
|
||||
as you would if you wrote in _TypeScript_ or _ES6-with-decorators_.
|
||||
|
||||
This _API_ (_Application Programming Interface_) is commonly known as the _ES5 DSL_ (_Domain Specific Language_).
|
||||
|
||||
Set an application namespace property, for example, `app.HeroDslComponent`, to the result of an `ng.core.Component` function call.
|
||||
Pass the same metadata object to `ng.core.Component` as you did before.
|
||||
Then chain a call to the `Class()` method which takes an object defining the class constructor and instance methods.
|
||||
|
||||
Here is an example of the `HeroComponent`, re-written with the DSL,
|
||||
next to the original _ES5_ version for comparison:
|
||||
|
||||
<code-tabs>
|
||||
<code-pane title="ES5 JavaScript with DSL" path="ts-to-js/js/src/app/hero.component.js" region="dsl">
|
||||
</code-pane>
|
||||
<code-pane title="ES5 JavaScript" path="ts-to-js/js/src/app/hero.component.js">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
{@a name-constructor}
|
||||
|
||||
<div class="callout is-helpful">
|
||||
|
||||
A **named** constructor displays clearly in the console log
|
||||
if the component throws a runtime error.
|
||||
An **unnamed** constructor displays as an anonymous function, for example, `class0`,
|
||||
which is impossible to find in the source code.
|
||||
|
||||
</div>
|
||||
|
||||
{@a getters-setters}
|
||||
|
||||
### Properties with getters and setters
|
||||
|
||||
_TypeScript_ and _ES6_ support with getters and setters.
|
||||
Here's an example of a read-only _TypeScript_ property with a getter
|
||||
that prepares a toggle-button label for the next clicked state:
|
||||
|
||||
<code-example path="ts-to-js/ts/src/app/hero-queries.component.ts" region="defined-property" title="ts/src/app/hero-queries.component.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
This _TypeScript_ "getter" property is transpiled to an _ES5_
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty"
|
||||
title="Defined Properties">defined property</a>.
|
||||
The _ES5 DSL_ does not support _defined properties_ directly
|
||||
but you can still create them by extracting the "class" prototype and
|
||||
adding the _defined property_ in raw JavaScript like this:
|
||||
|
||||
<code-example path="ts-to-js/js/src/app/hero-queries.component.js" region="defined-property" title="js/src/app/hero-queries.component.ts" linenums="false">
|
||||
</code-example>
|
||||
|
||||
{@a dsl-other}
|
||||
|
||||
### DSL for other classes
|
||||
There are similar DSLs for other decorated classes.
|
||||
You can define a directive with `ng.core.Directive`:
|
||||
|
||||
|
||||
<code-example>
|
||||
app.MyDirective = ng.core.Directive({
|
||||
selector: '[myDirective]'
|
||||
}).Class({
|
||||
...
|
||||
});
|
||||
</code-example>
|
||||
|
||||
and a pipe with `ng.core.Pipe`:
|
||||
|
||||
<code-example>
|
||||
app.MyPipe = ng.core.Pipe({
|
||||
name: 'myPipe'
|
||||
}).Class({
|
||||
...
|
||||
});
|
||||
</code-example>
|
||||
|
||||
{@a interfaces}
|
||||
|
||||
## Interfaces
|
||||
|
||||
A _TypeScript_ interface helps ensure that a class implements the interface's members correctly.
|
||||
Always try to use Angular interfaces where appropriate.
|
||||
For example, the component class that implements the `ngOnInit` lifecycle hook method
|
||||
should implement the `OnInit` interface.
|
||||
|
||||
_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.
|
||||
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-lifecycle.component.ts">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-lifecycle.component.es6">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-lifecycle.component.es6">
|
||||
</code-pane>
|
||||
<code-pane title="ES5 JavaScript" path="ts-to-js/js/src/app/hero-lifecycle.component.js">
|
||||
</code-pane>
|
||||
<code-pane title="ES5 JavaScript with DSL" path="ts-to-js/js/src/app/hero-lifecycle.component.js" region="dsl">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
{@a io-decorators}
|
||||
|
||||
## Input and Output Metadata
|
||||
|
||||
### Input and Output Decorators
|
||||
|
||||
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#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_ 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_.
|
||||
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/confirm.component.ts">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/confirm.component.es6">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/confirm.component.es6">
|
||||
</code-pane>
|
||||
<code-pane title="ES5 JavaScript" path="ts-to-js/js/src/app/confirm.component.js">
|
||||
</code-pane>
|
||||
<code-pane title="ES5 JavaScript with DSL" path="ts-to-js/js/src/app/confirm.component.js" region="dsl">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
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.
|
||||
|
||||
{@a dependency-injection}
|
||||
|
||||
## Dependency injection
|
||||
Angular relies heavily on [Dependency Injection](guide/dependency-injection) to provide services to the objects it creates.
|
||||
When Angular creates a new component, directive, pipe or another service,
|
||||
it sets the class constructor parameters to instances of services provided by an _Injector_.
|
||||
|
||||
The developer must tell Angular what to inject into each parameter.
|
||||
|
||||
{@a injection-class-type}
|
||||
|
||||
### Injection by class type
|
||||
|
||||
The easiest and most popular technique in _TypeScript_ and _ES6-with-decorators_ is to set the constructor parameter type
|
||||
to the class associated with the service to inject.
|
||||
|
||||
The _TypeScript_ transpiler writes parameter type information into the generated JavaScript.
|
||||
Angular reads that information at runtime and locates the corresponding service in the appropriate _Injector_.
|
||||
The _ES6-with-decorators_ transpiler does essentially the same thing using the same parameter-typing syntax.
|
||||
|
||||
_ES5_ and _plain ES6_ lack types so you must identify "injectables" by attaching a **`parameters`** array to the constructor function.
|
||||
Each item in the array specifies the service's injection token.
|
||||
|
||||
As with _TypeScript_, the most popular token is a class,
|
||||
or rather a _constructor function_ that represents a class in _ES5_ and _plain ES6_.
|
||||
The format of the `parameters` array varies:
|
||||
|
||||
* _Plain ES6_—nest each constructor function in a sub-array.
|
||||
* _ES5_—simply list the constructor functions.
|
||||
|
||||
When writing with _ES5 DSL_, set the `Class.constructor` property to
|
||||
an array whose first parameters are the injectable constructor functions and whose
|
||||
last parameter is the class constructor itself.
|
||||
This format should be familiar to AngularJS developers.
|
||||
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-di.component.ts">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-di.component.es6">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-di.component.es6">
|
||||
</code-pane>
|
||||
<code-pane title="ES5 JavaScript" path="ts-to-js/js/src/app/hero-di.component.js">
|
||||
</code-pane>
|
||||
<code-pane title="ES5 JavaScript with DSL" path="ts-to-js/js/src/app/hero-di.component.js" region="dsl">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
### Injection with the @Inject decorator
|
||||
|
||||
Sometimes the dependency injection token isn't a class or constructor function.
|
||||
|
||||
In _TypeScript_ and _ES6-with-decorators_, you precede the class constructor parameter
|
||||
by calling the `@Inject()` decorator with the injection token.
|
||||
In the following example, the token is the string `'heroName'`.
|
||||
|
||||
The other JavaScript dialects add a `parameters` array to the class constructor function.
|
||||
Each item contains a new instance of `Inject`:
|
||||
|
||||
* _Plain ES6_—each item is a new instance of `Inject(token)` in a sub-array.
|
||||
* _ES5_—simply list the string tokens.
|
||||
|
||||
When writing with _ES5 DSL_, set the `Class.constructor` property to a function definition
|
||||
array as before. Create a new instance of `ng.core.Inject(token)` for each parameter.
|
||||
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-di-inject.component.ts">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-di-inject.component.es6">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-di-inject.component.es6">
|
||||
</code-pane>
|
||||
<code-pane title="ES5 JavaScript" path="ts-to-js/js/src/app/hero-di-inject.component.js">
|
||||
</code-pane>
|
||||
<code-pane title="ES5 JavaScript with DSL" path="ts-to-js/js/src/app/hero-di-inject.component.js" region="dsl">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
### Additional Injection Decorators
|
||||
|
||||
You can qualify injection behavior with injection decorators from `@angular/core`.
|
||||
|
||||
In _TypeScript_ and _ES6-with-decorators_,
|
||||
you precede the constructor parameters with injection qualifiers such as:
|
||||
|
||||
* [`@Optional`](api/core/Optional) sets the parameter to `null` if the service is missing.
|
||||
* [`@Attribute`](api/core/Attribute) to inject a host element attribute value.
|
||||
* [`@ContentChild`](api/core/ContentChild) to inject a content child.
|
||||
* [`@ViewChild`](api/core/ViewChild) to inject a view child.
|
||||
* [`@Host`](api/core/Host) to inject a service in this component or its host.
|
||||
* [`@SkipSelf`](api/core/SkipSelf) to inject a service provided in an ancestor of this component.
|
||||
|
||||
In _plain ES6_ and _ES5_, create an instance of the equivalent injection qualifier in a nested array within the `parameters` array.
|
||||
For example, you'd write `new Optional()` in _plain ES6_ and `new ng.core.Optional()` in _ES5_.
|
||||
|
||||
When writing with _ES5 DSL_, set the `Class.constructor` property to a function definition
|
||||
array as before. Use a nested array to define a parameter's complete injection specification.
|
||||
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-title.component.ts">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-title.component.es6">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-title.component.es6">
|
||||
</code-pane>
|
||||
<code-pane title="ES5 JavaScript" path="ts-to-js/js/src/app/hero-title.component.js">
|
||||
</code-pane>
|
||||
<code-pane title="ES5 JavaScript with DSL" path="ts-to-js/js/src/app/hero-title.component.js" region="dsl">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
In the example above, there is no provider for the `'titlePrefix'` token.
|
||||
Without `@Optional()`, Angular would raise an error.
|
||||
With `@Optional()`, Angular sets the constructor parameter to `null`
|
||||
and the component displays the title without a prefix.
|
||||
|
||||
</div>
|
||||
|
||||
{@a host-binding}
|
||||
|
||||
## Host Binding
|
||||
|
||||
Angular supports bindings to properties and events of the _host element_, which is the
|
||||
element whose tag matches the component selector.
|
||||
|
||||
### Host Decorators
|
||||
|
||||
In _TypeScript_ and _ES6-with-decorators_, you can use host property decorators to bind a host
|
||||
element to a component or directive.
|
||||
The [`@HostBinding`](api/core/HostBinding) decorator
|
||||
binds host element properties to component data properties.
|
||||
The [`@HostListener`](api/core/HostListener) decorator binds
|
||||
host element events to component event handlers.
|
||||
|
||||
In _plain ES6_ or _ES5_, add a `host` attribute to the component metadata to achieve the
|
||||
same effect as `@HostBinding` and `@HostListener`.
|
||||
|
||||
The `host` value is an object whose properties are host property and listener bindings:
|
||||
|
||||
* Each key follows regular Angular binding syntax: `[property]` for host bindings
|
||||
or `(event)` for host listeners.
|
||||
* Each value identifies the corresponding component property or method.
|
||||
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-host.component.ts">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-host.component.es6">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-host.component.es6">
|
||||
</code-pane>
|
||||
<code-pane title="ES5 JavaScript" path="ts-to-js/js/src/app/hero-host.component.js">
|
||||
</code-pane>
|
||||
<code-pane title="ES5 JavaScript with DSL" path="ts-to-js/js/src/app/hero-host.component.js" region="dsl">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
### Host Metadata
|
||||
|
||||
Some developers prefer to specify host properties and listeners
|
||||
in the component metadata.
|
||||
They'd _rather_ do it the way you _must_ do it _ES5_ and _plain ES6_.
|
||||
|
||||
The following re-implementation of the `HeroComponent` shows that _any property metadata decorator_
|
||||
can be expressed as component or directive metadata in both _TypeScript_ and _ES6-with-decorators_.
|
||||
These particular _TypeScript_ and _ES6_ code snippets happen to be identical.
|
||||
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-host-meta.component.ts">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-host-meta.component.es6">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
{@a view-child-decorators}
|
||||
|
||||
### View and Child Decorators
|
||||
|
||||
Several _property_ decorators query a component's nested view and content components.
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
_View_ children are associated with element tags that appear _within_ the component's template.
|
||||
|
||||
_Content_ children are associated with elements that appear _between_ the component's element tags;
|
||||
they are projected into an `<ng-content>` slot in the component's template.
|
||||
|
||||
</div>
|
||||
|
||||
The [`@ViewChild`](api/core/ViewChild) and
|
||||
[`@ViewChildren`](api/core/ViewChildren) property decorators
|
||||
allow a component to query instances of other components that are used in
|
||||
its view.
|
||||
|
||||
In _ES5_ and _ES6_, you access a component's view children by adding a `queries` property to the component metadata.
|
||||
The `queries` property value is a hash map.
|
||||
|
||||
* Each _key_ is the name of a component property that will hold the view child or children.
|
||||
* Each _value_ is a new instance of either `ViewChild` or `ViewChildren`.
|
||||
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-queries.component.ts" region="view">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-queries.component.es6" region="view">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-queries.component.es6" region="view">
|
||||
</code-pane>
|
||||
<code-pane title="ES5 JavaScript with DSL" path="ts-to-js/js/src/app/hero-queries.component.js" region="view">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
The [`@ContentChild`](api/core/ContentChild) and
|
||||
[`@ContentChildren`](api/core/ContentChildren) property decorators
|
||||
allow a component to query instances of other components that have been projected
|
||||
into its view from elsewhere.
|
||||
|
||||
They can be added in the same way as [`@ViewChild`](api/core/ViewChild) and
|
||||
[`@ViewChildren`](api/core/ViewChildren).
|
||||
|
||||
<code-tabs>
|
||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-queries.component.ts" region="content">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-queries.component.es6" region="content">
|
||||
</code-pane>
|
||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-queries.component.es6" region="content">
|
||||
</code-pane>
|
||||
<code-pane title="ES5 JavaScript with DSL" path="ts-to-js/js/src/app/hero-queries.component.js" region="content">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
In _TypeScript_ and _ES6-with-decorators_ you can also use the `queries` metadata
|
||||
instead of the `@ViewChild` and `@ContentChild` property decorators.
|
||||
|
||||
</div>
|
||||
|
||||
{@a aot}
|
||||
|
||||
## AOT Compilation in _TypeScript_ only
|
||||
|
||||
Angular offers two modes of template compilation, JIT (_just-in-time_) and
|
||||
[AOT (_ahead-of-time_)](guide/aot-compiler).
|
||||
Currently the AOT compiler only works with _TypeScript_ applications because, in part, it generates
|
||||
_TypeScript_ files as an intermediate result.
|
||||
**AOT is not an option for pure JavaScript applications** at this time.
|
@ -293,11 +293,6 @@
|
||||
"title": "Security",
|
||||
"tooltip": "Developing for content security in Angular applications."
|
||||
},
|
||||
{
|
||||
"url": "guide/ts-to-js",
|
||||
"title": "TypeScript to JavaScript",
|
||||
"tooltip": "Convert Angular TypeScript examples into ES6 and ES5 JavaScript."
|
||||
},
|
||||
{
|
||||
"title": "Setup & Deployment",
|
||||
"tooltip": "Setup and Deployment",
|
||||
@ -333,6 +328,11 @@
|
||||
"title": "Ahead-of-Time Compilation",
|
||||
"tooltip": "Learn why and how to use the Ahead-of-Time (AOT) compiler."
|
||||
},
|
||||
{
|
||||
"url": "guide/metadata",
|
||||
"title": "Metadata and AOT",
|
||||
"tooltip": "How Angular handles metadata with AOT."
|
||||
},
|
||||
{
|
||||
"url": "guide/deployment",
|
||||
"title": "Deployment",
|
||||
|
@ -9,7 +9,7 @@
|
||||
"ng": "yarn check-env && ng",
|
||||
"start": "yarn check-env && ng serve",
|
||||
"prebuild": "yarn check-env && yarn setup",
|
||||
"build": "ng build --target=production --environment=stable -sm -bo",
|
||||
"build": "ng build --target=production --environment=stable -sm --build-optimizer",
|
||||
"postbuild": "yarn sw-manifest && yarn sw-copy",
|
||||
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint",
|
||||
"test": "yarn check-env && ng test",
|
||||
@ -49,18 +49,18 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^4.3.1",
|
||||
"@angular/animations": "^5.0.0-beta.3",
|
||||
"@angular/cdk": "^2.0.0-beta.8",
|
||||
"@angular/common": "^4.3.1",
|
||||
"@angular/compiler": "^4.3.1",
|
||||
"@angular/core": "^4.3.1",
|
||||
"@angular/forms": "^4.3.1",
|
||||
"@angular/http": "^4.3.1",
|
||||
"@angular/common": "^5.0.0-beta.3",
|
||||
"@angular/compiler": "^5.0.0-beta.3",
|
||||
"@angular/core": "^5.0.0-beta.3",
|
||||
"@angular/forms": "^5.0.0-beta.3",
|
||||
"@angular/http": "^5.0.0-beta.3",
|
||||
"@angular/material": "^2.0.0-beta.8",
|
||||
"@angular/platform-browser": "^4.3.1",
|
||||
"@angular/platform-browser-dynamic": "^4.3.1",
|
||||
"@angular/platform-server": "^4.3.1",
|
||||
"@angular/router": "^4.3.1",
|
||||
"@angular/platform-browser": "^5.0.0-beta.3",
|
||||
"@angular/platform-browser-dynamic": "^5.0.0-beta.3",
|
||||
"@angular/platform-server": "^5.0.0-beta.3",
|
||||
"@angular/router": "^5.0.0-beta.3",
|
||||
"@angular/service-worker": "^1.0.0-beta.16",
|
||||
"classlist.js": "^1.1.20150312",
|
||||
"core-js": "^2.4.1",
|
||||
@ -69,11 +69,11 @@
|
||||
"rxjs": "^5.2.0",
|
||||
"tslib": "^1.7.1",
|
||||
"web-animations-js": "^2.2.5",
|
||||
"zone.js": "^0.8.12"
|
||||
"zone.js": "^0.8.16"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/cli": "1.3.0-rc.3",
|
||||
"@angular/compiler-cli": "^4.3.1",
|
||||
"@angular/cli": "^1.3.0",
|
||||
"@angular/compiler-cli": "^5.0.0-beta.3",
|
||||
"@types/jasmine": "^2.5.52",
|
||||
"@types/node": "~6.0.60",
|
||||
"archiver": "^1.3.0",
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user