docs(aio): final migration from old site
This commit is contained in:
parent
8f1359d25f
commit
061475402c
|
@ -56,6 +56,7 @@ dist/
|
|||
|
||||
# TS to JS
|
||||
!cb-ts-to-js/js*/**/*.js
|
||||
cb-ts-to-js/js*/**/system*.js
|
||||
|
||||
# webpack
|
||||
!webpack/**/config/*.js
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
var templateUrlRegex = /templateUrl\s*:(\s*['"`](.*?)['"`]\s*)/gm;
|
||||
var stylesRegex = /styleUrls *:(\s*\[[^\]]*?\])/g;
|
||||
var stringRegex = /(['`"])((?:[^\\]\\\1|.)*?)\1/g;
|
||||
|
||||
module.exports.translate = function(load){
|
||||
var url = document.createElement('a');
|
||||
url.href = load.address;
|
||||
|
||||
var basePathParts = url.pathname.split('/');
|
||||
|
||||
basePathParts.pop();
|
||||
var basePath = basePathParts.join('/');
|
||||
|
||||
var baseHref = document.createElement('a');
|
||||
baseHref.href = this.baseURL;
|
||||
baseHref = baseHref.pathname;
|
||||
|
||||
basePath = basePath.replace(baseHref, '');
|
||||
|
||||
load.source = load.source
|
||||
.replace(templateUrlRegex, function(match, quote, url){
|
||||
let resolvedUrl = url;
|
||||
|
||||
if (url.startsWith('.')) {
|
||||
resolvedUrl = basePath + url.substr(1);
|
||||
}
|
||||
|
||||
return 'templateUrl: "' + resolvedUrl + '"';
|
||||
})
|
||||
.replace(stylesRegex, function(match, relativeUrls) {
|
||||
var urls = [];
|
||||
|
||||
while ((match = stringRegex.exec(relativeUrls)) !== null) {
|
||||
if (match[2].startsWith('.')) {
|
||||
urls.push('"' + basePath + match[2].substr(1) + '"');
|
||||
} else {
|
||||
urls.push('"' + match[2] + '"');
|
||||
}
|
||||
}
|
||||
|
||||
return "styleUrls: [" + urls.join(', ') + "]";
|
||||
});
|
||||
|
||||
return load;
|
||||
};
|
|
@ -1,52 +0,0 @@
|
|||
/**
|
||||
* System configuration for Angular samples
|
||||
* Adjust as necessary for your application needs.
|
||||
*/
|
||||
(function (global) {
|
||||
System.config({
|
||||
paths: {
|
||||
// paths serve as alias
|
||||
'npm:': 'node_modules/'
|
||||
},
|
||||
// map tells the System loader where to look for things
|
||||
map: {
|
||||
// our app is within the app folder
|
||||
'app': 'app',
|
||||
|
||||
// angular bundles
|
||||
'@angular/animations': 'npm:@angular/animations/bundles/animations.umd.js',
|
||||
'@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js',
|
||||
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
|
||||
'@angular/common': 'npm:@angular/common/bundles/common.umd.js',
|
||||
'@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
|
||||
'@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
|
||||
'@angular/platform-browser/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.js',
|
||||
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
|
||||
'@angular/http': 'npm:@angular/http/bundles/http.umd.js',
|
||||
'@angular/router': 'npm:@angular/router/bundles/router.umd.js',
|
||||
'@angular/router/upgrade': 'npm:@angular/router/bundles/router-upgrade.umd.js',
|
||||
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
|
||||
'@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
|
||||
'@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.js',
|
||||
|
||||
// other libraries
|
||||
'rxjs': 'npm:rxjs',
|
||||
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js'
|
||||
},
|
||||
// packages tells the System loader how to load when no filename and/or no extension
|
||||
packages: {
|
||||
app: {
|
||||
main: './main.js',
|
||||
defaultExtension: 'js',
|
||||
meta: {
|
||||
'./*.js': {
|
||||
loader: 'systemjs-angular-loader.js'
|
||||
}
|
||||
}
|
||||
},
|
||||
rxjs: {
|
||||
defaultExtension: 'js'
|
||||
}
|
||||
}
|
||||
});
|
||||
})(this);
|
|
@ -1,45 +0,0 @@
|
|||
var templateUrlRegex = /templateUrl\s*:(\s*['"`](.*?)['"`]\s*)/gm;
|
||||
var stylesRegex = /styleUrls *:(\s*\[[^\]]*?\])/g;
|
||||
var stringRegex = /(['`"])((?:[^\\]\\\1|.)*?)\1/g;
|
||||
|
||||
module.exports.translate = function(load){
|
||||
var url = document.createElement('a');
|
||||
url.href = load.address;
|
||||
|
||||
var basePathParts = url.pathname.split('/');
|
||||
|
||||
basePathParts.pop();
|
||||
var basePath = basePathParts.join('/');
|
||||
|
||||
var baseHref = document.createElement('a');
|
||||
baseHref.href = this.baseURL;
|
||||
baseHref = baseHref.pathname;
|
||||
|
||||
basePath = basePath.replace(baseHref, '');
|
||||
|
||||
load.source = load.source
|
||||
.replace(templateUrlRegex, function(match, quote, url){
|
||||
let resolvedUrl = url;
|
||||
|
||||
if (url.startsWith('.')) {
|
||||
resolvedUrl = basePath + url.substr(1);
|
||||
}
|
||||
|
||||
return 'templateUrl: "' + resolvedUrl + '"';
|
||||
})
|
||||
.replace(stylesRegex, function(match, relativeUrls) {
|
||||
var urls = [];
|
||||
|
||||
while ((match = stringRegex.exec(relativeUrls)) !== null) {
|
||||
if (match[2].startsWith('.')) {
|
||||
urls.push('"' + basePath + match[2].substr(1) + '"');
|
||||
} else {
|
||||
urls.push('"' + match[2] + '"');
|
||||
}
|
||||
}
|
||||
|
||||
return "styleUrls: [" + urls.join(', ') + "]";
|
||||
});
|
||||
|
||||
return load;
|
||||
};
|
|
@ -1,52 +0,0 @@
|
|||
/**
|
||||
* System configuration for Angular samples
|
||||
* Adjust as necessary for your application needs.
|
||||
*/
|
||||
(function (global) {
|
||||
System.config({
|
||||
paths: {
|
||||
// paths serve as alias
|
||||
'npm:': 'node_modules/'
|
||||
},
|
||||
// map tells the System loader where to look for things
|
||||
map: {
|
||||
// our app is within the app folder
|
||||
'app': 'app',
|
||||
|
||||
// angular bundles
|
||||
'@angular/animations': 'npm:@angular/animations/bundles/animations.umd.js',
|
||||
'@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js',
|
||||
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
|
||||
'@angular/common': 'npm:@angular/common/bundles/common.umd.js',
|
||||
'@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
|
||||
'@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
|
||||
'@angular/platform-browser/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.js',
|
||||
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
|
||||
'@angular/http': 'npm:@angular/http/bundles/http.umd.js',
|
||||
'@angular/router': 'npm:@angular/router/bundles/router.umd.js',
|
||||
'@angular/router/upgrade': 'npm:@angular/router/bundles/router-upgrade.umd.js',
|
||||
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
|
||||
'@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
|
||||
'@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.js',
|
||||
|
||||
// other libraries
|
||||
'rxjs': 'npm:rxjs',
|
||||
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js'
|
||||
},
|
||||
// packages tells the System loader how to load when no filename and/or no extension
|
||||
packages: {
|
||||
app: {
|
||||
main: './main.js',
|
||||
defaultExtension: 'js',
|
||||
meta: {
|
||||
'./*.js': {
|
||||
loader: 'systemjs-angular-loader.js'
|
||||
}
|
||||
}
|
||||
},
|
||||
rxjs: {
|
||||
defaultExtension: 'js'
|
||||
}
|
||||
}
|
||||
});
|
||||
})(this);
|
|
@ -1,45 +0,0 @@
|
|||
var templateUrlRegex = /templateUrl\s*:(\s*['"`](.*?)['"`]\s*)/gm;
|
||||
var stylesRegex = /styleUrls *:(\s*\[[^\]]*?\])/g;
|
||||
var stringRegex = /(['`"])((?:[^\\]\\\1|.)*?)\1/g;
|
||||
|
||||
module.exports.translate = function(load){
|
||||
var url = document.createElement('a');
|
||||
url.href = load.address;
|
||||
|
||||
var basePathParts = url.pathname.split('/');
|
||||
|
||||
basePathParts.pop();
|
||||
var basePath = basePathParts.join('/');
|
||||
|
||||
var baseHref = document.createElement('a');
|
||||
baseHref.href = this.baseURL;
|
||||
baseHref = baseHref.pathname;
|
||||
|
||||
basePath = basePath.replace(baseHref, '');
|
||||
|
||||
load.source = load.source
|
||||
.replace(templateUrlRegex, function(match, quote, url){
|
||||
let resolvedUrl = url;
|
||||
|
||||
if (url.startsWith('.')) {
|
||||
resolvedUrl = basePath + url.substr(1);
|
||||
}
|
||||
|
||||
return 'templateUrl: "' + resolvedUrl + '"';
|
||||
})
|
||||
.replace(stylesRegex, function(match, relativeUrls) {
|
||||
var urls = [];
|
||||
|
||||
while ((match = stringRegex.exec(relativeUrls)) !== null) {
|
||||
if (match[2].startsWith('.')) {
|
||||
urls.push('"' + basePath + match[2].substr(1) + '"');
|
||||
} else {
|
||||
urls.push('"' + match[2] + '"');
|
||||
}
|
||||
}
|
||||
|
||||
return "styleUrls: [" + urls.join(', ') + "]";
|
||||
});
|
||||
|
||||
return load;
|
||||
};
|
|
@ -1,52 +0,0 @@
|
|||
/**
|
||||
* System configuration for Angular samples
|
||||
* Adjust as necessary for your application needs.
|
||||
*/
|
||||
(function (global) {
|
||||
System.config({
|
||||
paths: {
|
||||
// paths serve as alias
|
||||
'npm:': 'node_modules/'
|
||||
},
|
||||
// map tells the System loader where to look for things
|
||||
map: {
|
||||
// our app is within the app folder
|
||||
'app': 'app',
|
||||
|
||||
// angular bundles
|
||||
'@angular/animations': 'npm:@angular/animations/bundles/animations.umd.js',
|
||||
'@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js',
|
||||
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
|
||||
'@angular/common': 'npm:@angular/common/bundles/common.umd.js',
|
||||
'@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
|
||||
'@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
|
||||
'@angular/platform-browser/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.js',
|
||||
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
|
||||
'@angular/http': 'npm:@angular/http/bundles/http.umd.js',
|
||||
'@angular/router': 'npm:@angular/router/bundles/router.umd.js',
|
||||
'@angular/router/upgrade': 'npm:@angular/router/bundles/router-upgrade.umd.js',
|
||||
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
|
||||
'@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
|
||||
'@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.js',
|
||||
|
||||
// other libraries
|
||||
'rxjs': 'npm:rxjs',
|
||||
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js'
|
||||
},
|
||||
// packages tells the System loader how to load when no filename and/or no extension
|
||||
packages: {
|
||||
app: {
|
||||
main: './main.js',
|
||||
defaultExtension: 'js',
|
||||
meta: {
|
||||
'./*.js': {
|
||||
loader: 'systemjs-angular-loader.js'
|
||||
}
|
||||
}
|
||||
},
|
||||
rxjs: {
|
||||
defaultExtension: 'js'
|
||||
}
|
||||
}
|
||||
});
|
||||
})(this);
|
|
@ -0,0 +1,116 @@
|
|||
/* #docregion , quickstart, toh */
|
||||
/* Master Styles */
|
||||
h1 {
|
||||
color: #369;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 250%;
|
||||
}
|
||||
h2, h3 {
|
||||
color: #444;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-weight: lighter;
|
||||
}
|
||||
body {
|
||||
margin: 2em;
|
||||
}
|
||||
/* #enddocregion quickstart */
|
||||
body, input[text], button {
|
||||
color: #888;
|
||||
font-family: Cambria, Georgia;
|
||||
}
|
||||
/* #enddocregion toh */
|
||||
a {
|
||||
cursor: pointer;
|
||||
cursor: hand;
|
||||
}
|
||||
button {
|
||||
font-family: Arial;
|
||||
background-color: #eee;
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
cursor: hand;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #cfd8dc;
|
||||
}
|
||||
button:disabled {
|
||||
background-color: #eee;
|
||||
color: #aaa;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
/* Navigation link styles */
|
||||
nav a {
|
||||
padding: 5px 10px;
|
||||
text-decoration: none;
|
||||
margin-right: 10px;
|
||||
margin-top: 10px;
|
||||
display: inline-block;
|
||||
background-color: #eee;
|
||||
border-radius: 4px;
|
||||
}
|
||||
nav a:visited, a:link {
|
||||
color: #607D8B;
|
||||
}
|
||||
nav a:hover {
|
||||
color: #039be5;
|
||||
background-color: #CFD8DC;
|
||||
}
|
||||
nav a.active {
|
||||
color: #039be5;
|
||||
}
|
||||
|
||||
/* items class */
|
||||
.items {
|
||||
margin: 0 0 2em 0;
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
width: 24em;
|
||||
}
|
||||
.items li {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
left: 0;
|
||||
background-color: #EEE;
|
||||
margin: .5em;
|
||||
padding: .3em 0;
|
||||
height: 1.6em;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.items li:hover {
|
||||
color: #607D8B;
|
||||
background-color: #DDD;
|
||||
left: .1em;
|
||||
}
|
||||
.items li.selected {
|
||||
background-color: #CFD8DC;
|
||||
color: white;
|
||||
}
|
||||
.items li.selected:hover {
|
||||
background-color: #BBD8DC;
|
||||
}
|
||||
.items .text {
|
||||
position: relative;
|
||||
top: -3px;
|
||||
}
|
||||
.items .badge {
|
||||
display: inline-block;
|
||||
font-size: small;
|
||||
color: white;
|
||||
padding: 0.8em 0.7em 0 0.7em;
|
||||
background-color: #607D8B;
|
||||
line-height: 1em;
|
||||
position: relative;
|
||||
left: -1px;
|
||||
top: -4px;
|
||||
height: 1.8em;
|
||||
margin-right: .8em;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
/* #docregion toh */
|
||||
/* everywhere else */
|
||||
* {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
|
@ -150,7 +150,9 @@
|
|||
<!-- #docregion phase2-->
|
||||
<div class="container">
|
||||
<h1>Hero Form</h1>
|
||||
<form>
|
||||
<!-- #docregion template-variable-->
|
||||
<form #heroForm="ngForm">
|
||||
<!-- #enddocregion template-variable-->
|
||||
<!-- #docregion ngModel-2-->
|
||||
{{diagnostic}}
|
||||
<div class="form-group">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
'use strict'; // necessary for es6 output in node
|
||||
'use strict'; // necessary for es6 output in node
|
||||
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
|
@ -64,7 +64,7 @@ describe('Pipes', function () {
|
|||
});
|
||||
|
||||
|
||||
xit('should support flying heroes (pure) ', function () {
|
||||
it('should support flying heroes (pure) ', function () {
|
||||
let nameEle = element(by.css('flying-heroes input[type="text"]'));
|
||||
let canFlyCheckEle = element(by.css('flying-heroes #can-fly'));
|
||||
let mutateCheckEle = element(by.css('flying-heroes #mutate'));
|
||||
|
@ -90,7 +90,7 @@ describe('Pipes', function () {
|
|||
});
|
||||
|
||||
|
||||
xit('should support flying heroes (impure) ', function () {
|
||||
it('should support flying heroes (impure) ', function () {
|
||||
let nameEle = element(by.css('flying-heroes-impure input[type="text"]'));
|
||||
let canFlyCheckEle = element(by.css('flying-heroes-impure #can-fly'));
|
||||
let mutateCheckEle = element(by.css('flying-heroes-impure #mutate'));
|
||||
|
|
|
@ -48,11 +48,11 @@
|
|||
<!-- #enddocregion display-none -->
|
||||
|
||||
<h4>NgIf with template</h4>
|
||||
<p><template> element</p>
|
||||
<p><ng-template> element</p>
|
||||
<!-- #docregion ngif-template -->
|
||||
<template [ngIf]="hero">
|
||||
<ng-template [ngIf]="hero">
|
||||
<div>{{hero.name}}</div>
|
||||
</template>
|
||||
</ng-template>
|
||||
<!-- #enddocregion ngif-template -->
|
||||
|
||||
<p>template attribute</p>
|
||||
|
@ -140,9 +140,9 @@
|
|||
<!--#enddocregion inside-ngfor -->
|
||||
<p class="code"><template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById"></p>
|
||||
<!--#docregion inside-ngfor -->
|
||||
<template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById">
|
||||
<ng-template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById">
|
||||
<div [class.odd]="odd">({{i}}) {{hero.name}}</div>
|
||||
</template>
|
||||
</ng-template>
|
||||
<!--#enddocregion inside-ngfor -->
|
||||
|
||||
</div>
|
||||
|
@ -179,21 +179,21 @@
|
|||
</div>
|
||||
<!-- #enddocregion ngswitch-template-attr -->
|
||||
|
||||
<h4>NgSwitch with <template></h4>
|
||||
<h4>NgSwitch with <ng-template></h4>
|
||||
<!-- #docregion ngswitch-template -->
|
||||
<div [ngSwitch]="hero?.emotion">
|
||||
<template [ngSwitchCase]="'happy'">
|
||||
<ng-template [ngSwitchCase]="'happy'">
|
||||
<happy-hero [hero]="hero"></happy-hero>
|
||||
</template>
|
||||
<template [ngSwitchCase]="'sad'">
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="'sad'">
|
||||
<sad-hero [hero]="hero"></sad-hero>
|
||||
</template>
|
||||
<template [ngSwitchCase]="'confused'">
|
||||
</ng-template>
|
||||
<ng-template [ngSwitchCase]="'confused'">
|
||||
<confused-hero [hero]="hero"></confused-hero>
|
||||
</template >
|
||||
<template ngSwitchDefault>
|
||||
</ng-template >
|
||||
<ng-template ngSwitchDefault>
|
||||
<unknown-hero [hero]="hero"></unknown-hero>
|
||||
</template>
|
||||
</ng-template>
|
||||
</div>
|
||||
<!-- #enddocregion ngswitch-template -->
|
||||
|
||||
|
@ -202,9 +202,9 @@
|
|||
<h2><template></h2>
|
||||
<!-- #docregion template-tag -->
|
||||
<p>Hip!</p>
|
||||
<template>
|
||||
<ng-template>
|
||||
<p>Hip!</p>
|
||||
</template>
|
||||
</ng-template>
|
||||
<p>Hooray!</p>
|
||||
<!-- #enddocregion template-tag -->
|
||||
|
||||
|
@ -242,9 +242,9 @@
|
|||
(A) <p template="myUnless condition" class="code unless">
|
||||
</p>
|
||||
|
||||
<template [myUnless]="condition">
|
||||
<ng-template [myUnless]="condition">
|
||||
<p class="code unless">
|
||||
(A) <template [myUnless]="condition">
|
||||
</p>
|
||||
</template>
|
||||
</ng-template>
|
||||
|
||||
|
|
|
@ -544,13 +544,13 @@ bindon-ngModel
|
|||
|
||||
<!-- NgIf binding with template (no *) -->
|
||||
|
||||
<template [ngIf]="currentHero">Add {{currentHero.name}} with template</template>
|
||||
<ng-template [ngIf]="currentHero">Add {{currentHero.name}} with template</ng-template>
|
||||
|
||||
<!-- Does not show because isActive is false! -->
|
||||
<div>Hero Detail removed from DOM (via template) because isActive is false</div>
|
||||
<template [ngIf]="isActive">
|
||||
<ng-template [ngIf]="isActive">
|
||||
<hero-detail></hero-detail>
|
||||
</template>
|
||||
</ng-template>
|
||||
|
||||
<!-- #docregion NgIf-3 -->
|
||||
<!-- isSpecial is true -->
|
||||
|
|
|
@ -141,7 +141,7 @@ function createComponent() {
|
|||
comp = fixture.componentInstance;
|
||||
|
||||
const injector = fixture.debugElement.injector;
|
||||
location = injector.get(Location);
|
||||
location = injector.get(Location) as SpyLocation;
|
||||
router = injector.get(Router);
|
||||
router.initialNavigation();
|
||||
spyOn(injector.get(TwainService), 'getQuote')
|
||||
|
|
|
@ -34,7 +34,7 @@ describe('Angular async helper', () => {
|
|||
|
||||
// Use done. Cannot use setInterval with async or fakeAsync
|
||||
// See https://github.com/angular/angular/issues/10127
|
||||
it('should run async test with successful delayed Observable', done => {
|
||||
it('should run async test with successful delayed Observable', (done: any) => {
|
||||
const source = Observable.of(true).delay(10);
|
||||
source.subscribe(
|
||||
val => actuallyDone = true,
|
||||
|
|
|
@ -18,7 +18,7 @@ describe('FancyService without the TestBed', () => {
|
|||
expect(service.getValue()).toBe('real value');
|
||||
});
|
||||
|
||||
it('#getAsyncValue should return async value', done => {
|
||||
it('#getAsyncValue should return async value', (done: DoneFn) => {
|
||||
service.getAsyncValue().then(value => {
|
||||
expect(value).toBe('async value');
|
||||
done();
|
||||
|
@ -26,7 +26,7 @@ describe('FancyService without the TestBed', () => {
|
|||
});
|
||||
|
||||
// #docregion getTimeoutValue
|
||||
it('#getTimeoutValue should return timeout value', done => {
|
||||
it('#getTimeoutValue should return timeout value', (done: DoneFn) => {
|
||||
service = new FancyService();
|
||||
service.getTimeoutValue().then(value => {
|
||||
expect(value).toBe('timeout value');
|
||||
|
@ -35,7 +35,7 @@ describe('FancyService without the TestBed', () => {
|
|||
});
|
||||
// #enddocregion getTimeoutValue
|
||||
|
||||
it('#getObservableValue should return observable value', done => {
|
||||
it('#getObservableValue should return observable value', (done: DoneFn) => {
|
||||
service.getObservableValue().subscribe(value => {
|
||||
expect(value).toBe('observable value');
|
||||
done();
|
||||
|
|
|
@ -73,7 +73,7 @@ describe('use inject helper in beforeEach', () => {
|
|||
}));
|
||||
|
||||
// Must use done. See https://github.com/angular/angular/issues/10127
|
||||
it('test should wait for FancyService.getObservableDelayValue', done => {
|
||||
it('test should wait for FancyService.getObservableDelayValue', (done: DoneFn) => {
|
||||
service.getObservableDelayValue().subscribe(value => {
|
||||
expect(value).toBe('observable delay value');
|
||||
done();
|
||||
|
@ -187,20 +187,21 @@ describe('TestBed Component Tests', () => {
|
|||
expect(selected).toHaveText(hero.name);
|
||||
});
|
||||
|
||||
it('can access the instance variable of an `*ngFor` row', () => {
|
||||
it('can access the instance variable of an `*ngFor` row component', () => {
|
||||
const fixture = TestBed.createComponent(IoParentComponent);
|
||||
const comp = fixture.componentInstance;
|
||||
const heroName = comp.heroes[0].name; // first hero's name
|
||||
|
||||
fixture.detectChanges();
|
||||
const heroEl = fixture.debugElement.query(By.css('.hero')); // first hero
|
||||
const ngForRow = fixture.debugElement.query(By.directive(IoComponent)); // first hero ngForRow
|
||||
|
||||
const ngForRow = heroEl.parent; // Angular's NgForRow wrapper element
|
||||
const hero = ngForRow.context['hero']; // the hero object passed into the row
|
||||
expect(hero.name).toBe(heroName, 'ngRow.context.hero');
|
||||
|
||||
// jasmine.any is instance-of-type test.
|
||||
expect(ngForRow.componentInstance).toEqual(jasmine.any(IoComponent), 'component is IoComp');
|
||||
|
||||
const hero = ngForRow.context['$implicit']; // the hero object
|
||||
expect(hero.name).toBe(comp.heroes[0].name, '1st hero\'s name');
|
||||
const rowComp = ngForRow.componentInstance;
|
||||
// jasmine.any is an "instance-of-type" test.
|
||||
expect(rowComp).toEqual(jasmine.any(IoComponent), 'component is IoComp');
|
||||
expect(rowComp.hero.name).toBe(heroName, 'component.hero');
|
||||
});
|
||||
|
||||
|
||||
|
@ -343,7 +344,7 @@ describe('TestBed Component Tests', () => {
|
|||
const childComp = el.componentInstance as BankAccountComponent;
|
||||
expect(childComp).toEqual(jasmine.any(BankAccountComponent));
|
||||
|
||||
expect(el.context).toBe(comp, 'context is the parent component');
|
||||
expect(el.context).toBe(childComp, 'context is the child component');
|
||||
|
||||
expect(el.attributes['account']).toBe(childComp.id, 'account attribute');
|
||||
expect(el.attributes['bank']).toBe(childComp.bank, 'bank attribute');
|
||||
|
@ -447,7 +448,7 @@ describe('TestBed Component Overrides:', () => {
|
|||
// `inject` uses TestBed's injector
|
||||
inject([FancyService], (s: FancyService) => testBedProvider = s)();
|
||||
tcProvider = fixture.debugElement.injector.get(FancyService);
|
||||
tpcProvider = fixture.debugElement.children[0].injector.get(FancyService);
|
||||
tpcProvider = fixture.debugElement.children[0].injector.get(FancyService) as FakeFancyService;
|
||||
|
||||
expect(testBedProvider).not.toBe(tcProvider, 'testBed/tc not same providers');
|
||||
expect(testBedProvider).not.toBe(tpcProvider, 'testBed/tpc not same providers');
|
||||
|
|
|
@ -12,7 +12,7 @@ describe('HeroDetailComponent - no TestBed', () => {
|
|||
let hds: any;
|
||||
let router: any;
|
||||
|
||||
beforeEach( done => {
|
||||
beforeEach((done: any) => {
|
||||
expectedHero = new Hero(42, 'Bubba');
|
||||
activatedRoute = new ActivatedRouteStub();
|
||||
activatedRoute.testParams = { id: expectedHero.id };
|
||||
|
@ -45,7 +45,7 @@ describe('HeroDetailComponent - no TestBed', () => {
|
|||
expect(router.navigate.calls.any()).toBe(false, 'router.navigate not called yet');
|
||||
});
|
||||
|
||||
it('should navigate when click save resolves', done => {
|
||||
it('should navigate when click save resolves', (done: any) => {
|
||||
comp.save();
|
||||
// waits for async save to complete before navigating
|
||||
hds.saveHero.calls.first().returnValue
|
||||
|
|
|
@ -90,7 +90,7 @@ function overrideSetup() {
|
|||
beforeEach( async(() => {
|
||||
createComponent();
|
||||
// get the component's injected HeroDetailServiceSpy
|
||||
hdsSpy = fixture.debugElement.injector.get(HeroDetailService);
|
||||
hdsSpy = fixture.debugElement.injector.get(HeroDetailService) as any;
|
||||
}));
|
||||
|
||||
it('should have called `getHero`', () => {
|
||||
|
|
|
@ -78,7 +78,7 @@ describe('TwainComponent', () => {
|
|||
// #enddocregion tests
|
||||
|
||||
// #docregion done-test
|
||||
it('should show quote after getQuote promise (done)', done => {
|
||||
it('should show quote after getQuote promise (done)', (done: any) => {
|
||||
fixture.detectChanges();
|
||||
|
||||
// get the spy promise and wait for it to resolve
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
'use strict'; // necessary for es6 output in node
|
||||
'use strict'; // necessary for es6 output in node
|
||||
|
||||
import { browser, element, by, protractor } from 'protractor';
|
||||
|
||||
|
@ -53,7 +53,7 @@ describe('User Input Tests', function () {
|
|||
expect(outputTextEle.getText()).toEqual('a | ab | abc |');
|
||||
});
|
||||
|
||||
xit('should be able to filter key events', () => {
|
||||
it('should be able to filter key events', () => {
|
||||
let mainEle = element(by.css('key-up3'));
|
||||
let inputEle = mainEle.element(by.css('input'));
|
||||
let outputTextEle = mainEle.element(by.css('p'));
|
||||
|
|
|
@ -62,7 +62,7 @@ module.exports = {
|
|||
// Workaround for angular/angular#11580
|
||||
new webpack.ContextReplacementPlugin(
|
||||
// The (\\|\/) piece accounts for path separators in *nix and Windows
|
||||
/angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
|
||||
/angular(\\|\/)core(\\|\/)@angular/,
|
||||
helpers.root('./src'), // location of your src
|
||||
{} // a map of your routes
|
||||
),
|
||||
|
|
|
@ -6,21 +6,21 @@ var helpers = require('./helpers');
|
|||
|
||||
module.exports = webpackMerge(commonConfig, {
|
||||
devtool: 'cheap-module-eval-source-map',
|
||||
|
||||
|
||||
output: {
|
||||
path: helpers.root('dist'),
|
||||
publicPath: 'http://localhost:8080/',
|
||||
publicPath: '/',
|
||||
filename: '[name].js',
|
||||
chunkFilename: '[id].chunk.js'
|
||||
},
|
||||
|
||||
|
||||
plugins: [
|
||||
new ExtractTextPlugin('[name].css')
|
||||
],
|
||||
|
||||
|
||||
devServer: {
|
||||
historyApiFallback: true,
|
||||
stats: 'minimal'
|
||||
}
|
||||
});
|
||||
// #enddocregion
|
||||
// #enddocregion
|
||||
|
|
|
@ -1079,7 +1079,7 @@ This is especially convenient when you consider that most dependency values are
|
|||
|
||||
<p>
|
||||
What if the dependency value isn't a class? Sometimes the thing you want to inject is a
|
||||
span string, function, or object.
|
||||
string, function, or object.
|
||||
</p>
|
||||
|
||||
|
||||
|
|
|
@ -408,7 +408,7 @@ console:
|
|||
|
||||
|
||||
<code-example format="nocode">
|
||||
Angular 2 is running in the development mode. Call enableProdMode() to enable the production mode.
|
||||
Angular is running in the development mode. Call enableProdMode() to enable the production mode.
|
||||
</code-example>
|
||||
|
||||
|
||||
|
|
|
@ -7,48 +7,48 @@ Render dynamic forms with FormGroup.
|
|||
@description
|
||||
|
||||
|
||||
We can't always justify the cost and time to build handcrafted forms,
|
||||
especially if we'll need a great number of them, they're similar to each other, and they change frequently
|
||||
Building handcrafted forms can be costly and time-consuming,
|
||||
especially if you need a great number of them, they're similar to each other, and they change frequently
|
||||
to meet rapidly changing business and regulatory requirements.
|
||||
|
||||
It may be more economical to create the forms dynamically, based on metadata that describe the business object model.
|
||||
It may be more economical to create the forms dynamically, based on
|
||||
metadata that describes the business object model.
|
||||
|
||||
In this cookbook we show how to use `formGroup` to dynamically render a simple form with different control types and validation.
|
||||
It's a primitive start.
|
||||
This cookbook shows you how to use `formGroup` to dynamically
|
||||
render a simple form with different control types and validation.
|
||||
It's a primitive start.
|
||||
It might evolve to support a much richer variety of questions, more graceful rendering, and superior user experience.
|
||||
All such greatness has humble beginnings.
|
||||
|
||||
In our example we use a dynamic form to build an online application experience for heroes seeking employment.
|
||||
The example in this cookbook is a dynamic form to build an
|
||||
online application experience for heroes seeking employment.
|
||||
The agency is constantly tinkering with the application process.
|
||||
We can create the forms on the fly *without changing our application code*.
|
||||
You can create the forms on the fly *without changing the application code*.
|
||||
{@a toc}
|
||||
|
||||
## Table of contents
|
||||
|
||||
[Bootstrap](guide/dynamic-form#bootstrap)
|
||||
|
||||
[Question Model](guide/dynamic-form#object-model)
|
||||
|
||||
[Form Component](guide/dynamic-form#form-component)
|
||||
|
||||
[Questionnaire Metadata](guide/dynamic-form#questionnaire-metadata)
|
||||
|
||||
[Dynamic Template](guide/dynamic-form#dynamic-template)
|
||||
# Contents
|
||||
* [Bootstrap](guide/dynamic-form#bootstrap)
|
||||
* [Question model](guide/dynamic-form#object-model)
|
||||
* [Question form components](guide/dynamic-form#form-component)
|
||||
* [Questionnaire data](guide/dynamic-form#questionnaire-data)
|
||||
* [Dynamic template](guide/dynamic-form#dynamic-template)
|
||||
|
||||
|
||||
**See the <live-example name="cb-dynamic-form"></live-example>**.
|
||||
See the <live-example name="cb-dynamic-form"></live-example>.
|
||||
|
||||
{@a bootstrap}
|
||||
|
||||
## Bootstrap
|
||||
|
||||
We start by creating an `NgModule` called `AppModule`.
|
||||
Start by creating an `NgModule` called `AppModule`.
|
||||
|
||||
In our example we will be using Reactive Forms.
|
||||
This cookbook uses [reactive forms](guide/reactive-forms).
|
||||
|
||||
Reactive Forms belongs to a different `NgModule` called `ReactiveFormsModule`, so in order to access any Reactive Forms directives, we have to import `ReactiveFormsModule` from the `@angular/forms` library.
|
||||
Reactive forms belongs to a different `NgModule` called `ReactiveFormsModule`,
|
||||
so in order to access any reactive forms directives, you have to import
|
||||
`ReactiveFormsModule` from the `@angular/forms` library.
|
||||
|
||||
We bootstrap our `AppModule` in main.ts.
|
||||
Bootstrap the `AppModule` in `main.ts`.
|
||||
|
||||
|
||||
<code-tabs>
|
||||
|
@ -66,13 +66,13 @@ We bootstrap our `AppModule` in main.ts.
|
|||
|
||||
{@a object-model}
|
||||
|
||||
## Question Model
|
||||
## Question model
|
||||
|
||||
The next step is to define an object model that can describe all scenarios needed by the form functionality.
|
||||
The hero application process involves a form with a lot of questions.
|
||||
The "question" is the most fundamental object in the model.
|
||||
The hero application process involves a form with a lot of questions.
|
||||
The _question_ is the most fundamental object in the model.
|
||||
|
||||
We have created `QuestionBase` as the most fundamental question class.
|
||||
The following `QuestionBase` is a fundamental question class.
|
||||
|
||||
|
||||
<code-example path="cb-dynamic-form/src/app/question-base.ts" title="src/app/question-base.ts">
|
||||
|
@ -81,10 +81,13 @@ We have created `QuestionBase` as the most fundamental question class.
|
|||
|
||||
|
||||
|
||||
From this base we derived two new classes in `TextboxQuestion` and `DropdownQuestion` that represent Textbox and Dropdown questions.
|
||||
The idea is that the form will be bound to specific question types and render the appropriate controls dynamically.
|
||||
From this base you can derive two new classes in `TextboxQuestion` and `DropdownQuestion`
|
||||
that represent textbox and dropdown questions.
|
||||
The idea is that the form will be bound to specific question types and render the
|
||||
appropriate controls dynamically.
|
||||
|
||||
`TextboxQuestion` supports multiple html5 types like text, email, url etc via the `type` property.
|
||||
`TextboxQuestion` supports multiple HTML5 types such as text, email, and url
|
||||
via the `type` property.
|
||||
|
||||
|
||||
<code-example path="cb-dynamic-form/src/app/question-textbox.ts" title="src/app/question-textbox.ts" linenums="false">
|
||||
|
@ -102,8 +105,9 @@ The idea is that the form will be bound to specific question types and render th
|
|||
|
||||
|
||||
|
||||
Next we have defined `QuestionControlService`, a simple service for transforming our questions to a `FormGroup`.
|
||||
In a nutshell, the form group consumes the metadata from the question model and allows us to specify default values and validation rules.
|
||||
Next is `QuestionControlService`, a simple service for transforming the questions to a `FormGroup`.
|
||||
In a nutshell, the form group consumes the metadata from the question model and
|
||||
allows you to specify default values and validation rules.
|
||||
|
||||
|
||||
<code-example path="cb-dynamic-form/src/app/question-control.service.ts" title="src/app/question-control.service.ts" linenums="false">
|
||||
|
@ -113,10 +117,11 @@ In a nutshell, the form group consumes the metadata from the question model and
|
|||
{@a form-component}
|
||||
|
||||
## Question form components
|
||||
Now that we have defined the complete model we are ready to create components to represent the dynamic form.
|
||||
Now that you have defined the complete model you are ready
|
||||
to create components to represent the dynamic form.
|
||||
|
||||
|
||||
`DynamicFormComponent` is the entry point and the main container for the form.
|
||||
`DynamicFormComponent` is the entry point and the main container for the form.
|
||||
|
||||
<code-tabs>
|
||||
|
||||
|
@ -132,9 +137,10 @@ Now that we have defined the complete model we are ready to create components to
|
|||
|
||||
|
||||
|
||||
It presents a list of questions, each question bound to a `<df-question>` component element.
|
||||
It presents a list of questions, each bound to a `<df-question>` component element.
|
||||
The `<df-question>` tag matches the `DynamicFormQuestionComponent`,
|
||||
the component responsible for rendering the details of each _individual_ question based on values in the data-bound question object.
|
||||
the component responsible for rendering the details of each _individual_
|
||||
question based on values in the data-bound question object.
|
||||
|
||||
|
||||
<code-tabs>
|
||||
|
@ -151,26 +157,30 @@ the component responsible for rendering the details of each _individual_ questio
|
|||
|
||||
|
||||
|
||||
Notice this component can present any type of question in our model.
|
||||
We only have two types of questions at this point but we can imagine many more.
|
||||
Notice this component can present any type of question in your model.
|
||||
You only have two types of questions at this point but you can imagine many more.
|
||||
The `ngSwitch` determines which type of question to display.
|
||||
|
||||
In both components we're relying on Angular's **formGroup** to connect the template HTML to the
|
||||
In both components you're relying on Angular's **formGroup** to connect the template HTML to the
|
||||
underlying control objects, populated from the question model with display and validation rules.
|
||||
|
||||
`formControlName` and `formGroup` are directives defined in `ReactiveFormsModule`. Our templates can access these directives directly since we imported `ReactiveFormsModule` from `AppModule`.
|
||||
{@a questionnaire-metadata}
|
||||
`formControlName` and `formGroup` are directives defined in
|
||||
`ReactiveFormsModule`. The templates can access these directives
|
||||
directly since you imported `ReactiveFormsModule` from `AppModule`.
|
||||
{@a questionnaire-data}
|
||||
|
||||
## Questionnaire data
|
||||
|
||||
`DynamicFormComponent` expects the list of questions in the form of an array bound to `@Input() questions`.
|
||||
`DynamicFormComponent` expects the list of questions in the form of an array bound to `@Input() questions`.
|
||||
|
||||
The set of questions you've defined for the job application is returned from the `QuestionService`.
|
||||
In a real app you'd retrieve these questions from storage.
|
||||
|
||||
The key point is that you control the hero job application questions
|
||||
entirely through the objects returned from `QuestionService`.
|
||||
Questionnaire maintenance is a simple matter of adding, updating,
|
||||
and removing objects from the `questions` array.
|
||||
|
||||
The set of questions we have defined for the job application is returned from the `QuestionService`.
|
||||
In a real app we'd retrieve these questions from storage.
|
||||
|
||||
The key point is that we control the hero job application questions entirely through the objects returned from `QuestionService`.
|
||||
Questionnaire maintenance is a simple matter of adding, updating, and removing objects from the `questions` array.
|
||||
|
||||
|
||||
<code-example path="cb-dynamic-form/src/app/question.service.ts" title="src/app/question.service.ts">
|
||||
|
||||
|
@ -178,7 +188,7 @@ underlying control objects, populated from the question model with display and v
|
|||
|
||||
|
||||
|
||||
Finally, we display an instance of the form in the `AppComponent` shell.
|
||||
Finally, display an instance of the form in the `AppComponent` shell.
|
||||
|
||||
|
||||
<code-example path="cb-dynamic-form/src/app/app.component.ts" title="app.component.ts">
|
||||
|
@ -188,17 +198,18 @@ Finally, we display an instance of the form in the `AppComponent` shell.
|
|||
{@a dynamic-template}
|
||||
|
||||
## Dynamic Template
|
||||
Although in this example we're modelling a job application for heroes, there are no references to any specific hero question
|
||||
outside the objects returned by `QuestionService`.
|
||||
Although in this example you're modelling a job application for heroes, there are
|
||||
no references to any specific hero question
|
||||
outside the objects returned by `QuestionService`.
|
||||
|
||||
This is very important since it allows us to repurpose the components for any type of survey
|
||||
as long as it's compatible with our *question* object model.
|
||||
The key is the dynamic data binding of metadata used to render the form
|
||||
without making any hardcoded assumptions about specific questions.
|
||||
In addition to control metadata, we are also adding validation dynamically.
|
||||
This is very important since it allows you to repurpose the components for any type of survey
|
||||
as long as it's compatible with the *question* object model.
|
||||
The key is the dynamic data binding of metadata used to render the form
|
||||
without making any hardcoded assumptions about specific questions.
|
||||
In addition to control metadata, you are also adding validation dynamically.
|
||||
|
||||
The *Save* button is disabled until the form is in a valid state.
|
||||
When the form is valid, we can click *Save* and the app renders the current form values as JSON.
|
||||
The *Save* button is disabled until the form is in a valid state.
|
||||
When the form is valid, you can click *Save* and the app renders the current form values as JSON.
|
||||
This proves that any user input is bound back to the data model.
|
||||
Saving and retrieving the data is an exercise for another time.
|
||||
|
||||
|
|
|
@ -296,6 +296,19 @@ You added a *Submit* button at the bottom with some classes on it for styling.
|
|||
|
||||
*You're not using Angular yet*. There are no bindings or extra directives, just layout.
|
||||
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
In template driven forms, if you've imported `FormsModule`, you don't have to do anything
|
||||
to the `<form>` tag in order to make use of `FormsModule`. Continue on to see how this works.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
The `container`, `form-group`, `form-control`, and `btn` classes
|
||||
come from [Twitter Bootstrap](http://getbootstrap.com/css/). These classes are purely cosmetic.
|
||||
Bootstrap gives the form a little style.
|
||||
|
@ -400,6 +413,42 @@ You left yourself a note to throw it away when you're done.
|
|||
|
||||
Focus on the binding syntax: `[(ngModel)]="..."`.
|
||||
|
||||
You need one more addition to display the data. Declare
|
||||
a template variable for the form. Update the `<form>` tag with
|
||||
`#heroForm="ngForm"` as follows:
|
||||
|
||||
|
||||
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (excerpt)" region="template-variable">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
The variable `heroForm` is now a reference to the `NgForm` directive that governs the form as a whole.
|
||||
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
### The _NgForm_ directive
|
||||
|
||||
What `NgForm` directive?
|
||||
You didn't add an [NgForm](api/forms/index/NgForm-directive) directive.
|
||||
|
||||
Angular did. Angular automatically creates and attaches an `NgForm` directive to the `<form>` tag.
|
||||
|
||||
The `NgForm` directive supplements the `form` element with additional features.
|
||||
It holds the controls you created for the elements with an `ngModel` directive
|
||||
and `name` attribute, and monitors their properties, including their validity.
|
||||
It also has its own `valid` property which is true only *if every contained
|
||||
control* is valid.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
If you ran the app now and started typing in the *Name* input box,
|
||||
adding and deleting characters, you'd see them appear and disappear
|
||||
from the interpolated text.
|
||||
|
@ -442,7 +491,7 @@ Defining a `name` attribute is a requirement when using `[(ngModel)]` in combina
|
|||
Internally, Angular creates `FormControl` instances and
|
||||
registers them with an `NgForm` directive that Angular attached to the `<form>` tag.
|
||||
Each `FormControl` is registered under the name you assigned to the `name` attribute.
|
||||
Read more in [The NgForm directive](guide/forms#ngForm), later in this page.
|
||||
Read more in the previous section, [The NgForm directive](guide/forms#ngForm).
|
||||
|
||||
|
||||
</div>
|
||||
|
@ -784,32 +833,9 @@ to the hero form component's `onSubmit()` method:
|
|||
|
||||
|
||||
|
||||
You added something extra at the end. You defined a
|
||||
template reference variable, `#heroForm`, and initialized it with the value "ngForm".
|
||||
|
||||
The variable `heroForm` is now a reference to the `NgForm` directive that governs the form as a whole.
|
||||
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
### The _NgForm_ directive
|
||||
|
||||
What `NgForm` directive?
|
||||
You didn't add an [NgForm](api/forms/index/NgForm-directive) directive.
|
||||
|
||||
Angular did. Angular automatically creates and attaches an `NgForm` directive to the `<form>` tag.
|
||||
|
||||
The `NgForm` directive supplements the `form` element with additional features.
|
||||
It holds the controls you created for the elements with an `ngModel` directive
|
||||
and `name` attribute, and monitors their properties, including their validity.
|
||||
It also has its own `valid` property which is true only *if every contained
|
||||
control* is valid.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
You'd already defined a template reference variable,
|
||||
`#heroForm`, and initialized it with the value "ngForm".
|
||||
Now, use that variable to access the form with the Submit button.
|
||||
|
||||
|
||||
You'll bind the form's overall validity via
|
||||
|
|
|
@ -12,24 +12,36 @@ Translate the app's template text into multiple languages.
|
|||
|
||||
Angular's _internationalization_ (_i18n_) tools help make your app available in multiple languages.
|
||||
|
||||
## Table of contents
|
||||
# Contents
|
||||
|
||||
* [Angular and i18n template translation](guide/i18n#angular-i18n)
|
||||
* [Mark text with the _i18n_ attribute](guide/i18n#i18n-attribute)
|
||||
* [Help the translator with a description and intent](guide/i18n#help-translator)
|
||||
* [Translate text without creating an element](guide/i18n#no-element)
|
||||
* [Add _i18n-..._ translation attributes](guide/i18n#translate-attributes)
|
||||
* [Handle singular and plural](guide/i18n#cardinality)
|
||||
* [Select among alternative texts](guide/i18n#select)
|
||||
* [Create a translation source file with the **_ng-xi18n_ extraction tool**](guide/i18n#ng-xi18n)
|
||||
* [Other translation formats](guide/i18n#other-formats)
|
||||
* [Other options](guide/i18n#ng-xi18n-options)
|
||||
* [Add an `npm` script for convenience](guide/i18n#npm-i18n-script)
|
||||
* [Translate text messages](guide/i18n#translate)
|
||||
* [Create a localization folder](guide/i18n#localization-folder)
|
||||
* [Translate text nodes](guide/i18n#translate-text-nodes)
|
||||
* [Translate `plural` and `select`](guide/i18n#translate-plural-select)
|
||||
* [Translate `plural`](guide/i18n#translate-plural)
|
||||
* [Translate `select`](guide/i18n#translate-select)
|
||||
* [The app before translation](guide/i18n#app-pre-translation)
|
||||
* [Merge the completed translation file into the app](guide/i18n#merge)
|
||||
|
||||
* [Merge with the JIT compiler](guide/i18n#jit)
|
||||
* [SystemJS text plugin](guide/i18n#text-plugin)
|
||||
* [Create translation providers](guide/i18n#create-translation-providers)
|
||||
* [Bootstrap the app with translation providers](guide/i18n#bootstrap-the-app)
|
||||
* [Internationalization with the AOT compiler](guide/i18n#aot)
|
||||
|
||||
* [Translation file maintenance and _id_ changes](guide/i18n#maintenance)
|
||||
|
||||
|
||||
**Try this** <live-example name="cb-i18n" title="i18n Example in Spanish">live example</live-example>
|
||||
Try this <live-example name="cb-i18n" title="i18n Example in Spanish">live example</live-example>
|
||||
of a JIT-compiled app, translated into Spanish.
|
||||
|
||||
|
||||
|
@ -40,7 +52,7 @@ of a JIT-compiled app, translated into Spanish.
|
|||
|
||||
## Angular and _i18n_ template translation
|
||||
|
||||
Application internationalization is a challenging, many-faceted effort that
|
||||
Application internationalization is a challenging, many-faceted effort that
|
||||
takes dedication and enduring commitment.
|
||||
Angular's _i18n_ internationalization facilities can help.
|
||||
|
||||
|
@ -67,10 +79,10 @@ The _i18n_ template translation process has four phases:
|
|||
|
||||
1. An angular _i18n_ tool extracts the marked messages into an industry standard translation source file.
|
||||
|
||||
1. A translator edits that file, translating the extracted text messages into the target language,
|
||||
1. A translator edits that file, translating the extracted text messages into the target language,
|
||||
and returns the file to you.
|
||||
|
||||
1. The Angular compiler imports the completed translation files,
|
||||
1. The Angular compiler imports the completed translation files,
|
||||
replaces the original messages with translated text, and generates a new version of the application
|
||||
in the target language.
|
||||
|
||||
|
@ -91,7 +103,7 @@ Place it on every element tag whose fixed text should be translated.
|
|||
|
||||
|
||||
|
||||
`i18n` is not an Angular _directive_.
|
||||
`i18n` is not an Angular _directive_.
|
||||
It's a custom _attribute_, recognized by Angular tools and compilers.
|
||||
After translation, the compiler removes it.
|
||||
|
||||
|
@ -100,7 +112,7 @@ After translation, the compiler removes it.
|
|||
|
||||
|
||||
|
||||
In the accompanying sample, an `<h1>` tag displays a simple English language greeting
|
||||
In the accompanying sample, an `<h1>` tag displays a simple English language greeting
|
||||
that you translate into Spanish:
|
||||
|
||||
<code-example path="cb-i18n/src/app/app.component.1.html" region="greeting" title="src/app/app.component.html" linenums="false">
|
||||
|
@ -118,9 +130,12 @@ Add the `i18n` attribute to the tag to mark it for translation.
|
|||
|
||||
|
||||
|
||||
{@a help-translator}
|
||||
|
||||
|
||||
### Help the translator with a _description_ and _intent_
|
||||
|
||||
In order to translate it accurately, the translator may
|
||||
In order to translate it accurately, the translator may
|
||||
need a description of the message.
|
||||
Assign a description to the i18n attribute:
|
||||
|
||||
|
@ -131,8 +146,8 @@ Assign a description to the i18n attribute:
|
|||
|
||||
|
||||
|
||||
In order to deliver a correct translation, the translator may need to
|
||||
know your _intent_—the true _meaning_ of the text
|
||||
In order to deliver a correct translation, the translator may need to
|
||||
know your _intent_—the true _meaning_ of the text
|
||||
within _this particular_ application context.
|
||||
In front of the description, add some contextual meaning to the assigned string,
|
||||
separating it from the description with the `|` character (`<meaning>|<description>`):
|
||||
|
@ -144,11 +159,15 @@ separating it from the description with the `|` character (`<meaning>|<descripti
|
|||
|
||||
|
||||
|
||||
While all appearances of a message with the _same_ meaning have the _same_ translation,
|
||||
While all appearances of a message with the _same_ meaning have the _same_ translation,
|
||||
a message with *a variety of possible meanings* could have different translations.
|
||||
The Angular extraction tool preserves both the _meaning_ and the _description_ in the translation source file
|
||||
to facilitiate contextually-specific translations.
|
||||
|
||||
|
||||
{@a no-element}
|
||||
|
||||
|
||||
### Translate text without creating an element
|
||||
|
||||
Suppose there is a stretch of text that you'd like to translate.
|
||||
|
@ -189,9 +208,9 @@ You've added an image to your template. You care about accessibility too so you
|
|||
|
||||
|
||||
|
||||
The `title` attribute needs to be translated.
|
||||
The `title` attribute needs to be translated.
|
||||
Angular i18n support has more translation attributes in the form,`i18n-x`, where `x` is the
|
||||
name of the attribute to translate.
|
||||
name of the attribute to translate.
|
||||
|
||||
To translate the `title` on the `img` tag from the previous example, write:
|
||||
|
||||
|
@ -213,9 +232,9 @@ You can also assign a meaning and a description with the `i18n-x="<meaning>|<des
|
|||
|
||||
Different languages have different pluralization rules.
|
||||
|
||||
Suppose your application says something about a collection of wolves.
|
||||
Suppose your application says something about a collection of wolves.
|
||||
In English, depending upon the number of wolves, you could display "no wolves", "one wolf", "two wolves", or "a wolf pack".
|
||||
Other languages might express the _cardinality_ differently.
|
||||
Other languages might express the _cardinality_ differently.
|
||||
|
||||
Here's how you could mark up the component template to display the phrase appropriate to the number of wolves:
|
||||
|
||||
|
@ -226,10 +245,10 @@ Here's how you could mark up the component template to display the phrase approp
|
|||
|
||||
|
||||
|
||||
* The first parameter is the key. It is bound to the component property (`wolves`)
|
||||
* The first parameter is the key. It is bound to the component property (`wolves`)
|
||||
that determines the number of wolves.
|
||||
* The second parameter identifies this as a `plural` translation type.
|
||||
* The third parameter defines a pluralization pattern consisting of pluralization
|
||||
* The third parameter defines a pluralization pattern consisting of pluralization
|
||||
categories and their matching values.
|
||||
|
||||
Pluralization categories include:
|
||||
|
@ -244,7 +263,7 @@ Put the default _English_ translation in braces (`{}`) next to the pluralization
|
|||
* When you're talking about one wolf, you could write `=1 {one wolf}`.
|
||||
|
||||
* For zero wolves, you could write `=0 {no wolves}`.
|
||||
* For two wolves, you could write `=2 {two wolves}`.
|
||||
* For two wolves, you could write `=2 {two wolves}`.
|
||||
|
||||
You could keep this up for three, four, and every other number of wolves.
|
||||
Or you could specify the **`other`** category as a catch-all for any unmatched cardinality
|
||||
|
@ -257,8 +276,8 @@ and write something like: `other {a wolf pack}`.
|
|||
|
||||
This syntax conforms to the
|
||||
<a href="http://userguide.icu-project.org/formatparse/messages" target="_blank" title="ICU Message Format">ICU Message Format</a>
|
||||
that derives from the
|
||||
<a href="http://cldr.unicode.org/" target="_blank" title="CLDR">Common Locale Data Repository (CLDR),</a>
|
||||
that derives from the
|
||||
<a href="http://cldr.unicode.org/" target="_blank" title="CLDR">Common Locale Data Repository (CLDR)</a>,
|
||||
which specifies the
|
||||
<a href="http://unicode.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules" target="_blank" title="Pluralization Rules">pluralization rules</a>.
|
||||
|
||||
|
@ -274,12 +293,12 @@ which specifies the
|
|||
The application displays different text depending upon whether the hero is male or female.
|
||||
These text alternatives require translation too.
|
||||
|
||||
You can handle this with a `select` translation.
|
||||
You can handle this with a `select` translation.
|
||||
A `select` also follows the
|
||||
<a href="http://userguide.icu-project.org/formatparse/messages" target="_blank" title="ICU Message Format">ICU message syntax</a>.
|
||||
<a href="http://userguide.icu-project.org/formatparse/messages" target="_blank" title="ICU Message Format">ICU message syntax</a>.
|
||||
You choose among alternative translation based on a string value instead of a number.
|
||||
|
||||
The following format message in the component template binds to the component's `gender`
|
||||
The following format message in the component template binds to the component's `gender`
|
||||
property, which outputs either an "m" or an "f".
|
||||
The message maps those values to the appropriate translation:
|
||||
|
||||
|
@ -296,7 +315,7 @@ The message maps those values to the appropriate translation:
|
|||
|
||||
## Create a translation source file with the _ng-xi18n_ tool
|
||||
|
||||
Use the **_ng-xi18n_ extraction tool** to extract the `i18n`-marked texts
|
||||
Use the **_ng-xi18n_ extraction tool** to extract the `i18n`-marked texts
|
||||
into a translation source file in an industry standard format.
|
||||
|
||||
This is an Angular CLI tool in the `@angular/compiler-cli` npm package.
|
||||
|
@ -340,7 +359,7 @@ By default, the tool generates a translation file named **`messages.xlf`** in th
|
|||
|
||||
### Other translation formats
|
||||
|
||||
You can generate a file named **`messages.xmb`** in the
|
||||
You can generate a file named **`messages.xmb`** in the
|
||||
<a href="http://cldr.unicode.org/development/development-process/design-proposals/xmb" target="_blank">XML Message Bundle (XMB)</a> format
|
||||
by adding the `--i18nFormat=xmb` flag.
|
||||
|
||||
|
@ -359,8 +378,8 @@ This sample sticks with the _XLIFF_ format.
|
|||
|
||||
|
||||
### Other options
|
||||
You may have to specify additional options.
|
||||
For example, if the `tsconfig.json` TypeScript configuration
|
||||
You may have to specify additional options.
|
||||
For example, if the `tsconfig.json` TypeScript configuration
|
||||
file is located somewhere other than in the root folder,
|
||||
you must identify the path to it with the `-p` option:
|
||||
|
||||
|
@ -383,7 +402,7 @@ to make the command easier to remember and run:
|
|||
|
||||
<code-example format='.' language='sh'>
|
||||
"scripts": {
|
||||
"i18n": "ng-xi18n",
|
||||
"i18n": "ng-xi18n",
|
||||
...
|
||||
}
|
||||
</code-example>
|
||||
|
@ -400,7 +419,7 @@ Now you can issue command variations such as these:
|
|||
|
||||
|
||||
|
||||
Note the `--` flag before the options.
|
||||
Note the `--` flag before the options.
|
||||
It tells _npm_ to pass every flag thereafter to `ng-xi18n`.
|
||||
|
||||
|
||||
|
@ -410,9 +429,9 @@ It tells _npm_ to pass every flag thereafter to `ng-xi18n`.
|
|||
|
||||
## Translate text messages
|
||||
|
||||
The `ng-xi18n` command generates a translation source file
|
||||
The `ng-xi18n` command generates a translation source file
|
||||
in the project root folder named `messages.xlf`.
|
||||
The next step is to translate the English language template
|
||||
The next step is to translate the English language template
|
||||
text into the specific language translation
|
||||
files. The cookbook sample creates a Spanish translation file.
|
||||
|
||||
|
@ -425,14 +444,14 @@ files. The cookbook sample creates a Spanish translation file.
|
|||
You will probably translate into more than one other language so it's a good idea
|
||||
for the project structure to reflect your entire internationalization effort.
|
||||
|
||||
One approach is to dedicate a folder to localization and store related assets
|
||||
(for example, internationalization files) there.
|
||||
One approach is to dedicate a folder to localization and store related assets,
|
||||
such as internationalization files, there.
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
||||
|
||||
Localization and internationalization are
|
||||
Localization and internationalization are
|
||||
<a href="https://en.wikipedia.org/wiki/Internationalization_and_localization" target="_blank">different but closely related terms</a>.
|
||||
|
||||
</div>
|
||||
|
@ -443,10 +462,13 @@ This cookbook follows that suggestion. It has a `locale` folder under the `src/`
|
|||
Assets within the folder carry a filename extension that matches a language-culture code from a
|
||||
<a href="https://msdn.microsoft.com/en-us/library/ee825488(v=cs.20).aspx" target="_blank">well-known codeset</a>.
|
||||
|
||||
Make a copy of the `messages.xlf` file, put it in the `locale` folder and
|
||||
Make a copy of the `messages.xlf` file, put it in the `locale` folder and
|
||||
rename it `messages.es.xlf`for the Spanish language translation.
|
||||
Do the same for each target language.
|
||||
|
||||
{@a translate-text-nodes}
|
||||
|
||||
|
||||
### Translate text nodes
|
||||
In the real world, you send the `messages.es.xlf` file to a Spanish translator who fills in the translations
|
||||
using one of the
|
||||
|
@ -462,7 +484,7 @@ Open `messages.es.xlf` and find the first `<trans-unit>` section:
|
|||
|
||||
|
||||
|
||||
This XML element represents the translation of the `<h1>` greeting tag you marked with the `i18n` attribute.
|
||||
This XML element represents the translation of the `<h1>` greeting tag you marked with the `i18n` attribute.
|
||||
|
||||
Using the _source_, _description_, and _meaning_ elements to guide your translation,
|
||||
replace the `<target/>` tag with the Spanish greeting:
|
||||
|
@ -502,16 +524,19 @@ Translate the other text nodes the same way:
|
|||
## Translate _plural_ and _select_
|
||||
Translating _plural_ and _select_ messages is a little tricky.
|
||||
|
||||
The `<source>` tag is empty for `plural` and `select` translation
|
||||
The `<source>` tag is empty for `plural` and `select` translation
|
||||
units, which makes them hard to correlate with the original template.
|
||||
The `XLIFF` format doesn't yet support the ICU rules; it soon will.
|
||||
The `XLIFF` format doesn't yet support the ICU rules.
|
||||
However, the `XMB` format does support the ICU rules.
|
||||
|
||||
You'll just have to look for them in relation to other translation units that you recognize from elsewhere in the source template.
|
||||
In this example, you know the translation unit for the `select` must be just below the translation unit for the logo.
|
||||
|
||||
|
||||
### Translate _plural_
|
||||
{@a translate-plural}
|
||||
|
||||
|
||||
### Translate _plural_
|
||||
To translate a `plural`, translate its ICU format match values:
|
||||
|
||||
|
||||
|
@ -521,7 +546,10 @@ To translate a `plural`, translate its ICU format match values:
|
|||
|
||||
|
||||
|
||||
### Translate _select_
|
||||
{@a translate-select}
|
||||
|
||||
|
||||
### Translate _select_
|
||||
The `select` behaves a little differently. Here again is the ICU format message in the component template:
|
||||
|
||||
|
||||
|
@ -534,7 +562,7 @@ The `select` behaves a little differently. Here again is the ICU format message
|
|||
The extraction tool broke that into _two_ translation units.
|
||||
|
||||
The first unit contains the text that was _outside_ the `select`.
|
||||
In place of the `select` is a placeholder, `<x id="ICU">`, that represents the `select` message.
|
||||
In place of the `select` is a placeholder, `<x id="ICU">`, that represents the `select` message.
|
||||
Translate the text and leave the placeholder where it is.
|
||||
|
||||
|
||||
|
@ -568,7 +596,7 @@ Here they are together, after translation:
|
|||
|
||||
|
||||
|
||||
The entire template translation is complete. It's
|
||||
The entire template translation is complete. It's
|
||||
time to incorporate that translation into the application.
|
||||
|
||||
|
||||
|
@ -617,21 +645,21 @@ When the previous steps finish, the sample app _and_ its translation file are as
|
|||
|
||||
To merge the translated text into component templates,
|
||||
compile the application with the completed translation file.
|
||||
The process is the same whether the file is in `.xlf` format or
|
||||
in another format (`.xlif` and `.xtb`) that Angular understands.
|
||||
The process is the same whether the file is in `.xlf` format or
|
||||
in another format that Angular understands, such as `.xlif` or `.xtb`.
|
||||
|
||||
You provide the Angular compiler with three new pieces of information:
|
||||
|
||||
* the translation file
|
||||
* the translation file format
|
||||
* the <a href="https://en.wikipedia.org/wiki/XLIFF" target="_blank">_Locale ID_</a>
|
||||
(`es` or `en-US` for instance)
|
||||
* The translation file.
|
||||
* The translation file format.
|
||||
* The <a href="https://en.wikipedia.org/wiki/XLIFF" target="_blank">_Locale ID_</a>
|
||||
(`es` or `en-US` for instance).
|
||||
|
||||
_How_ you provide this information depends upon whether you compile with
|
||||
the JIT (_Just-in-Time_) compiler or the AOT (_Ahead-of-Time_) compiler.
|
||||
the JIT (_Just-in-Time_) compiler or the AOT (_Ahead-of-Time_) compiler.
|
||||
|
||||
* With [JIT](guide/i18n#jit), you provide the information at bootstrap time.
|
||||
* With [AOT](guide/i18n#aot), you pass the information as `ngc` options.
|
||||
* With [AOT](guide/i18n#aot), you pass the information as `ngc` options.
|
||||
|
||||
|
||||
{@a jit}
|
||||
|
@ -663,7 +691,7 @@ in the `index.html`.
|
|||
{@a text-plugin}
|
||||
|
||||
|
||||
### SystemJS Text plugin
|
||||
### SystemJS text plugin
|
||||
|
||||
Notice the SystemJS mapping of `text` to a `systemjs-text-plugin.js`.
|
||||
With the help of a text plugin, SystemJS can read any file as raw text and
|
||||
|
@ -679,16 +707,19 @@ Create the following `systemjs-text-plugin.js` in the `src/` folder:
|
|||
|
||||
|
||||
|
||||
{@a create-translation-providers}
|
||||
|
||||
|
||||
### Create translation providers
|
||||
|
||||
Three providers tell the JIT compiler how to translate the template texts for a particular language
|
||||
while compiling the application:
|
||||
|
||||
* `TRANSLATIONS` is a string containing the content of the translation file.
|
||||
* `TRANSLATIONS_FORMAT` is the format of the file: `xlf`, `xlif` or `xtb`.
|
||||
* `TRANSLATIONS_FORMAT` is the format of the file: `xlf`, `xlif`, or `xtb`.
|
||||
* `LOCALE_ID` is the locale of the target language.
|
||||
|
||||
The `getTranslationProviders` function in the following `src/app/i18n-providers.ts`
|
||||
The `getTranslationProviders()` function in the following `src/app/i18n-providers.ts`
|
||||
creates those providers based on the user's _locale_
|
||||
and the corresponding translation file:
|
||||
|
||||
|
@ -704,22 +735,25 @@ and the corresponding translation file:
|
|||
The function returns an empty `noProviders` array as a `Promise`.
|
||||
It must return a `Promise` because this function could read a translation file asynchronously from the server.
|
||||
|
||||
1. It creates a transaction filename from the locale according to the name and location convention
|
||||
1. It creates a transaction filename from the locale according to the name and location convention
|
||||
[described earlier](guide/i18n#localization-folder).
|
||||
|
||||
1. The `getTranslationsWithSystemJs` method reads the translation and returns the contents as a string.
|
||||
Notice that it appends `!text` to the filename, telling SystemJS to use the [text plugin](guide/i18n#text-plugin).
|
||||
1. The `getTranslationsWithSystemJs()` method reads the translation and returns the contents as a string.
|
||||
Notice that it appends `!text` to the filename, telling SystemJS to use the [text plugin](guide/i18n#text-plugin).
|
||||
|
||||
1. The callback composes a providers array with the three translation providers.
|
||||
|
||||
1. Finally, `getTranslationProviders` returns the entire effort as a promise.
|
||||
1. Finally, `getTranslationProviders()` returns the entire effort as a promise.
|
||||
|
||||
{@a bootstrap-the-app}
|
||||
|
||||
|
||||
### Bootstrap the app with translation providers
|
||||
|
||||
The Angular `bootstrapModule` method has a second, _options_ parameter
|
||||
The Angular `bootstrapModule()` method has a second _options_ parameter
|
||||
that can influence the behavior of the compiler.
|
||||
|
||||
You'll create an _options_ object with the translation providers from `getTranslationProviders`
|
||||
You'll create an _options_ object with the translation providers from `getTranslationProviders()`
|
||||
and pass it to `bootstrapModule`.
|
||||
Open the `src/main.ts` and modify the bootstrap code as follows:
|
||||
|
||||
|
@ -729,7 +763,7 @@ Open the `src/main.ts` and modify the bootstrap code as follows:
|
|||
|
||||
|
||||
|
||||
Notice that it waits for the `getTranslationProviders` promise to resolve before
|
||||
Notice that it waits for the `getTranslationProviders()` promise to resolve before
|
||||
bootstrapping the app.
|
||||
|
||||
The app is now _internationalized_ for English and Spanish and there is a clear path for adding
|
||||
|
@ -740,40 +774,40 @@ more languages.
|
|||
|
||||
|
||||
|
||||
### _Internationalize_ with the AOT compiler
|
||||
### _Internationalization_ with the AOT compiler
|
||||
|
||||
The JIT compiler translates the application into the target language
|
||||
The JIT compiler translates the application into the target language
|
||||
while compiling dynamically in the browser.
|
||||
That's flexible but may not be fast enough for your users.
|
||||
That's flexible but may not be fast enough for your users.
|
||||
|
||||
The AOT (_Ahead-of-Time_) compiler is part of a build process that
|
||||
The AOT (_Ahead-of-Time_) compiler is part of a build process that
|
||||
produces a small, fast, ready-to-run application package.
|
||||
When you internationalize with the AOT compiler, you pre-build
|
||||
When you internationalize with the AOT compiler, you pre-build
|
||||
a separate application package for each
|
||||
language. Then in the host web page (`index.html`),
|
||||
language. Then in the host web page, in this case `index.html`,
|
||||
you determine which language the user needs
|
||||
and serve the appropriate application package.
|
||||
|
||||
This cookbook doesn't cover how to build multiple application packages and
|
||||
This cookbook doesn't cover how to build multiple application packages and
|
||||
serve them according to the user's language preference.
|
||||
It does explain the few steps necessary to tell the AOT compiler to apply a translations file.
|
||||
|
||||
Internationalization with the AOT compiler requires
|
||||
some setup specifically for AOT compilation.
|
||||
Start with the application project as shown
|
||||
Internationalization with the AOT compiler requires
|
||||
some setup specifically for AOT compilation.
|
||||
Start with the application project as shown
|
||||
[just before merging the translation file](guide/i18n#app-pre-translation)
|
||||
and refer to the [AOT cookbook](guide/aot-compiler) to make it _AOT-ready_.
|
||||
|
||||
Next, issue an `ngc` compile command for each supported language (including English).
|
||||
Next, issue an `ngc` compile command for each supported language, including English.
|
||||
The result is a separate version of the application for each language.
|
||||
|
||||
Tell AOT how to translate by adding three options to the `ngc` command:
|
||||
|
||||
* `--i18nFile`: the path to the translation file
|
||||
* `--locale`: the name of the locale
|
||||
* `--i18nFormat`: the format of the localization file
|
||||
* `--i18nFile`: the path to the translation file.
|
||||
* `--locale`: the name of the locale.
|
||||
* `--i18nFormat`: the format of the localization file.
|
||||
|
||||
For this sample, the Spanish language command would be
|
||||
For this sample, the Spanish language command would be:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
./node_modules/.bin/ngc --i18nFile=./locale/messages.es.xlf --locale=es --i18nFormat=xlf
|
||||
|
@ -803,19 +837,19 @@ Windows users may have to quote the command:
|
|||
|
||||
|
||||
## Translation file maintenance and _id_ changes
|
||||
|
||||
As the application evolves, you will change the _i18n_ markup
|
||||
|
||||
As the application evolves, you will change the _i18n_ markup
|
||||
and re-run the `ng-xi18n` extraction tool many times.
|
||||
The _new_ markup that you add is not a problem;
|
||||
but _most_ changes to _existing_ markup trigger
|
||||
generation of _new_ `id`s for the affected translation units.
|
||||
but _most_ changes to existing markup trigger
|
||||
generation of new `id`s for the affected translation units.
|
||||
|
||||
After an `id` changes, the translation files are no longer in-sync.
|
||||
**All translated versions of the application will fail** during re-compilation.
|
||||
The error messages identify the old `id`s that are no longer valid but
|
||||
After an `id` changes, the translation files are no longer in sync.
|
||||
**All translated versions of the application will fail** during re-compilation.
|
||||
The error messages identify the old `id`s that are no longer valid but
|
||||
they don't tell you what the new `id`s should be.
|
||||
|
||||
**Commit all translation message files to source control**,
|
||||
**Commit all translation message files to source control**,
|
||||
especially the English source `messages.xlf`.
|
||||
The difference between the old and the new `messages.xlf` file
|
||||
help you find and update `id` changes across your translation files.
|
|
@ -156,7 +156,7 @@ Then you'll learn about the [Angular form classes](guide/reactive-forms#essentia
|
|||
|
||||
## Setup
|
||||
|
||||
Follow the steps in the [_Setup_ guide](setup "Setup guide")
|
||||
Follow the steps in the [_Setup_ guide](guide/setup "Setup guide")
|
||||
for creating a new project folder (perhaps called `reactive-forms`)
|
||||
based on the _QuickStart seed_.
|
||||
|
||||
|
@ -989,7 +989,7 @@ by binding to its `hero` input property.
|
|||
|
||||
In this approach, the value of `hero` in the `HeroDetailComponent` changes
|
||||
every time the user selects a new hero.
|
||||
You should call _setValue_ in the [ngOnChanges](guide/lifecyle-hooks#onchanges)
|
||||
You should call _setValue_ in the [ngOnChanges](guide/lifecycle-hooks#onchanges)
|
||||
hook, which Angular calls whenever the input `hero` property changes
|
||||
as the following steps demonstrate.
|
||||
|
||||
|
|
|
@ -10,10 +10,10 @@ Setting the document or window title using the Title service.
|
|||
{@a top}
|
||||
|
||||
|
||||
Our app should be able to make the browser title bar say whatever we want it to say.
|
||||
Your app should be able to make the browser title bar say whatever you want it to say.
|
||||
This cookbook explains how to do it.
|
||||
|
||||
**See the <live-example name="cb-set-document-title"></live-example>**.
|
||||
See the <live-example name="cb-set-document-title"></live-example>.
|
||||
|
||||
|
||||
<table>
|
||||
|
@ -49,11 +49,11 @@ The obvious approach is to bind a property of the component to the HTML `<title>
|
|||
|
||||
|
||||
Sorry but that won't work.
|
||||
The root component of our application is an element contained within the `<body>` tag.
|
||||
The root component of the application is an element contained within the `<body>` tag.
|
||||
The HTML `<title>` is in the document `<head>`, outside the body, making it inaccessible to Angular data binding.
|
||||
|
||||
We could grab the browser `document` object and set the title manually.
|
||||
That's dirty and undermines our chances of running the app outside of a browser someday.
|
||||
You could grab the browser `document` object and set the title manually.
|
||||
That's dirty and undermines your chances of running the app outside of a browser someday.
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
|
@ -74,10 +74,10 @@ Fortunately, Angular bridges the gap by providing a `Title` service as part of t
|
|||
The [Title](api/platform-browser/index/Title-class) service is a simple class that provides an API
|
||||
for getting and setting the current HTML document title:
|
||||
|
||||
* `getTitle() : string` — Gets the title of the current HTML document.
|
||||
* `setTitle( newTitle : string )` — Sets the title of the current HTML document.
|
||||
* `getTitle() : string`—Gets the title of the current HTML document.
|
||||
* `setTitle( newTitle : string )`—Sets the title of the current HTML document.
|
||||
|
||||
Let's inject the `Title` service into the root `AppComponent` and expose a bindable `setTitle` method that calls it:
|
||||
You can inject the `Title` service into the root `AppComponent` and expose a bindable `setTitle` method that calls it:
|
||||
|
||||
|
||||
<code-example path="cb-set-document-title/src/app/app.component.ts" region="class" title="src/app/app.component.ts (class)" linenums="false">
|
||||
|
@ -86,7 +86,7 @@ Let's inject the `Title` service into the root `AppComponent` and expose a binda
|
|||
|
||||
|
||||
|
||||
We bind that method to three anchor tags and, voilà!
|
||||
Bind that method to three anchor tags and voilà!
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src="assets/images/cookbooks/set-document-title/set-title-anim.gif" alt="Set title"></img>
|
||||
|
@ -94,7 +94,7 @@ We bind that method to three anchor tags and, voilà!
|
|||
|
||||
|
||||
|
||||
Here's the complete solution
|
||||
Here's the complete solution:
|
||||
|
||||
|
||||
<code-tabs>
|
||||
|
@ -116,17 +116,18 @@ Here's the complete solution
|
|||
|
||||
|
||||
|
||||
## Why we provide the *Title* service in *bootstrap*
|
||||
## Why provide the *Title* service in *bootstrap*
|
||||
|
||||
We generally recommended providing application-wide services in the root application component, `AppComponent`.
|
||||
Generally you want to provide application-wide services in the root application component, `AppComponent`.
|
||||
|
||||
Here we recommend registering the title service during bootstrapping,
|
||||
a location we reserve for configuring the runtime Angular environment.
|
||||
This cookbook recommends registering the title service during bootstrapping,
|
||||
a location you reserve for configuring the runtime Angular environment.
|
||||
|
||||
That's exactly what we're doing.
|
||||
That's exactly what you're doing.
|
||||
The `Title` service is part of the Angular *browser platform*.
|
||||
If we bootstrap our application into a different platform,
|
||||
we'll have to provide a different `Title` service that understands the concept of a "document title" for that specific platform.
|
||||
Ideally the application itself neither knows nor cares about the runtime environment.
|
||||
If you bootstrap your application into a different platform,
|
||||
you'll have to provide a different `Title` service that understands
|
||||
the concept of a "document title" for that specific platform.
|
||||
Ideally, the application itself neither knows nor cares about the runtime environment.
|
||||
|
||||
[Back to top](guide/set-document-title#top)
|
|
@ -32,10 +32,11 @@ how you can write your own structural directives to do the same thing.
|
|||
|
||||
* [Inside the *NgSwitch* directives](guide/structural-directives#ngSwitch)
|
||||
* [Prefer the (*) prefix](guide/structural-directives#prefer-asterisk)
|
||||
* [The <template> element](guide/structural-directives#template)
|
||||
* [The <ng-template> element](guide/structural-directives#template)
|
||||
* [Group sibling elements with <ng-container>](guide/structural-directives#ng-container)
|
||||
* [Write a structural directive](guide/structural-directives#unless)
|
||||
|
||||
|
||||
Try the <live-example></live-example>.
|
||||
|
||||
|
||||
|
@ -67,7 +68,7 @@ No brackets. No parentheses. Just `*ngIf` set to a string.
|
|||
You'll learn in this guide that the [asterisk (*) is a convenience notation](guide/structural-directives#asterisk)
|
||||
and the string is a [_microsyntax_](guide/structural-directives#microsyntax) rather than the usual
|
||||
[template expression](guide/template-syntax#template-expressions).
|
||||
Angular desugars this notation into a marked-up `<template>` that surrounds the
|
||||
Angular desugars this notation into a marked-up `<ng-template>` that surrounds the
|
||||
host element and its descendents.
|
||||
Each structural directive does something different with that template.
|
||||
|
||||
|
@ -163,7 +164,7 @@ Confirm that fact using browser developer tools to inspect the DOM.
|
|||
|
||||
|
||||
The top paragraph is in the DOM. The bottom, disused paragraph is not;
|
||||
in its place is a comment about "template bindings" (more about that [later](guide/structural-directives#asterisk)).
|
||||
in its place is a comment about "bindings" (more about that [later](guide/structural-directives#asterisk)).
|
||||
|
||||
When the condition is false, `NgIf` removes its host element from the DOM,
|
||||
detaches it from DOM events (the attachments that it made),
|
||||
|
@ -243,7 +244,7 @@ First, it translates the `*ngIf="..."` into a template _attribute_, `template="n
|
|||
|
||||
|
||||
|
||||
Then it translates the template _attribute_ into a template _element_, wrapped around the host element, like this.
|
||||
Then it translates the template _attribute_ into a `<ng-template>` _element_, wrapped around the host element, like this.
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (ngif-template)" region="ngif-template">
|
||||
|
@ -252,8 +253,8 @@ Then it translates the template _attribute_ into a template _element_, wrapped a
|
|||
|
||||
|
||||
|
||||
* The `*ngIf` directive moved to the `<template>` element where it became a property binding,`[ngIf]`.
|
||||
* The rest of the `<div>`, including its class attribute, moved inside the `<template>` element.
|
||||
* The `*ngIf` directive moved to the `<ng-template>` element where it became a property binding,`[ngIf]`.
|
||||
* The rest of the `<div>`, including its class attribute, moved inside the `<ng-template>` element.
|
||||
|
||||
None of these forms are actually rendered.
|
||||
Only the finished product ends up in the DOM.
|
||||
|
@ -265,8 +266,8 @@ Only the finished product ends up in the DOM.
|
|||
|
||||
|
||||
|
||||
Angular consumed the `<template>` content during its actual rendering and
|
||||
replaced the `<template>` with a diagnostic comment.
|
||||
Angular consumed the `<ng-template>` content during its actual rendering and
|
||||
replaced the `<ng-template>` with a diagnostic comment.
|
||||
|
||||
The [`NgFor`](guide/structural-directives#ngFor) and [`NgSwitch...`](guide/structural-directives#ngSwitch) directives follow the same pattern.
|
||||
|
||||
|
@ -278,7 +279,7 @@ The [`NgFor`](guide/structural-directives#ngFor) and [`NgSwitch...`](guide/struc
|
|||
## Inside _*ngFor_
|
||||
|
||||
Angular transforms the `*ngFor` in similar fashion from asterisk (*) syntax through
|
||||
template _attribute_ to template _element_.
|
||||
template _attribute_ to `<ng-template>` _element_.
|
||||
|
||||
Here's a full-featured application of `NgFor`, written all three ways:
|
||||
|
||||
|
@ -301,7 +302,7 @@ You enable these features in the string assigned to `ngFor`, which you write in
|
|||
|
||||
|
||||
Everything _outside_ the `ngFor` string stays with the host element
|
||||
(the `<div>`) as it moves inside the `<template>`.
|
||||
(the `<div>`) as it moves inside the `<ng-template>`.
|
||||
In this example, the `[ngClass]="odd"` stays on the `<div>`.
|
||||
|
||||
|
||||
|
@ -315,7 +316,7 @@ In this example, the `[ngClass]="odd"` stays on the `<div>`.
|
|||
### Microsyntax
|
||||
|
||||
The Angular microsyntax lets you configure a directive in a compact, friendly string.
|
||||
The microsyntax parser translates that string into attributes on the `<template>`:
|
||||
The microsyntax parser translates that string into attributes on the `<ng-template>`:
|
||||
|
||||
* The `let` keyword declares a [_template input variable_](guide/structural-directives#template-input-variable)
|
||||
that you reference within the template. The input variables in this example are `hero`, `i`, and `odd`.
|
||||
|
@ -342,7 +343,10 @@ which `NgFor` has initialized with the hero for the current iteration.
|
|||
describes additional `NgFor` directive properties and context properties.
|
||||
|
||||
These microsyntax mechanisms are available to you when you write your own structural directives.
|
||||
Studying the source code for `NgIf` and `NgFor` is a great way to learn more.
|
||||
Studying the
|
||||
[source code for `NgIf`](https://github.com/angular/angular/blob/master/packages/common/src/directives/ng_if.ts "Source: NgIf")
|
||||
and [`NgFor`](https://github.com/angular/angular/blob/master/packages/common/src/directives/ng_for_of.ts "Source: NgFor")
|
||||
is a great way to learn more.
|
||||
|
||||
|
||||
|
||||
|
@ -446,7 +450,7 @@ can be desugared into the template _attribute_ form.
|
|||
|
||||
|
||||
|
||||
That, in turn, can be desugared into the `<template>` element form.
|
||||
That, in turn, can be desugared into the `<ng-template>` element form.
|
||||
|
||||
|
||||
<code-example path="structural-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (ngswitch-template)" region="ngswitch-template">
|
||||
|
@ -465,22 +469,21 @@ Use [<ng-container>](guide/structural-directives#ng-container) when there'
|
|||
to host the directive.
|
||||
|
||||
While there's rarely a good reason to apply a structural directive in template _attribute_ or _element_ form,
|
||||
it's still important to know that Angular creates a `<template>` and to understand how it works.
|
||||
You'll refer to the `<template>` when you [write your own structural directive](guide/structural-directives#unless).
|
||||
it's still important to know that Angular creates a `<ng-template>` and to understand how it works.
|
||||
You'll refer to the `<ng-template>` when you [write your own structural directive](guide/structural-directives#unless).
|
||||
|
||||
|
||||
{@a template}
|
||||
|
||||
|
||||
|
||||
## The *<template>*
|
||||
## The *<ng-template>*
|
||||
|
||||
The <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template" target="_blank" title="MDN: Template Tag">HTML 5 <template></a>
|
||||
is a formula for rendering HTML.
|
||||
The <ng-template> is an Angular element for rendering HTML.
|
||||
It is never displayed directly.
|
||||
In fact, before rendering the view, Angular _replaces_ the `<template>` and its contents with a comment.
|
||||
In fact, before rendering the view, Angular _replaces_ the `<ng-template>` and its contents with a comment.
|
||||
|
||||
If there is no structural directive and you merely wrap some elements in a `<template>`,
|
||||
If there is no structural directive and you merely wrap some elements in a `<ng-template>`,
|
||||
those elements disappear.
|
||||
That's the fate of the middle "Hip!" in the phrase "Hip! Hip! Hooray!".
|
||||
|
||||
|
@ -500,7 +503,7 @@ Angular erases the middle "Hip!", leaving the cheer a bit less enthusiastic.
|
|||
|
||||
|
||||
|
||||
A structural directive puts a `<template>` to work
|
||||
A structural directive puts a `<ng-template>` to work
|
||||
as you'll see when you [write your own structural directive](guide/structural-directives#unless).
|
||||
|
||||
|
||||
|
@ -708,11 +711,11 @@ Angular's own directives do not.
|
|||
|
||||
A simple structural directive like this one creates an
|
||||
[_embedded view_](api/core/index/EmbeddedViewRef-class "API: EmbeddedViewRef")
|
||||
from the Angular-generated `<template>` and inserts that view in a
|
||||
from the Angular-generated `<ng-template>` and inserts that view in a
|
||||
[_view container_](api/core/index/ViewContainerRef-class "API: ViewContainerRef")
|
||||
adjacent to the directive's original `<p>` host element.
|
||||
|
||||
You'll acquire the `<template>` contents with a
|
||||
You'll acquire the `<ng-template>` contents with a
|
||||
[`TemplateRef`](api/core/index/TemplateRef-class "API: TemplateRef")
|
||||
and access the _view container_ through a
|
||||
[`ViewContainerRef`](api/core/index/ViewContainerRef-class "API: ViewContainerRef").
|
||||
|
@ -839,7 +842,7 @@ You learned
|
|||
|
||||
* that structural directives manipulate HTML layout.
|
||||
* to use [`<ng-container>`](guide/structural-directives#ngcontainer) as a grouping element when there is no suitable host element.
|
||||
* that the Angular desugars [asterisk (*) syntax](guide/structural-directives#asterisk) into a `<template>`.
|
||||
* that the Angular desugars [asterisk (*) syntax](guide/structural-directives#asterisk) into a `<ng-template>`.
|
||||
* how that works for the `NgIf`, `NgFor` and `NgSwitch` built-in directives.
|
||||
* about the [_microsyntax_](guide/structural-directives#microsyntax) that expands into a [`<template>`](guide/structural-directives#template).
|
||||
* about the [_microsyntax_](guide/structural-directives#microsyntax) that expands into a [`<ng-template>`](guide/structural-directives#template).
|
||||
* to write a [custom structural directive](guide/structural-directives#unless), `UnlessDirective`.
|
|
@ -3028,11 +3028,11 @@ module are referenced across the entire application.
|
|||
|
||||
|
||||
|
||||
<div class="s-rule do">
|
||||
<div class="s-rule avoid">
|
||||
|
||||
|
||||
|
||||
**Do** not provide services in shared modules. Services are usually
|
||||
**Avoid** providing services in shared modules. Services are usually
|
||||
singletons that are provided once for the entire application or
|
||||
in a particular feature module.
|
||||
|
||||
|
@ -3949,7 +3949,7 @@ in those editors that support it; it won't help with CSS styles.
|
|||
|
||||
|
||||
|
||||
<div class="s-rule do">
|
||||
<div class="s-rule consider">
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -2028,7 +2028,7 @@ The string `"let hero of heroes"` means:
|
|||
> *Take each hero in the `heroes` array, store it in the local `hero` looping variable, and
|
||||
make it available to the templated HTML for each iteration.*
|
||||
|
||||
Angular translates this instruction into a `<template>` around the host element,
|
||||
Angular translates this instruction into a `<ng-template>` around the host element,
|
||||
then uses this template repeatedly to create a new set of elements and bindings for each `hero`
|
||||
in the list.
|
||||
|
||||
|
|
|
@ -157,7 +157,7 @@ Add these properties near the top of the `app.component.ts` file, just below the
|
|||
|
||||
|
||||
|
||||
In the `Hero` class, refactor the component's `hero` property to be of type `Hero`,
|
||||
In the `AppComponent` class, refactor the component's `hero` property to be of type `Hero`,
|
||||
then initialize it with an `id` of `1` and the name `Windstorm`.
|
||||
|
||||
|
||||
|
|
|
@ -163,7 +163,7 @@ Now you can fill the template with hero names.
|
|||
|
||||
### List heroes with ngFor
|
||||
|
||||
The goal is to bind the array of `heroes` in the component to the template, iterate over them,
|
||||
The goal is to bind the array of heroes in the component to the template, iterate over them,
|
||||
and display them individually.
|
||||
|
||||
Modify the `<li>` tag by adding the built-in directive `*ngFor`.
|
||||
|
@ -255,7 +255,7 @@ In this case, the _master_ is the heroes list and the _detail_ is the selected h
|
|||
Next you'll connect the master to the detail through a `selectedHero` component property,
|
||||
which is bound to a click event.
|
||||
|
||||
### Add a click event
|
||||
### Handle click events
|
||||
Add a click event binding to the `<li>` like this:
|
||||
|
||||
|
||||
|
@ -309,7 +309,7 @@ Add an `onSelect()` method that sets the `selectedHero` property to the `hero` t
|
|||
|
||||
|
||||
The template still refers to the old `hero` property.
|
||||
Bind to the new selectedHero property instead as follows:
|
||||
Bind to the new `selectedHero` property instead as follows:
|
||||
|
||||
|
||||
|
||||
|
@ -321,7 +321,8 @@ Bind to the new selectedHero property instead as follows:
|
|||
|
||||
### Hide the empty detail with ngIf
|
||||
|
||||
When the app loads, the `selectedHero` is undefined and won't be defined until you click a hero's name.
|
||||
When the app loads, `selectedHero` is undefined.
|
||||
The selected hero is initialized when the user clicks a hero's name.
|
||||
Angular can't display properties of the undefined `selectedHero` and throws the following error,
|
||||
visible in the browser's console:
|
||||
|
||||
|
@ -360,7 +361,7 @@ Don't forget the asterisk (`*`) in front of `ngIf`.
|
|||
The app no longer fails and the list of names displays again in the browser.
|
||||
|
||||
|
||||
When there is no `selectedHero`, the `ngIf` directive removes the hero detail HTML from the DOM.
|
||||
When there is no selected hero, the `ngIf` directive removes the hero detail HTML from the DOM.
|
||||
There are no hero detail elements or bindings to worry about.
|
||||
|
||||
When the user picks a hero, `selectedHero` becomes defined and
|
||||
|
@ -461,5 +462,5 @@ Your app should look like this <live-example></live-example>.
|
|||
|
||||
## The road ahead
|
||||
You've expanded the Tour of Heroes app, but it's far from complete.
|
||||
You can't put the entire app into a single component.
|
||||
In the [next page](tutorial/toh-pt3), you'll split the app into sub-components and make them work together.
|
||||
An app shouldn't be one monolithic component.
|
||||
In the [next page](tutorial/toh-pt3), you'll split the app into subcomponents and make them work together.
|
|
@ -276,7 +276,7 @@ This module declares only the two application components, `AppComponent` and `He
|
|||
|
||||
|
||||
|
||||
Read more about Angular modules in the [NgModules](guide/ngmodule "Angular Modules (NgModule) guide.
|
||||
Read more about Angular modules in the [NgModules](guide/ngmodule "Angular Modules (NgModule)") guide.
|
||||
|
||||
|
||||
</div>
|
||||
|
@ -305,7 +305,7 @@ Coordinate the master `AppComponent` with the `HeroDetailComponent`
|
|||
by binding the `selectedHero` property of the `AppComponent`
|
||||
to the `hero` property of the `HeroDetailComponent`.
|
||||
|
||||
<code-example path="toh-3/app/app.component.1.html" region="hero-detail-binding" title="app.component.html (excerpt)" linenums="false">
|
||||
<code-example path="toh-3/app/app.component.1.html" region="hero-detail-binding" title="app.component.ts (excerpt)" linenums="false">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
|
|
@ -1066,8 +1066,8 @@ by telling the router where to go.
|
|||
|
||||
This approach requires the following changes to the component class:
|
||||
|
||||
1. Import the `router` from the Angular router library.
|
||||
1. Inject the `router` in the constructor, along with the `HeroService`.
|
||||
1. Import the `Router` from the Angular router library.
|
||||
1. Inject the `Router` in the constructor, along with the `HeroService`.
|
||||
1. Implement `gotoDetail()` by calling the router `navigate()` method.
|
||||
|
||||
|
||||
|
@ -1223,7 +1223,7 @@ Look at the app now. The dashboard, heroes, and navigation links are styled.
|
|||
|
||||
|
||||
<figure class='image-display'>
|
||||
<img src='assets/images/devguide/toh/dashboard-top-heroes.png' alt="View navigations"></img>
|
||||
<img src='assets/images/devguide/toh/heroes-dashboard-1.png' alt="View navigations"></img>
|
||||
</figure>
|
||||
|
||||
|
||||
|
|
|
@ -503,8 +503,9 @@ The calling component can easily consume a single result in the form of a Promis
|
|||
But requests aren't always done only once.
|
||||
You may start one request,
|
||||
cancel it, and make a different request before the server has responded to the first request.
|
||||
A *request-cancel-new-request* sequence is difficult to implement with *Promises*, but
|
||||
easy with *Observables*.
|
||||
|
||||
A *request-cancel-new-request* sequence is difficult to implement with `Promise`s, but
|
||||
easy with `Observable`s.
|
||||
|
||||
### Add the ability to search by name
|
||||
You're going to add a *hero search* feature to the Tour of Heroes.
|
||||
|
|
|
@ -3,6 +3,8 @@ var stylesRegex = /styleUrls *:(\s*\[[^\]]*?\])/g;
|
|||
var stringRegex = /(['`"])((?:[^\\]\\\1|.)*?)\1/g;
|
||||
|
||||
module.exports.translate = function(load){
|
||||
if (load.source.indexOf('moduleId') != -1) return load;
|
||||
|
||||
var url = document.createElement('a');
|
||||
url.href = load.address;
|
||||
|
||||
|
@ -15,11 +17,13 @@ module.exports.translate = function(load){
|
|||
baseHref.href = this.baseURL;
|
||||
baseHref = baseHref.pathname;
|
||||
|
||||
basePath = basePath.replace(baseHref, '');
|
||||
if (!baseHref.startsWith('/base/')) { // it is not karma
|
||||
basePath = basePath.replace(baseHref, '');
|
||||
}
|
||||
|
||||
load.source = load.source
|
||||
.replace(templateUrlRegex, function(match, quote, url){
|
||||
let resolvedUrl = url;
|
||||
var resolvedUrl = url;
|
||||
|
||||
if (url.startsWith('.')) {
|
||||
resolvedUrl = basePath + url.substr(1);
|
||||
|
|
Loading…
Reference in New Issue