Merge remote-tracking branch 'origin/master'
# Conflicts: # public/docs/ts/latest/cookbook/_data.json # public/docs/ts/latest/guide/_data.json # public/docs/ts/latest/guide/attribute-directives.jade # public/docs/ts/latest/guide/component-styles.jade # public/docs/ts/latest/guide/hierarchical-dependency-injection.jade # public/docs/ts/latest/guide/style-guide.jade # public/docs/ts/latest/tutorial/toh-pt3.jade # public/docs/ts/latest/tutorial/toh-pt4.jade # public/docs/ts/latest/tutorial/toh-pt5.jade
This commit is contained in:
commit
ae3b205c77
109
gulpfile.js
109
gulpfile.js
|
@ -86,21 +86,46 @@ var _exampleBoilerplateFiles = [
|
|||
|
||||
var _exampleDartWebBoilerPlateFiles = ['styles.css'];
|
||||
|
||||
// --filter may be passed in to filter/select _example app subdir names
|
||||
// i.e. gulp run-e2e-tests --filter=foo ; would select all example apps with
|
||||
// 'foo' in their folder names.
|
||||
/**
|
||||
* Run Protractor End-to-End Tests for Doc Samples
|
||||
*
|
||||
* Flags
|
||||
* --filter to filter/select _example app subdir names
|
||||
* e.g. gulp run-e2e-tests --filter=foo // all example apps with 'foo' in their folder names.
|
||||
*
|
||||
* --fast by-passes the npm install and webdriver update
|
||||
* Use it for repeated test runs (but not the FIRST run)
|
||||
* e.g. gulp run-e2e-tests --fast
|
||||
*
|
||||
* --lang to filter by code language
|
||||
* e.g. gulp run-e2e-tests --lang=ts // only TypeScript apps
|
||||
* default is (ts|js)
|
||||
* all means (ts|js|dart)
|
||||
*/
|
||||
gulp.task('run-e2e-tests', function() {
|
||||
var spawnInfo = spawnExt('npm', ['install'], { cwd: EXAMPLES_PATH});
|
||||
return spawnInfo.promise.then(function() {
|
||||
copyExampleBoilerplate();
|
||||
var exePath = path.join(process.cwd(), "./node_modules/.bin/");
|
||||
spawnInfo = spawnExt('webdriver-manager', ['update'], {cwd: exePath});
|
||||
return spawnInfo.promise;
|
||||
}).then(function() {
|
||||
|
||||
var exePath = path.join(process.cwd(), "./node_modules/.bin/");
|
||||
|
||||
var promise;
|
||||
if (argv.fast) {
|
||||
// fast; skip all setup
|
||||
promise = Promise.resolve(true);
|
||||
} else {
|
||||
// Not 'fast'; do full setup
|
||||
var spawnInfo = spawnExt('npm', ['install'], { cwd: EXAMPLES_PATH});
|
||||
promise = spawnInfo.promise.then(function() {
|
||||
copyExampleBoilerplate();
|
||||
spawnInfo = spawnExt('webdriver-manager', ['update'], {cwd: exePath});
|
||||
return spawnInfo.promise;
|
||||
});
|
||||
}
|
||||
|
||||
promise.then(function() {
|
||||
return findAndRunE2eTests(argv.filter);
|
||||
}).then(function(status) {
|
||||
reportStatus(status);
|
||||
}).fail(function(e) {
|
||||
}).catch(function(e) {
|
||||
gutil.log(e);
|
||||
return e;
|
||||
});
|
||||
});
|
||||
|
@ -114,10 +139,13 @@ function findAndRunE2eTests(filter) {
|
|||
var startTime = new Date().getTime();
|
||||
// create an output file with header.
|
||||
var outputFile = path.join(process.cwd(), 'protractor-results.txt');
|
||||
var header = "Protractor example results for " + lang + " on " + (new Date()).toLocaleString() + "\n\n";
|
||||
if (filter) {
|
||||
header += ' Filter: ' + filter.toString() + '\n\n';
|
||||
}
|
||||
|
||||
var header = `Doc Sample Protractor Results for ${lang} on ${new Date().toLocaleString()}\n`;
|
||||
header += argv.fast ?
|
||||
' Fast Mode (--fast): no npm install, webdriver update, or boilerplate copy\n' :
|
||||
' Slow Mode: npm install, webdriver update, and boilerplate copy\n';
|
||||
header += ` Filter: ${filter ? filter : 'All tests'}\n\n`;
|
||||
|
||||
fs.writeFileSync(outputFile, header);
|
||||
|
||||
// create an array of combos where each
|
||||
|
@ -175,25 +203,32 @@ function runE2eTsTests(appDir, protractorConfigFilename, outputFile) {
|
|||
}
|
||||
|
||||
function runProtractor(prepPromise, appDir, appRunSpawnInfo, protractorConfigFilename, outputFile) {
|
||||
return prepPromise.then(function (data) {
|
||||
// start protractor
|
||||
var pcFilename = path.resolve(protractorConfigFilename); // need to resolve because we are going to be running from a different dir
|
||||
var exePath = path.join(process.cwd(), "./node_modules/.bin/");
|
||||
var spawnInfo = spawnExt('protractor',
|
||||
[ pcFilename, '--params.appDir=' + appDir, '--params.outputFile=' + outputFile], { cwd: exePath });
|
||||
return spawnInfo.promise;
|
||||
}).then(function(data) {
|
||||
// kill the app now that protractor has completed.
|
||||
// Ugh... proc.kill does not work properly on windows with child processes.
|
||||
// appRun.proc.kill();
|
||||
treeKill(appRunSpawnInfo.proc.pid);
|
||||
return !data;
|
||||
}).fail(function(err) {
|
||||
// Ugh... proc.kill does not work properly on windows with child processes.
|
||||
// appRun.proc.kill();
|
||||
treeKill(appRunSpawnInfo.proc.pid);
|
||||
return false;
|
||||
});
|
||||
return prepPromise
|
||||
.catch(function(){
|
||||
var emsg = `AppDir failed during compile: ${appDir}\n\n`;
|
||||
gutil.log(emsg);
|
||||
fs.appendFileSync(outputFile, emsg);
|
||||
return Promise.reject(emsg);
|
||||
})
|
||||
.then(function (data) {
|
||||
// start protractor
|
||||
var pcFilename = path.resolve(protractorConfigFilename); // need to resolve because we are going to be running from a different dir
|
||||
var exePath = path.join(process.cwd(), "./node_modules/.bin/");
|
||||
var spawnInfo = spawnExt('protractor',
|
||||
[ pcFilename, '--params.appDir=' + appDir, '--params.outputFile=' + outputFile], { cwd: exePath });
|
||||
return spawnInfo.promise
|
||||
})
|
||||
.then(
|
||||
function() { return finish(true);},
|
||||
function() { return finish(false);}
|
||||
)
|
||||
|
||||
function finish(ok){
|
||||
// Ugh... proc.kill does not work properly on windows with child processes.
|
||||
// appRun.proc.kill();
|
||||
treeKill(appRunSpawnInfo.proc.pid);
|
||||
return ok;
|
||||
}
|
||||
}
|
||||
|
||||
// start the server in appDir/build/web; then run protractor with the specified
|
||||
|
@ -252,9 +287,11 @@ function spawnExt(command, args, options) {
|
|||
proc.stderr.on('data', function (data) {
|
||||
gutil.log(data.toString());
|
||||
});
|
||||
proc.on('close', function (data) {
|
||||
proc.on('close', function (returnCode) {
|
||||
gutil.log('completed: ' + descr);
|
||||
deferred.resolve(data);
|
||||
// Many tasks (e.g., tsc) complete but are actually errors;
|
||||
// Confirm return code is zero.
|
||||
returnCode === 0 ? deferred.resolve(0) : deferred.reject(returnCode);
|
||||
});
|
||||
proc.on('error', function (data) {
|
||||
gutil.log('completed with error:' + descr);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector:'countdown-timer',
|
||||
selector: 'countdown-timer',
|
||||
template: '<p>{{message}}</p>'
|
||||
})
|
||||
export class CountdownTimerComponent implements OnInit, OnDestroy {
|
||||
|
@ -24,12 +24,12 @@ export class CountdownTimerComponent implements OnInit, OnDestroy {
|
|||
|
||||
private countDown() {
|
||||
this.clearTimer();
|
||||
this.intervalId = setInterval(()=>{
|
||||
this.intervalId = window.setInterval(() => {
|
||||
this.seconds -= 1;
|
||||
if (this.seconds == 0) {
|
||||
this.message = "Blast off!";
|
||||
if (this.seconds === 0) {
|
||||
this.message = 'Blast off!';
|
||||
} else {
|
||||
if (this.seconds < 0) { this.seconds = 10;} // reset
|
||||
if (this.seconds < 0) { this.seconds = 10; } // reset
|
||||
this.message = `T-${this.seconds} seconds and counting`;
|
||||
}
|
||||
}, 1000);
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
describe('Cookbook: component-relative paths', function () {
|
||||
|
||||
function getPageStruct() {
|
||||
return {
|
||||
title: element( by.tagName( 'h1' )),
|
||||
absComp: element( by.css( 'absolute-path div' ) ),
|
||||
relComp: element( by.css( 'relative-path div' ) )
|
||||
}
|
||||
}
|
||||
|
||||
var page;
|
||||
beforeAll(function () {
|
||||
browser.get('');
|
||||
page = getPageStruct();
|
||||
});
|
||||
|
||||
it('should display title of the sample', function () {
|
||||
expect(element(by.tagName('h1')).getText()).toContain('Paths');
|
||||
});
|
||||
|
||||
it('should have absolute-path element', function () {
|
||||
expect(page.absComp.isPresent()).toBe(true, 'no <absolute-path> element');
|
||||
});
|
||||
|
||||
it('should display the absolute path text', function () {
|
||||
expect(page.absComp.getText()).toContain('Absolute');
|
||||
});
|
||||
|
||||
it('should display the component-relative path text', function () {
|
||||
expect(page.relComp.getText()).toContain('Component-relative');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { SomeAbsoluteComponent, SomeRelativeComponent} from './some.component';
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
template:
|
||||
`<h1>Absolute & <i>Component-Relative</i> Paths</h1>
|
||||
<absolute-path></absolute-path>
|
||||
<relative-path></relative-path>
|
||||
`,
|
||||
directives: [SomeAbsoluteComponent, SomeRelativeComponent]
|
||||
})
|
||||
export class AppComponent {}
|
|
@ -0,0 +1,5 @@
|
|||
import { bootstrap } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
bootstrap(AppComponent);
|
|
@ -0,0 +1,22 @@
|
|||
/* #docregion */
|
||||
div.absolute {
|
||||
background: beige;
|
||||
border: 1px solid darkred;
|
||||
color: red;
|
||||
margin: 8px;
|
||||
max-width: 20em;
|
||||
padding: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.relative {
|
||||
background: powderblue;
|
||||
border: 1px solid darkblue;
|
||||
color: Blue;
|
||||
font-style: italic;
|
||||
margin: 8px;
|
||||
max-width: 20em;
|
||||
padding: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<!-- #docregion -->
|
||||
<div class={{class}}>
|
||||
{{type}}<br>{{path}}
|
||||
</div>
|
|
@ -0,0 +1,37 @@
|
|||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
///////// Using Absolute Paths ///////
|
||||
|
||||
// #docregion absolute-config
|
||||
@Component({
|
||||
selector: 'absolute-path',
|
||||
templateUrl: 'app/some.component.html',
|
||||
styleUrls: ['app/some.component.css']
|
||||
})
|
||||
// #enddocregion absolute-config
|
||||
export class SomeAbsoluteComponent {
|
||||
class = 'absolute';
|
||||
type = 'Absolute template & style URLs';
|
||||
path = 'app/path.component.html';
|
||||
}
|
||||
|
||||
///////// Using Relative Paths ///////
|
||||
|
||||
// #docregion relative-config
|
||||
@Component({
|
||||
// #docregion module-id
|
||||
moduleId: module.id,
|
||||
// #enddocregion module-id
|
||||
selector: 'relative-path',
|
||||
templateUrl: 'some.component.html',
|
||||
styleUrls: ['some.component.css']
|
||||
})
|
||||
// #enddocregion relative-config
|
||||
|
||||
export class SomeRelativeComponent {
|
||||
class = 'relative';
|
||||
type = 'Component-relative template & style URLs';
|
||||
path = 'path.component.html';
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<base href="/">
|
||||
|
||||
<title>
|
||||
Component-Relative Paths
|
||||
</title>
|
||||
|
||||
<!-- #docregion style -->
|
||||
<link rel="stylesheet" type="text/css" href="styles.css">
|
||||
<!-- #enddocregion style -->
|
||||
|
||||
<!-- Polyfill(s) for older browsers -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/reflect-metadata/Reflect.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('app').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<my-app>Loading app...</my-app>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"description": "Module-relative Paths",
|
||||
"files": [
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js"
|
||||
],
|
||||
"tags": [ "cookbook" ]
|
||||
}
|
|
@ -8,7 +8,7 @@ import { LocationStrategy,
|
|||
|
||||
import { HeroData } from './hero-data';
|
||||
import { InMemoryBackendService,
|
||||
SEED_DATA } from 'angular2-in-memory-web-api/core';
|
||||
SEED_DATA } from 'angular2-in-memory-web-api';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
|
|
|
@ -1,90 +1,93 @@
|
|||
/*global jasmine, __karma__, window*/
|
||||
(function () {
|
||||
|
||||
// Error.stackTraceLimit = Infinity;
|
||||
|
||||
// /*global jasmine, __karma__, window*/
|
||||
Error.stackTraceLimit = Infinity;
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;
|
||||
|
||||
// Cancel Karma's synchronous start,
|
||||
// we call `__karma__.start()` later, once all the specs are loaded.
|
||||
__karma__.loaded = function () { };
|
||||
__karma__.loaded = function () {
|
||||
};
|
||||
|
||||
// SET THE RUNTIME APPLICATION ROOT HERE
|
||||
var appRoot ='app'; // no trailing slash!
|
||||
|
||||
// RegExp for client application base path within karma (which always starts 'base\')
|
||||
var karmaBase = '^\/base\/'; // RegEx string for base of karma folders
|
||||
var appPackage = 'base/' + appRoot; //e.g., base/app
|
||||
var appRootRe = new RegExp(karmaBase + appRoot + '\/');
|
||||
var onlyAppFilesRe = new RegExp(karmaBase + appRoot + '\/(?!.*\.spec\.js$)([a-z0-9-_\.\/]+)\.js$');
|
||||
|
||||
var moduleNames = [];
|
||||
|
||||
// Configure systemjs packages to use the .js extension for imports from the app folder
|
||||
var packages = {};
|
||||
packages[appPackage] = {
|
||||
defaultExtension: false,
|
||||
format: 'register',
|
||||
map: Object.keys(window.__karma__.files)
|
||||
.filter(onlyAppFiles)
|
||||
// Create local module name mapping to karma file path for app files
|
||||
// with karma's fingerprint in query string, e.g.:
|
||||
// './hero.service': '/base/app/hero.service.js?f4523daf879cfb7310ef6242682ccf10b2041b3e'
|
||||
.reduce(function (pathsMapping, appPath) {
|
||||
var moduleName = appPath.replace(appRootRe, './').replace(/\.js$/, '');
|
||||
pathsMapping[moduleName] = appPath + '?' + window.__karma__.files[appPath];
|
||||
return pathsMapping;
|
||||
}, {})
|
||||
}
|
||||
|
||||
System.config({ packages: packages });
|
||||
|
||||
// Configure Angular for the browser and
|
||||
// with test versions of the platform providers
|
||||
Promise.all([
|
||||
System.import('angular2/testing'),
|
||||
System.import('angular2/platform/testing/browser')
|
||||
])
|
||||
.then(function (results) {
|
||||
var testing = results[0];
|
||||
var browser = results[1];
|
||||
testing.setBaseTestProviders(
|
||||
browser.TEST_BROWSER_PLATFORM_PROVIDERS,
|
||||
browser.TEST_BROWSER_APPLICATION_PROVIDERS);
|
||||
|
||||
// Load all spec files
|
||||
// (e.g. 'base/app/hero.service.spec.js')
|
||||
return Promise.all(
|
||||
Object.keys(window.__karma__.files)
|
||||
.filter(onlySpecFiles)
|
||||
.map(function (moduleName) {
|
||||
moduleNames.push(moduleName);
|
||||
return System.import(moduleName);
|
||||
}));
|
||||
})
|
||||
|
||||
.then(success, fail);
|
||||
|
||||
////// Helpers //////
|
||||
|
||||
function onlyAppFiles(filePath) {
|
||||
return onlyAppFilesRe.test(filePath);
|
||||
function isJsFile(path) {
|
||||
return path.slice(-3) == '.js';
|
||||
}
|
||||
|
||||
function onlySpecFiles(filePath) {
|
||||
return /\.spec\.js$/.test(filePath);
|
||||
function isSpecFile(path) {
|
||||
return /\.spec\.js$/.test(path);
|
||||
}
|
||||
|
||||
function success () {
|
||||
console.log(
|
||||
'Spec files loaded:\n ' +
|
||||
moduleNames.join('\n ') +
|
||||
'\nStarting Jasmine testrunner');
|
||||
__karma__.start();
|
||||
function isBuiltFile(path) {
|
||||
var builtPath = '/base/app/';
|
||||
return isJsFile(path) && (path.substr(0, builtPath.length) == builtPath);
|
||||
}
|
||||
|
||||
function fail(error) {
|
||||
__karma__.error(error.stack || error);
|
||||
var allSpecFiles = Object.keys(window.__karma__.files)
|
||||
.filter(isSpecFile)
|
||||
.filter(isBuiltFile);
|
||||
|
||||
//////////////////////////
|
||||
// Load our SystemJS configuration.
|
||||
|
||||
// map tells the System loader where to look for things
|
||||
var map = {
|
||||
'app': 'app',
|
||||
|
||||
'@angular': 'node_modules/@angular',
|
||||
'angular2-in-memory-web-api': 'node_modules/angular2-in-memory-web-api',
|
||||
'rxjs': 'node_modules/rxjs'
|
||||
};
|
||||
|
||||
// packages tells the System loader how to load when no filename and/or no extension
|
||||
var packages = {
|
||||
'app': { main: 'main.js', defaultExtension: 'js' },
|
||||
'rxjs': { defaultExtension: 'js' },
|
||||
'angular2-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' },
|
||||
};
|
||||
|
||||
var ngPackageNames = [
|
||||
'common',
|
||||
'compiler',
|
||||
'core',
|
||||
'http',
|
||||
'platform-browser',
|
||||
'platform-browser-dynamic',
|
||||
'router',
|
||||
'router-deprecated',
|
||||
'upgrade',
|
||||
];
|
||||
|
||||
// Add package entries for angular packages
|
||||
ngPackageNames.forEach(function(pkgName) {
|
||||
|
||||
// Bundled (~40 requests): DOESN'T WORK IN KARMA OR WALLABY (YET?)
|
||||
//packages['@angular/'+pkgName] = { main: pkgName + '.umd.js', defaultExtension: 'js' };
|
||||
|
||||
// Individual files (~300 requests):
|
||||
packages['@angular/'+pkgName] = { main: 'index.js', defaultExtension: 'js' };
|
||||
});
|
||||
|
||||
var config = {
|
||||
baseURL: '/base',
|
||||
map: map,
|
||||
packages: packages
|
||||
}
|
||||
|
||||
})();
|
||||
System.config(config);
|
||||
//////////////
|
||||
|
||||
Promise.all([
|
||||
System.import('@angular/core/testing'),
|
||||
System.import('@angular/platform-browser-dynamic/testing')
|
||||
]).then(function (providers) {
|
||||
var testing = providers[0];
|
||||
var testingBrowser = providers[1];
|
||||
|
||||
testing.setBaseTestProviders(
|
||||
testingBrowser.TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
|
||||
testingBrowser.TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS);
|
||||
|
||||
}).then(function() {
|
||||
// Finally, load all spec files.
|
||||
// This will run the tests directly.
|
||||
return Promise.all(
|
||||
allSpecFiles.map(function (moduleName) {
|
||||
return System.import(moduleName);
|
||||
}));
|
||||
}).then(__karma__.start, __karma__.error);
|
||||
|
|
|
@ -21,34 +21,30 @@ module.exports = function(config) {
|
|||
}
|
||||
},
|
||||
files: [
|
||||
// Polyfills.
|
||||
'node_modules/code-js/client/shim.min.js',
|
||||
// System.js for module loading
|
||||
'node_modules/systemjs/dist/system.src.js',
|
||||
|
||||
// Zone.js dependencies
|
||||
// Note - do not include zone.js itself here, it is already
|
||||
// included in angular2-polyfills
|
||||
// Polyfills
|
||||
'node_modules/core-js/client/shim.js',
|
||||
|
||||
// Reflect and Zone.js
|
||||
'node_modules/reflect-metadata/Reflect.js',
|
||||
'node_modules/zone.js/dist/zone.js',
|
||||
'node_modules/zone.js/dist/jasmine-patch.js',
|
||||
'node_modules/zone.js/dist/async-test.js',
|
||||
'node_modules/zone.js/dist/fake-async-test.js',
|
||||
|
||||
{ pattern: 'node_modules/reflect-metadata/Reflect.js', included: true, watched: false },
|
||||
{ pattern: 'https://code.angularjs.org/tools/system.js', included: true, watched: false },
|
||||
|
||||
// RxJs.
|
||||
{ pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false },
|
||||
{ pattern: 'node_modules/rxjs/**/*.js.map', included: false, watched: false },
|
||||
|
||||
{pattern: 'karma-test-shim.js', included: true, watched: true},
|
||||
{pattern: 'built/test/matchers.js', included: true, watched: true},
|
||||
// Angular 2 itself and the testing library
|
||||
{pattern: 'node_modules/@angular/**/*.js', included: false, watched: false},
|
||||
{pattern: 'node_modules/@angular/**/*.js.map', included: false, watched: false},
|
||||
|
||||
// paths loaded via module imports
|
||||
{pattern: 'built/**/*.js', included: false, watched: true},
|
||||
'karma-test-shim.js',
|
||||
|
||||
{pattern: 'node_modules/@angular/**/*.js', included: false, watched: true},
|
||||
{pattern: 'node_modules/@angular/**/*.js.map', included: false, watched: true},
|
||||
|
||||
// transpiled application & spec code paths to be loaded via module imports
|
||||
// transpiled application & spec code paths loaded via module imports
|
||||
{pattern: appBase + '**/*.js', included: false, watched: true},
|
||||
|
||||
// asset (HTML & CSS) paths loaded via Angular's component compiler
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "HTTP client (server communication)"
|
||||
"docHref": "https://angular.io/docs/dart/latest/guide/server-communication.html"
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
import "package:angular2/core.dart" show Component;
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
import "toh/hero_list_component.dart" show HeroListComponent;
|
||||
import "wiki/wiki_component.dart" show WikiComponent;
|
||||
import "wiki/wiki_smart_component.dart" show WikiSmartComponent;
|
||||
import 'toh/hero_list_component.dart';
|
||||
import 'wiki/wiki_component.dart';
|
||||
import 'wiki/wiki_smart_component.dart';
|
||||
|
||||
@Component(
|
||||
selector: "my-app",
|
||||
selector: 'my-app',
|
||||
template: '''
|
||||
<hero-list></hero-list>
|
||||
<my-wiki></my-wiki>
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
// #docregion
|
||||
int _toInt(id) => id is int ? id : int.parse(id);
|
||||
|
||||
class Hero {
|
||||
final int id;
|
||||
final String name;
|
||||
|
@ -9,6 +11,4 @@ class Hero {
|
|||
new Hero(_toInt(hero['id']), hero['name']);
|
||||
|
||||
Map toJson() => {'id': id, 'name': name};
|
||||
|
||||
static int _toInt(id) => id is int ? id : int.parse(id);
|
||||
}
|
||||
|
|
|
@ -6,12 +6,12 @@ import 'dart:convert';
|
|||
import 'hero.dart';
|
||||
import 'package:angular2/core.dart';
|
||||
import 'package:http/browser_client.dart';
|
||||
import 'package:http/http.dart' show Response;
|
||||
import 'package:http/http.dart';
|
||||
|
||||
@Injectable()
|
||||
class HeroService {
|
||||
// #docregion endpoint, http-get
|
||||
final String _heroesUrl = 'app/heroes'; // URL to web API
|
||||
static const _heroesUrl = 'app/heroes'; // URL to web API
|
||||
// #enddocregion endpoint, http-get
|
||||
final BrowserClient _http;
|
||||
|
||||
|
@ -35,7 +35,7 @@ class HeroService {
|
|||
|
||||
// #docregion addhero, addhero-sig
|
||||
Future<Hero> addHero(String name) async {
|
||||
// #enddocregion addhero-sig
|
||||
// #enddocregion addhero-sig
|
||||
try {
|
||||
final response = await _http.post(_heroesUrl,
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
|
@ -50,8 +50,8 @@ class HeroService {
|
|||
// #docregion extract-data
|
||||
dynamic _extractData(Response res) {
|
||||
var body = JSON.decode(res.body);
|
||||
// TODO: once fixed, https://github.com/adaojunior/http-in-memory-web-api/issues/1
|
||||
// Drop the `?? body` term
|
||||
// TODO: https://github.com/adaojunior/http-in-memory-web-api/issues/1
|
||||
// Once #1 is fixed, drop the `?? body` term:
|
||||
return body['data'] ?? body;
|
||||
}
|
||||
// #enddocregion extract-data
|
||||
|
@ -69,6 +69,6 @@ class HeroService {
|
|||
|
||||
/*
|
||||
// #docregion endpoint-json
|
||||
private _heroesUrl = 'heroes.json'; // URL to JSON file
|
||||
static const _heroesUrl = 'heroes.json'; // URL to JSON file
|
||||
// #enddocregion endpoint-json
|
||||
*/
|
||||
|
|
|
@ -23,4 +23,3 @@ transformers:
|
|||
resolved_identifiers:
|
||||
BrowserClient: 'package:http/browser_client.dart'
|
||||
- dart_to_js_script_rewriter
|
||||
# #enddocregion transformers
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
// #docplaster
|
||||
// #docregion final
|
||||
import 'package:angular2/core.dart' show Provider;
|
||||
import 'package:angular2/core.dart';
|
||||
// #docregion v1
|
||||
import 'package:angular2/platform/browser.dart' show bootstrap;
|
||||
import 'package:angular2/platform/browser.dart';
|
||||
// #docregion http-providers
|
||||
import 'package:http/browser_client.dart' show BrowserClient;
|
||||
import 'package:http/browser_client.dart';
|
||||
// #enddocregion http-providers
|
||||
|
||||
import 'package:server_communication/app_component.dart';
|
||||
|
|
|
@ -5,7 +5,7 @@ import { provide } from '@angular/core';
|
|||
import { XHRBackend } from '@angular/http';
|
||||
|
||||
import { InMemoryBackendService,
|
||||
SEED_DATA } from 'angular2-in-memory-web-api/core';
|
||||
SEED_DATA } from 'angular2-in-memory-web-api';
|
||||
import { HeroData } from './hero-data';
|
||||
|
||||
// The usual bootstrapping imports
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
var packages = {
|
||||
'app': { main: 'main.js', defaultExtension: 'js' },
|
||||
'rxjs': { defaultExtension: 'js' },
|
||||
'angular2-in-memory-web-api': { defaultExtension: 'js' },
|
||||
'angular2-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' },
|
||||
};
|
||||
|
||||
var ngPackageNames = [
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
var packages = {
|
||||
'app': { main: 'main.ts', defaultExtension: 'ts' },
|
||||
'rxjs': { defaultExtension: 'js' },
|
||||
'angular2-in-memory-web-api': { defaultExtension: 'js' },
|
||||
'angular2-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' },
|
||||
};
|
||||
|
||||
var ngPackageNames = [
|
||||
|
|
|
@ -8,8 +8,10 @@ import {
|
|||
beforeEach, beforeEachProviders,
|
||||
describe, ddescribe, xdescribe,
|
||||
expect, it, iit, xit,
|
||||
async, inject, ComponentFixture, TestComponentBuilder
|
||||
} from '@angular/testing';
|
||||
async, inject
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { ComponentFixture, TestComponentBuilder } from '@angular/compiler/testing';
|
||||
|
||||
import { Hero, HeroService, MockHeroService } from './mock-hero.service';
|
||||
|
||||
|
@ -18,7 +20,7 @@ import { Router, MockRouter,
|
|||
RouterOutlet, MockRouterOutlet} from './mock-router';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
let fixture: ComponentFixture;
|
||||
let fixture: ComponentFixture<AppComponent>;
|
||||
let comp: AppComponent;
|
||||
|
||||
beforeEach(async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
// Can't test with ROUTER_DIRECTIVES yet
|
||||
// import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from '@angular/router';
|
||||
// import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from '@angular/router-deprecated';
|
||||
|
||||
import { RouteConfig, RouterLink,
|
||||
RouterOutlet, ROUTER_PROVIDERS } from '@angular/router';
|
||||
RouterOutlet, ROUTER_PROVIDERS } from '@angular/router-deprecated';
|
||||
|
||||
import { DashboardComponent } from './dashboard.component';
|
||||
import { HeroesComponent } from './heroes.component';
|
||||
|
|
|
@ -19,12 +19,13 @@ import { DebugElement } from '@angular/core';
|
|||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import {
|
||||
beforeEach, beforeEachProviders, withProviders,
|
||||
beforeEach, beforeEachProviders,
|
||||
describe, ddescribe, xdescribe,
|
||||
expect, it, iit, xit,
|
||||
async, inject, fakeAsync, tick,
|
||||
ComponentFixture, TestComponentBuilder
|
||||
} from '@angular/testing';
|
||||
async, inject
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { ComponentFixture, TestComponentBuilder } from '@angular/compiler/testing';
|
||||
|
||||
import { provide } from '@angular/core';
|
||||
import { ViewMetadata } from '@angular/core';
|
||||
|
@ -90,7 +91,7 @@ xdescribe('async & inject testing errors', () => {
|
|||
let itPromise = patchJasmineIt();
|
||||
|
||||
it('should fail with an error from a promise', async(() => {
|
||||
return Promise.reject('baz')
|
||||
return Promise.reject('baz');
|
||||
}));
|
||||
|
||||
itPromise.then(
|
||||
|
|
|
@ -16,12 +16,14 @@ import { DebugElement } from '@angular/core';
|
|||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import {
|
||||
beforeEach, beforeEachProviders, withProviders,
|
||||
beforeEach, beforeEachProviders,
|
||||
describe, ddescribe, xdescribe,
|
||||
expect, it, iit, xit,
|
||||
async, inject, fakeAsync, tick,
|
||||
ComponentFixture, TestComponentBuilder
|
||||
} from '@angular/testing';
|
||||
async, inject,
|
||||
fakeAsync, tick, withProviders
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { ComponentFixture, TestComponentBuilder } from '@angular/compiler/testing';
|
||||
|
||||
import { provide } from '@angular/core';
|
||||
import { ViewMetadata } from '@angular/core';
|
||||
|
@ -349,7 +351,7 @@ describe('test component builder', function() {
|
|||
})), 10000); // Long timeout because this test makes an actual XHR.
|
||||
|
||||
describe('(lifecycle hooks w/ MyIfParentComp)', () => {
|
||||
let fixture: ComponentFixture;
|
||||
let fixture: ComponentFixture<MyIfParentComp>;
|
||||
let parent: MyIfParentComp;
|
||||
let child: MyIfChildComp;
|
||||
|
||||
|
|
|
@ -8,8 +8,10 @@ import {
|
|||
beforeEach, beforeEachProviders,
|
||||
describe, ddescribe, xdescribe,
|
||||
expect, it, iit, xit,
|
||||
async, inject, TestComponentBuilder
|
||||
} from '@angular/testing';
|
||||
async, inject
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { ComponentFixture, TestComponentBuilder } from '@angular/compiler/testing';
|
||||
|
||||
import { Hero, HeroService, MockHeroService } from './mock-hero.service';
|
||||
import { Router, MockRouter } from './mock-router';
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// #docregion
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
// #docregion import-router
|
||||
import { Router } from '@angular/router';
|
||||
import { Router } from '@angular/router-deprecated';
|
||||
// #enddocregion import-router
|
||||
|
||||
import { Hero } from './hero';
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
// #enddocregion import-oninit
|
||||
// #docregion import-route-params
|
||||
import {RouteParams} from '@angular/router';
|
||||
import {RouteParams} from '@angular/router-deprecated';
|
||||
// #enddocregion import-route-params
|
||||
|
||||
import { Hero } from './hero';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Router } from '@angular/router-deprecated';
|
||||
|
||||
import { Hero } from './hero';
|
||||
import { HeroDetailComponent } from './hero-detail.component';
|
||||
|
@ -47,4 +47,4 @@ export class HeroesComponent implements OnInit {
|
|||
}
|
||||
// #enddocregion heroes-component-renaming
|
||||
// #enddocregion class
|
||||
// #enddocregion
|
||||
// #enddocregion
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
/* tslint:disable:no-unused-variable */
|
||||
import {
|
||||
beforeEach, beforeEachProviders, withProviders,
|
||||
beforeEach, beforeEachProviders,
|
||||
describe, ddescribe, xdescribe,
|
||||
expect, it, iit, xit,
|
||||
async, inject, TestComponentBuilder
|
||||
} from '@angular/testing';
|
||||
async, inject, withProviders
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { TestComponentBuilder } from '@angular/compiler/testing';
|
||||
|
||||
import { provide } from '@angular/core';
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
export * from '@angular/router';
|
||||
export * from '@angular/router-deprecated';
|
||||
|
||||
import { Directive, DynamicComponentLoader, ViewContainerRef,
|
||||
Injectable, Optional, Input } from '@angular/core';
|
||||
|
||||
import { ComponentInstruction, Instruction,
|
||||
Router, RouterOutlet} from '@angular/router';
|
||||
Router, RouterOutlet} from '@angular/router-deprecated';
|
||||
|
||||
let _resolveToTrue = Promise.resolve(true);
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import { provide } from '@angular/core';
|
||||
import { XHRBackend } from '@angular/http';
|
||||
|
||||
import { InMemoryBackendService, SEED_DATA } from 'angular2-in-memory-web-api/core';
|
||||
import { InMemoryBackendService, SEED_DATA } from 'angular2-in-memory-web-api';
|
||||
import { InMemoryDataService } from './in-memory-data.service';
|
||||
|
||||
// The usual bootstrapping imports
|
||||
|
|
|
@ -1,27 +1,25 @@
|
|||
// Configuration for the Wallaby Visual Studio Code testing extension
|
||||
// https://marketplace.visualstudio.com/items?itemName=WallabyJs.wallaby-vscode
|
||||
// Note: Wallaby is not open source and costs money
|
||||
|
||||
module.exports = function () {
|
||||
|
||||
return {
|
||||
files: [
|
||||
// System.js for module loading
|
||||
{pattern: 'node_modules/systemjs/dist/system-polyfills.js', instrument: false},
|
||||
{pattern: 'node_modules/systemjs/dist/system.js', instrument: false},
|
||||
|
||||
// Polyfills
|
||||
{pattern: 'node_modules/code-js/client/shim.min.js', instrument: false},
|
||||
{pattern: 'node_modules/angular2/bundles/angular2-polyfills.js', instrument: false},
|
||||
{pattern: 'node_modules/core-js/client/shim.min.js', instrument: false},
|
||||
|
||||
// Zone.js dependencies
|
||||
// Note - do not include zone.js itself or long-stack-trace-zone.js` here as
|
||||
// they are included already in angular2-polyfills
|
||||
// Reflect, Zone.js, and test shims
|
||||
// Rx.js, Angular 2 itself, and the testing library not here because loaded by systemjs
|
||||
{pattern: 'node_modules/reflect-metadata/Reflect.js', instrument: false},
|
||||
{pattern: 'node_modules/zone.js/dist/zone.js', instrument: false},
|
||||
{pattern: 'node_modules/zone.js/dist/jasmine-patch.js', instrument: false},
|
||||
{pattern: 'node_modules/zone.js/dist/async-test.js', instrument: false},
|
||||
{pattern: 'node_modules/zone.js/dist/fake-async-test.js', instrument: false},
|
||||
|
||||
// Rx.js, Angular 2 itself, and the testing library not here because loaded by systemjs
|
||||
|
||||
{pattern: 'app/**/*+(ts|html|css)', load: false},
|
||||
{pattern: 'app/**/*.spec.ts', ignore: true}
|
||||
],
|
||||
|
@ -36,41 +34,24 @@ module.exports = function () {
|
|||
|
||||
testFramework: 'jasmine',
|
||||
|
||||
debug: true,
|
||||
|
||||
bootstrap: function (wallaby) {
|
||||
wallaby.delayStart();
|
||||
systemConfig();
|
||||
|
||||
System.config({
|
||||
defaultJSExtensions: true,
|
||||
packages: {
|
||||
app: {
|
||||
meta: {
|
||||
'*': {
|
||||
scriptLoad: true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
paths: {
|
||||
'npm:*': 'node_modules/*'
|
||||
},
|
||||
map: {
|
||||
'angular2': 'npm:angular2',
|
||||
'rxjs': 'npm:rxjs'
|
||||
}
|
||||
});
|
||||
|
||||
// Configure Angular for the browser and
|
||||
// with test versions of the platform providers
|
||||
Promise.all([
|
||||
System.import('angular2/testing'),
|
||||
System.import('angular2/platform/testing/browser')
|
||||
System.import('@angular/core/testing'),
|
||||
System.import('@angular/platform-browser-dynamic/testing')
|
||||
])
|
||||
.then(function (results) {
|
||||
var testing = results[0];
|
||||
var browser = results[1];
|
||||
.then(function (providers) {
|
||||
var testing = providers[0];
|
||||
var testingBrowser = providers[1];
|
||||
|
||||
testing.setBaseTestProviders(
|
||||
browser.TEST_BROWSER_PLATFORM_PROVIDERS,
|
||||
browser.TEST_BROWSER_APPLICATION_PROVIDERS);
|
||||
testingBrowser.TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
|
||||
testingBrowser.TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS);
|
||||
|
||||
|
||||
// Load all spec files
|
||||
return Promise.all(wallaby.tests.map(function (specFile) {
|
||||
|
@ -85,8 +66,58 @@ module.exports = function () {
|
|||
throw e;
|
||||
}, 0);
|
||||
});
|
||||
},
|
||||
|
||||
debug: true
|
||||
//////////////////////////
|
||||
// SystemJS configuration.
|
||||
function systemConfig() {
|
||||
|
||||
// map tells the System loader where to look for things
|
||||
var map = {
|
||||
'app': 'app',
|
||||
|
||||
'@angular': 'node_modules/@angular',
|
||||
'angular2-in-memory-web-api': 'node_modules/angular2-in-memory-web-api',
|
||||
'rxjs': 'node_modules/rxjs'
|
||||
};
|
||||
|
||||
// packages tells the System loader how to load when no filename and/or no extension
|
||||
var packages = {
|
||||
'app': { main: 'main.js', defaultExtension: 'js' },
|
||||
'rxjs': { defaultExtension: 'js' },
|
||||
'angular2-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' },
|
||||
};
|
||||
|
||||
var ngPackageNames = [
|
||||
'common',
|
||||
'compiler',
|
||||
'core',
|
||||
'http',
|
||||
'platform-browser',
|
||||
'platform-browser-dynamic',
|
||||
'router',
|
||||
'router-deprecated',
|
||||
'upgrade',
|
||||
];
|
||||
|
||||
// Add package entries for angular packages
|
||||
ngPackageNames.forEach(function(pkgName) {
|
||||
|
||||
// Bundled (~40 requests): DOESN'T WORK IN WALLABY OR KARMA (YET?)
|
||||
// packages['@angular/'+pkgName] = { main: pkgName + '.umd.js', defaultExtension: 'js' };
|
||||
|
||||
// Individual files (~300 requests):
|
||||
packages['@angular/'+pkgName] = { main: 'index.js', defaultExtension: 'js' };
|
||||
});
|
||||
|
||||
var config = {
|
||||
map: map,
|
||||
packages: packages
|
||||
}
|
||||
|
||||
System.config(config);
|
||||
}
|
||||
//////////////////
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
|
|
|
@ -14,12 +14,16 @@ include ../../../_includes/_util-fns
|
|||
- var _liveLink = 'sample repo';
|
||||
|
||||
mixin liveExampleLink(linkText, exampleUrlPartName)
|
||||
a(href='https://angular-examples.github.io/#{exampleUrlPartName}' target="_blank")= linkText
|
||||
- var text = linkText || 'live example';
|
||||
- var ex = exampleUrlPartName || getExampleName();
|
||||
- var href = 'http://angular-examples.github.io/' + ex;
|
||||
a(href='#{href}' target="_blank")= text
|
||||
|
||||
mixin liveExampleLink2(linkText, exampleUrlPartName)
|
||||
- var liveExampleSourceLinkText = attributes.srcLinkText || 'view source'
|
||||
| #[+liveExampleLink(linkText, exampleUrlPartName)]
|
||||
| (#[a(href='https://github.com/angular-examples/#{exampleUrlPartName}' target="_blank") #{liveExampleSourceLinkText}])
|
||||
- var srcText = attributes.srcText || 'view source';
|
||||
- var ex = exampleUrlPartName || attributes.example || getExampleName();
|
||||
- var href = 'http://github.com/angular-examples/' + ex;
|
||||
span #[+liveExampleLink(linkText, ex)] (#[a(href='#{href}' target="_blank") #{srcText}])
|
||||
|
||||
- var adjustExamplePath = function(_path) {
|
||||
- if(!_path) return _path;
|
||||
|
|
|
@ -17,6 +17,12 @@
|
|||
"intro": "Share information between different directives and components"
|
||||
},
|
||||
|
||||
"component-relative-paths": {
|
||||
"title": "Component-relative Paths",
|
||||
"intro": "Use relative URLs for component templates and styles.",
|
||||
"hide": true
|
||||
},
|
||||
|
||||
"dependency-injection": {
|
||||
"title": "Dependency Injection",
|
||||
"intro": "Techniques for Dependency Injection",
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
!= partial("../../../_includes/_ts-temp")
|
|
@ -80,8 +80,8 @@
|
|||
},
|
||||
|
||||
"server-communication": {
|
||||
"title": "Http Client",
|
||||
"intro": "Talk to a remote server with the Angular Http Client."
|
||||
"title": "HTTP Client",
|
||||
"intro": "Talk to a remote server with an HTTP Client."
|
||||
},
|
||||
|
||||
"lifecycle-hooks": {
|
||||
|
|
|
@ -5,11 +5,11 @@ block includes
|
|||
- var _Http = 'BrowserClient';
|
||||
- var _Angular_Http = 'Dart <code>BrowserClient</code>'
|
||||
- var _httpUrl = 'https://pub.dartlang.org/packages/http'
|
||||
- var _Angular_http_library = 'Dart <a href="!{_httpUrl}"><b>http</b> library</a>'
|
||||
- var _Angular_http_library = 'Dart <a href="' + _httpUrl + '"><b>http</b></a> library'
|
||||
|
||||
block demos-list
|
||||
li HTTP client: Tour of Heroes
|
||||
li JSONP client: Wikipedia to fetch data from a service that doesn't support CORS (under development)
|
||||
li #[a(href="#http-client") HTTP client: Tour of Heroes]
|
||||
li #[a(href="#cors") JSONP client: Wikipedia to fetch data from a service that does not support CORS] #[b (under development)]
|
||||
|
||||
block rxjs-import
|
||||
//- N/A
|
||||
|
|
|
@ -16,6 +16,11 @@
|
|||
"intro": "Share information between different directives and components"
|
||||
},
|
||||
|
||||
"component-relative-paths": {
|
||||
"title": "Component-relative Paths",
|
||||
"intro": "Use relative URLs for component templates and styles."
|
||||
},
|
||||
|
||||
"dependency-injection": {
|
||||
"title": "Dependency Injection",
|
||||
"intro": "Techniques for Dependency Injection"
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
!= partial("../../../_includes/_ts-temp")
|
|
@ -80,8 +80,8 @@
|
|||
},
|
||||
|
||||
"server-communication": {
|
||||
"title": "Http Client",
|
||||
"intro": "Talk to a remote server with the Angular Http Client."
|
||||
"title": "HTTP Client",
|
||||
"intro": "Talk to a remote server with an HTTP Client."
|
||||
},
|
||||
|
||||
"lifecycle-hooks": {
|
||||
|
|
|
@ -5,8 +5,11 @@ include ../../../_includes/_util-fns
|
|||
//- Other values match the defaults.
|
||||
|
||||
mixin liveExampleLink(linkText, exampleUrlPartName)
|
||||
a(href='/resources/live-examples/#{exampleUrlPartName}/ts/plnkr.html' target="_blank")= linkText
|
||||
- var text = linkText || 'live example';
|
||||
- var ex = exampleUrlPartName || getExampleName();
|
||||
- var href = '/resources/live-examples/' + ex + '/ts/plnkr.html';
|
||||
a(href='#{href}' target="_blank")= text
|
||||
|
||||
mixin liveExampleLink2(linkText, exampleUrlPartName)
|
||||
//- In Dart this also gives a link to the source.
|
||||
| #[+liveExampleLink(linkText, exampleUrlPartName)]
|
||||
//- In Dart this gives 2 links: to the demo and to the source.
|
||||
+liveExampleLink(linkText, exampleUrlPartName)
|
||||
|
|
|
@ -15,6 +15,12 @@
|
|||
"title": "组件通讯",
|
||||
"intro": "在不同的指令和组件之间共享信息"
|
||||
},
|
||||
|
||||
"component-relative-paths": {
|
||||
"title": "相对于组件的路径",
|
||||
"intro": "为组件的模板和样式指定相对于组件的路径"
|
||||
},
|
||||
|
||||
"dependency-injection": {
|
||||
"title": "依赖注入",
|
||||
"intro": "依赖注入技术"
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
include ../_util-fns
|
||||
|
||||
:marked
|
||||
## Write *Component-Relative* URLs to component templates and style files
|
||||
|
||||
Our components ofter refer to external template and style files.
|
||||
We identify those files with a URL in the `templateUrl` and `styleUrls` properties of the `@Component` metadata
|
||||
as seen here:
|
||||
|
||||
+makeExample('cb-component-relative-paths/ts/app/some.component.ts','absolute-config')(format='.')
|
||||
:marked
|
||||
By default, we *must* specify the full path back to the application root.
|
||||
We call this an ***absolute path*** because it is *absolute* with respect to the application root.
|
||||
|
||||
There are two problems with an *absolute path*
|
||||
|
||||
1. We have to remember the full path back to the application root.
|
||||
|
||||
1. We have to update the URL when we move the component around in the application files structure.
|
||||
|
||||
It would be much easier to write and maintain our application components if we could specify template and style locations
|
||||
*relative* to their component class file.
|
||||
|
||||
*We can!*
|
||||
|
||||
.alert.is-important
|
||||
:marked
|
||||
We can if we build our application as `commonjs` modules and load those modules
|
||||
with a suitable package loader such as `systemjs` or `webpack`.
|
||||
Learn why [below](#why-default).
|
||||
|
||||
The Angular 2 CLI uses these technologies and defaults to the
|
||||
*component-relative path* approach described here.
|
||||
CLI users can skip this chapter or read on to understand
|
||||
how it works.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## _Component-Relative_ Paths
|
||||
|
||||
Our goal is to specify template and style URLs *relative* to their component class files,
|
||||
hence the term ***component-relative path***.
|
||||
|
||||
The key to success is following a convention that puts related component files in well-known locations.
|
||||
|
||||
We recommend keeping component template and component-specific style files as *siblings* of their
|
||||
companion component class files.
|
||||
Here we see the three files for `SomeComponent` sitting next to each other in the `app` folder.
|
||||
|
||||
.filetree
|
||||
.file app
|
||||
.children
|
||||
.file some.component.css
|
||||
.file some.component.html
|
||||
.file some.component.ts
|
||||
.file ...
|
||||
:marked
|
||||
We'll have more files and folders — and greater folder depth — as our application grows.
|
||||
We'll be fine as long as the component files travel together as the inseparable siblings they are.
|
||||
|
||||
### Set the *moduleId*
|
||||
|
||||
Having adopted this file structure convention, we can specify locations of the template and style files
|
||||
relative to the component class file simply by setting the `moduleId` property of the `@Component` metadata like this
|
||||
+makeExample('cb-component-relative-paths/ts/app/some.component.ts','module-id')(format='.')
|
||||
:marked
|
||||
We strip the `app/` base path from the `templateUrl` and `styleUrls`. The result looks like this:
|
||||
+makeExample('cb-component-relative-paths/ts/app/some.component.ts','relative-config')(format='.')
|
||||
|
||||
.alert.is-helpful
|
||||
:marked
|
||||
Webpack users may prefer [an alternative approach](#webpack) that uses `require`.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Source
|
||||
|
||||
**We can see the [live example](/resources/live-examples/cb-component-relative-paths/ts/plnkr.html)**
|
||||
and download the source code from there
|
||||
or simply read the pertinent source here.
|
||||
+makeTabs(
|
||||
`cb-component-relative-paths/ts/app/some.component.ts,
|
||||
cb-component-relative-paths/ts/app/some.component.html,
|
||||
cb-component-relative-paths/ts/app/some.component.css,
|
||||
cb-component-relative-paths/ts/app/app.component.ts`,
|
||||
null,
|
||||
`app/some.component.ts, app/some.component.html, app/some.component.css, app/app.component.ts`)
|
||||
|
||||
a#why-default
|
||||
.l-main-section
|
||||
:marked
|
||||
## Appendix: why *component-relative* is not the default
|
||||
|
||||
A *component-relative* path is obviously superior to an *absolute* path.
|
||||
Why did Angular default to the *absolute* path?
|
||||
Why do *we* have to set the `moduleId`? Why can't Angular set it?
|
||||
|
||||
First, let's look at what happens if we use a relative path and omit the `moduleId`.
|
||||
|
||||
`EXCEPTION: Failed to load some.component.html`
|
||||
|
||||
Angular can't find the file so it throws an error.
|
||||
|
||||
Why can't Angular calculate the template and style URLs from the component file's location?
|
||||
|
||||
Because the location of the component can't be determined without the developer's help.
|
||||
Angular apps can be loaded in many ways: from individual files, from SystemJS packages, or
|
||||
from CommonJS packages, to name a few.
|
||||
We might generate modules in any of several formats.
|
||||
We might not be writing modular code at all!
|
||||
|
||||
With this diversity of packaging and module load strategies,
|
||||
it's not possible for Angular to know with certainty where these files reside at runtime.
|
||||
|
||||
The only location Angular can be sure of is the URL of the `index.html` home page, the application root.
|
||||
So by default it resolves template and style paths relative to the URL of `index.html`.
|
||||
That's why we previously wrote our file URLs with an `app/` base path prefix.
|
||||
|
||||
But *if* we follow the recommended guidelines and we write modules in `commonjs` format
|
||||
and we use a module loader that *plays nice*,
|
||||
*then* we — the developers of the application —
|
||||
know that the semi-global `module.id` variable is available and contains
|
||||
the absolute URL of the component class module file.
|
||||
|
||||
That knowledge enables us to tell Angular where the *component* file is
|
||||
by setting the `moduleId`:
|
||||
+makeExample('cb-component-relative-paths/ts/app/some.component.ts','module-id')(format='.')
|
||||
|
||||
a#webpack
|
||||
.l-main-section
|
||||
:marked
|
||||
## Webpack: load templates and styles with *require*
|
||||
Webpack developers have an alternative to `moduleId`.
|
||||
|
||||
They can load templates and styles at runtime by setting the component metadata `template` and `style` properties
|
||||
with `require` statements that reference *component-relative* URLS.
|
||||
|
||||
+makeExample('webpack/ts/src/app/app.component.ts')(format='.')
|
||||
:marked
|
||||
See the [Introduction to Webpack](../guide/webpack.html).
|
|
@ -80,8 +80,8 @@
|
|||
},
|
||||
|
||||
"server-communication": {
|
||||
"title": "Http客户端",
|
||||
"intro": "通过Angular Http客户端与远程服务器对话。"
|
||||
"title": "HTTP客户端",
|
||||
"intro": "通过HTTP客户端与远程服务器对话。"
|
||||
},
|
||||
|
||||
"lifecycle-hooks": {
|
||||
|
|
|
@ -19,8 +19,8 @@ block includes
|
|||
* [pass values into the directive using data binding](#bindings)
|
||||
* [使用数据绑定把值传到指令中](#bindings)
|
||||
|
||||
p.
|
||||
#[+liveExampleLink2('在线例子', 'attribute-directives')].
|
||||
p Try the #[+liveExampleLink2()].
|
||||
|
||||
:marked
|
||||
## Directives overview
|
||||
## 指令概览
|
||||
|
|
|
@ -32,12 +32,9 @@ block includes
|
|||
* [Appendix 1: Inspecting the generated runtime component styles](#inspect-generated-css)
|
||||
* [附录1: 审查生成的运行时组件样式](#inspect-generated-css)
|
||||
* [Appendix 2: Loading Styles with Relative URLs](#relative-urls)
|
||||
* [附录2: 用相对URL加载样式](#relative-urls)
|
||||
p
|
||||
| #[+liveExampleLink2('Run the live example', 'component-styles')]
|
||||
| of the code shown in this chapter.
|
||||
p
|
||||
| #[+liveExampleLink2('运行本章代码的在线范例', 'component-styles')]
|
||||
|
||||
p Run the #[+liveExampleLink2()] of the code shown in this chapter.
|
||||
p 运行本章这些代码的#[+liveExampleLink2()]
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
|
@ -473,96 +470,12 @@ code-example(format='').
|
|||
|
||||
block module-id
|
||||
:marked
|
||||
We'd *prefer* to write this:
|
||||
We can change the way Angular calculates the full URL be setting the component metadata's `moduleId` property to `module.id`.
|
||||
|
||||
我们期望的*首选*方式是这样写:
|
||||
|
||||
+makeExample('component-styles/ts/app/quest-summary.component.ts', 'urls')(format='.')
|
||||
|
||||
:marked
|
||||
We can't do that by default. Angular can't find the files and throws an error:
|
||||
|
||||
但默认情况下我们不能这么做。Angular找不到这个文件,并且抛出一个错误:
|
||||
|
||||
`EXCEPTION: Failed to load quest-summary.component.html`
|
||||
|
||||
`异常:不能加载quest-summary.component.html文件(EXCEPTION: Failed to load quest-summary.component.html)`
|
||||
|
||||
Why can't Angular calculate the HTML and CSS URLs from the component file's location?
|
||||
|
||||
为什么Angular不能根据组件文件的位置计算出HTML和CSS文件的URL呢?
|
||||
|
||||
Unfortunately, that location is not readily known.
|
||||
Angular apps can be loaded in many ways: from individual files, from SystemJS packages, or
|
||||
from CommonJS packages, to name a few.
|
||||
With this diversity of load strategies, it's not easy to tell at runtime where these files actually reside.
|
||||
|
||||
很不幸,当前的位置并不是已知的。
|
||||
Angular应用可以用多种方式被加载:从独立的文件、从SystemJS包,或从CommonJS包等等。
|
||||
由于存在这么多种加载策略,要想在运行期知道这些文件的实际位置,可不是一件容易的事。
|
||||
|
||||
The only location Angular can be sure of is the URL of the `index.html` home page.
|
||||
So by default it resolves template and style paths relative to the URL of `index.html`.
|
||||
That's why we previously wrote our CSS file URLs with an `app/` base path prefix.
|
||||
|
||||
Angular唯一能够确定的位置,就是起始页`index.html`的URL。
|
||||
所以,默认情况下,它都是以`index.html`的URL为基准来计算模板和样式的相对路径的。
|
||||
这就是为什么我们以前写自身CSS文件的URL都以`app/`基本路径作为前缀。
|
||||
|
||||
Although this works with any code loading scheme, it is very inconvenient.
|
||||
We move file folders around all the time during the evolution of our applications.
|
||||
It's no fun patching the style and template URLs when we do.
|
||||
|
||||
虽然这能够和各种代码加载方案协同工作,但它很不方便。
|
||||
在应用程序的进化过程中,我们经常要移动文件夹。整天被迫修改样式和模板的URL可一点儿都不好玩儿!
|
||||
|
||||
### *moduleId*
|
||||
### *moduleId*
|
||||
|
||||
We can change the way Angular calculates the full URL be setting the component metadata's `moduleId` property.
|
||||
|
||||
我们可以通过设置组件元数据中的`moduleId`属性,我们可以改变Angular计算完整URL的方式。
|
||||
|
||||
If we knew the component file's base path, we'd set `moduleId` to that and
|
||||
let Angular construct the full URL from this base path plus the CSS and template file names.
|
||||
|
||||
如果我们知道组件文件的基本路径,我们就可以把`moduleId`设置成它,
|
||||
让Angular可以用这个基本路径加上CSS和模板文件的名字来构造出完整URL。
|
||||
|
||||
Our challenge is to calculate the base path with minimal effort.
|
||||
If it's too hard, we shouldn't bother; we should just write the full path to the root and move on.
|
||||
Fortunately, *certain* module loaders make it relatively easy to find the base path.
|
||||
|
||||
我们的挑战是如何用最小的代价计算出这个基本路径。
|
||||
如果太难,我们也不想自找麻烦,还不如直接写到程序根目录的全路径,然后继续。
|
||||
幸运的是,在*某些*模块加载器中找到基本路径要相对简单一些。
|
||||
|
||||
SystemJS (starting in v.0.19.19) sets a *semi-global* variable to the URL of the component file.
|
||||
That makes it trivial to set the component metadata `moduleId` property to the component's URL
|
||||
and let Angular determine the module-relative paths for style and template URLs from there.
|
||||
|
||||
SystemJS(自从v.0.19.19起)设置了一个*半全局*变量来指向组件文件的URL。
|
||||
有了它,把组件元数据的`moduleId`属性设置为组件本身的URL就简单多了,
|
||||
这样Angular就能让样式和模板的URL使用相对于模块的路径。
|
||||
|
||||
The name of the *semi-global* variable depends upon whether we told TypeScript to transpile to
|
||||
'system' or 'commonjs' format (see the `module` option in the
|
||||
[TypeScript compiler documentation](http://www.typescriptlang.org/docs/handbook/compiler-options.html)).
|
||||
The variables are `__moduleName` and `module.id` respectively.
|
||||
|
||||
这个*半全局*变量的名字取决于我们是告诉TypeScript转译成'system'还是'commonjs'格式
|
||||
(参见[TypeScript编译器文档](http://www.typescriptlang.org/docs/handbook/compiler-options.html)中的`module`选项)。
|
||||
这个变量分别是`__moduleName`(system格式)和`module.id`(commonjs格式)。
|
||||
|
||||
Here's an example in which we set the metadata `moduleId` to `module.id`.
|
||||
|
||||
下面的例子中示范了我们如何把元数据中的`moduleId`设置为`module.id`。
|
||||
通过把组件元数据的`moduleId`属性设置为`module.id`,我们可以更改Angular计算完整URL的方式
|
||||
|
||||
+makeExample('component-styles/ts/app/quest-summary.component.ts','', 'app/quest-summary.component.ts')
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
With a module bundler like Webpack we are more likely to set the `styles` and `template` properties with the bundler's
|
||||
`require` mechanism rather than bother with `styleUrls` and `templateUrl`.
|
||||
|
||||
如果使用像Webpack这样的模块打包器,我们更喜欢借助打包器本身的`require`机制来设置`styles`和`template`属性,而不是`styleUrls`和`templateUrl`。
|
||||
:marked
|
||||
Learn more about `moduleId` in the [Component-Relative Paths](../cookbook/component-relative-paths.html) chapter.
|
||||
|
||||
要学习更多关于`moduleId`的知识,请参见[相对于组件的路径](../cookbook/component-relative-paths.html)一章。
|
||||
|
|
|
@ -20,8 +20,9 @@ block includes
|
|||
In this chapter we explore these points and write some code.
|
||||
|
||||
在本章中,我们将浏览这些要点,并写点代码儿来验证它。
|
||||
p
|
||||
| Try the #[+liveExampleLink2('live example', 'hierarchical-dependency-injection')].
|
||||
|
||||
p Try the #[+liveExampleLink2()].
|
||||
p 试试#[+liveExampleLink2()].
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
|
|
|
@ -24,7 +24,7 @@ block includes
|
|||
* [AfterViewInit and AfterViewChecked](#afterview)
|
||||
* [AfterContentInit and AfterContentChecked](#aftercontent)
|
||||
|
||||
p Try the #[+liveExampleLink2('live example', 'lifecycle-hooks')].
|
||||
p Try the #[+liveExampleLink2()].
|
||||
|
||||
a#hooks-overview
|
||||
.l-main-section
|
||||
|
@ -199,7 +199,7 @@ a#the-sample
|
|||
.l-main-section
|
||||
h2 Lifecycle exercises
|
||||
p.
|
||||
The #[+liveExampleLink('live example', 'lifecycle-hooks')]
|
||||
The #[+liveExampleLink()]
|
||||
demonstrates the lifecycle hooks in action through a series of exercises
|
||||
presented as components under the control of the root `AppComponent`.
|
||||
:marked
|
||||
|
|
|
@ -17,7 +17,7 @@ block includes
|
|||
In fact, we'd like to apply them in our HTML templates as we do styles.
|
||||
p.
|
||||
Welcome, Angular pipes, the simple display-value transformations that we can declare in our HTML!
|
||||
Try the #[+liveExampleLink2('live example', 'pipes')].
|
||||
Try the #[+liveExampleLink2()].
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
|
@ -169,7 +169,7 @@ figure.image-display
|
|||
Custom pipes must be registered manually.
|
||||
|
||||
p.
|
||||
If we try the #[+liveExampleLink('live code', 'pipes')] example,
|
||||
If we try the #[+liveExampleLink()],
|
||||
we can probe its behavior by changing the value and the optional exponent in the template.
|
||||
|
||||
:marked
|
||||
|
@ -219,10 +219,10 @@ a#change-detection
|
|||
Here's the `FlyingHeroesPipe` implementation which follows the pattern for custom pipes we saw earlier.
|
||||
+makeExample('pipes/ts/app/flying-heroes.pipe.ts', 'pure', 'app/flying-heroes.pipe.ts')(format='.')
|
||||
|
||||
:marked
|
||||
When we run the sample now we see odd behavior (try it in the [live example](/resources/live-examples/pipes/ts/plnkr.html)).
|
||||
p.
|
||||
When we run the sample now we see odd behavior (try it in the #[+liveExampleLink()]).
|
||||
Every hero we add is a flying hero but none of them are displayed.
|
||||
|
||||
:marked
|
||||
Although we're not getting the behavior we want, Angular isn't broken.
|
||||
It's just using a different change detection algorithm — one that ignores changes to the list or any of its items.
|
||||
|
||||
|
@ -238,7 +238,7 @@ a#change-detection
|
|||
|
||||
*If we **mutate** the #{_array}, no pipe is invoked and no display updated;
|
||||
if we **replace** the #{_array}, then the pipe executes and the display is updated*.
|
||||
The *Flying Heroes* in the [live example](/resources/live-examples/pipes/ts/plnkr.html) extends the
|
||||
The *Flying Heroes* extends the
|
||||
code with checkbox switches and additional displays to help us experience these effects.
|
||||
|
||||
figure.image-display
|
||||
|
@ -328,15 +328,15 @@ block pure-change
|
|||
|
||||
We can derive a `FlyingHeroesImpureComponent` that we derive from the `FlyingHeroesComponent`.
|
||||
+makeExample('pipes/ts/app/flying-heroes.component.ts','impure-component','app/flying-heroes.component.ts (FlyingHeroesImpureComponent)')(format='.')
|
||||
:marked
|
||||
p.
|
||||
The only substantive change is the pipe.
|
||||
We can confirm in the [live example](/resources/live-examples/pipes/ts/plnkr.html)
|
||||
that the *flying heroes* display updates as we enter new heroes even when we mutate the `heroes` #{_array}.
|
||||
- var _dollar = _docsFor === 'ts' ? '$' : '';
|
||||
:marked
|
||||
<a id="async-pipe"></a>
|
||||
### The impure *AsyncPipe*
|
||||
We can confirm in the #[+liveExampleLink()] that the #[i flying heroes]
|
||||
display updates as we enter new heroes even when we mutate the
|
||||
#[code heroes] #{_array}.
|
||||
|
||||
- var _dollar = _docsFor === 'ts' ? '$' : '';
|
||||
h3#async-pipe The impure #[i AsyncPipe]
|
||||
:marked
|
||||
The Angular `AsyncPipe` is an interesting example of an impure pipe.
|
||||
The `AsyncPipe` accepts a `#{_Promise}` or `#{_Observable}` as input
|
||||
and subscribes to the input automatically, eventually returning the emitted value(s).
|
||||
|
|
|
@ -16,7 +16,7 @@ block includes
|
|||
[JSONP](https://en.wikipedia.org/wiki/JSONP). A few browsers also support
|
||||
[Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).
|
||||
|
||||
The Angular HTTP client library simplifies application programming of the **XHR** and **JSONP** APIs
|
||||
The !{_Angular_http_library} simplifies application programming of the **XHR** and **JSONP** APIs
|
||||
as we'll learn in this chapter covering:
|
||||
|
||||
ul
|
||||
|
@ -30,25 +30,25 @@ ul
|
|||
li #[a(href="#update") Send data to the server]
|
||||
+ifDocsFor('ts')
|
||||
li #[a(href="#promises") Promises instead of observables]
|
||||
li #[a(href="#cross-origin-requests") Cross-origin requests: Wikipedia example]
|
||||
li #[a(href="#cors") Cross-origin requests: Wikipedia example]
|
||||
+ifDocsFor('ts')
|
||||
ul
|
||||
li #[a(href="#search-parameters") Set query string parameters]
|
||||
li #[a(href="#more-observables") Debounce search term input]
|
||||
li #[a(href="#in-mem-web-api") Appendix: the in-memory web api service]
|
||||
p.
|
||||
p.
|
||||
We illustrate these topics with code that you can
|
||||
#[+liveExampleLink2('run live in a browser', 'server-communication')].
|
||||
#[+liveExampleLink2('run live in a browser')].
|
||||
|
||||
.l-main-section
|
||||
h1 Demos
|
||||
p This chapter describes server communication with the help of the following demos
|
||||
ul
|
||||
block demos-list
|
||||
li HTTP client: Tour of Heroes with Observables
|
||||
li HTTP client: Tour of Heroes with #{_Promise}s
|
||||
li JSONP client: Wikipedia to fetch data from a service that doesn't support CORS
|
||||
li JSONP client: Wikipedia using observable operators to reduce server calls
|
||||
li #[a(href="#http-client") HTTP client: Tour of Heroes with Observables]
|
||||
li #[a(href="#promises") HTTP client: Tour of Heroes with #{_Promise}s]
|
||||
li #[a(href="#cors") JSONP client: Wikipedia to fetch data from a service that does not support CORS]
|
||||
li #[a(href="#more-observables") JSONP client: Wikipedia using observable operators to reduce server calls]
|
||||
:marked
|
||||
These demos are orchestrated by the root `AppComponent`
|
||||
+makeExample('server-communication/ts/app/app.component.ts', null, 'app/app.component.ts')
|
||||
|
@ -58,7 +58,7 @@ block rxjs-import
|
|||
There is nothing remarkable here _except_ for the import of RxJS operators.
|
||||
+makeExample('server-communication/ts/app/app.component.ts', 'import-rxjs')(format='.')
|
||||
:marked
|
||||
We'll talk about that [below](#rxjs) when we're ready to explore observables.
|
||||
We'll talk about that [below](#rxjs) when we're ready to explore observables.
|
||||
:marked
|
||||
First, we have to configure our application to use server communication facilities.
|
||||
|
||||
|
@ -66,14 +66,14 @@ block rxjs-import
|
|||
h1#http-providers Providing HTTP Services
|
||||
:marked
|
||||
We use the !{_Angular_Http} client to communicate with a server using a familiar HTTP request/response protocol.
|
||||
The `!{_Http}` client is one of a family of services in the !{_Angular_http_library}.
|
||||
The `#{_Http}` client is one of a family of services in the !{_Angular_http_library}.
|
||||
block system-config-of-http
|
||||
.l-sub-section
|
||||
:marked
|
||||
SystemJS knows how to load services from the !{_Angular_http_library} when we import from the `@angular/http` module
|
||||
because we registered that module name in the `system.config` file.
|
||||
:marked
|
||||
Before we can use the `!{_Http}` client , we'll have to register it as a service provider with the Dependency Injection system.
|
||||
Before we can use the `#{_Http}` client , we'll have to register it as a service provider with the Dependency Injection system.
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn about providers in the [Dependency Injection](dependency-injection.html) chapter.
|
||||
|
@ -84,7 +84,7 @@ p In this demo, we register providers in the #[code bootstrap] method of #[code
|
|||
block http-providers
|
||||
:marked
|
||||
We begin by importing the symbols we need, most of them familiar by now. The newcomer is `HTTP_PROVIDERS`,
|
||||
a collection of service providers from the Angular HTTP library.
|
||||
a collection of service providers from the !{_Angular_http_library}.
|
||||
|
||||
We register HTTP providers in the bootstrap method by passing them in an array as the second parameter after the root component.
|
||||
|
||||
|
@ -105,10 +105,8 @@ block http-providers
|
|||
For this reason, and this reason *only*, we hide it *above* the `AppComponent` in `main.ts`.
|
||||
|
||||
.l-main-section
|
||||
a#http-client
|
||||
h1#http-client The Tour of Heroes #[i HTTP] Client Demo
|
||||
:marked
|
||||
# The Tour of Heroes *HTTP* Client Demo
|
||||
|
||||
Our first demo is a mini-version of the [tutorial](../tutorial)'s "Tour of Heroes" (ToH) application.
|
||||
This version gets some heroes from the server, displays them in a list, lets us add new heroes, and saves them to the server.
|
||||
We use the !{_Angular_Http} client to communicate via `XMLHttpRequest (XHR)`.
|
||||
|
@ -182,7 +180,7 @@ h2#fetch-data Fetch data with the #[b HeroService]
|
|||
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'v1', 'app/toh/hero.service.ts (revised)')
|
||||
|
||||
:marked
|
||||
Notice that the Angular `!{_Http}` client service is
|
||||
Notice that the !{_Angular_Http} client service is
|
||||
[injected](dependency-injection.html) into the `HeroService` constructor.
|
||||
+makeExample('server-communication/ts/app/toh/hero.service.ts', 'ctor')
|
||||
:marked
|
||||
|
@ -237,7 +235,7 @@ block rxjs
|
|||
because the full library is so big. We only use a few operators in our app.
|
||||
|
||||
Instead, we'll import each `Observable` operator and static class method, one-by-one, until we have a custom *Observable* implementation tuned
|
||||
precisely to our requirements. We'll put the `import` statements in one `app/add-rxjs-operators.ts` file.
|
||||
precisely to our requirements. We'll put the `import` statements in one `app/rxjs-operators.ts` file.
|
||||
+makeExample('server-communication/ts/app/rxjs-operators.ts', null, 'app/rxjs-operators.ts')(format=".")
|
||||
:marked
|
||||
If we forget an operator, the TypeScript compiler will warn that it's missing and we'll update this file.
|
||||
|
@ -412,10 +410,8 @@ block hero-list-comp-add-hero
|
|||
+makeExample('server-communication/ts/app/toh/hero-list.component.ts', 'addHero', 'app/toh/hero-list.component.ts (addHero)')(format=".")
|
||||
|
||||
block promises
|
||||
a#promises
|
||||
h2#promises Fall back to Promises
|
||||
:marked
|
||||
## Fall back to Promises
|
||||
|
||||
Although the Angular `http` client API returns an `Observable<Response>` we can turn it into a
|
||||
[Promise<Response>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) if we prefer.
|
||||
It's easy to do and a promise-based version looks much like the observable-based version in simple cases.
|
||||
|
@ -461,11 +457,9 @@ block promises
|
|||
|
||||
Learn more about observables to understand the implications and consequences of subscriptions.
|
||||
|
||||
a#cross-origin-requests
|
||||
h2#cors Cross-origin requests: Wikipedia example
|
||||
:marked
|
||||
## Cross-origin requests: Wikipedia example
|
||||
|
||||
We just learned how to make `XMLHttpRequests` using Angular's built-in `Http` service.
|
||||
We just learned how to make `XMLHttpRequests` using the !{_Angular_Http} service.
|
||||
This is the most common approach for server communication.
|
||||
It doesn't work in all scenarios.
|
||||
|
||||
|
@ -496,7 +490,7 @@ figure.image-display
|
|||
block wikipedia-jsonp+
|
||||
:marked
|
||||
Wikipedia offers a modern `CORS` API and a legacy `JSONP` search API. Let's use the latter for this example.
|
||||
The Angular `Jsonp` service both extends the `Http` service for JSONP and restricts us to `GET` requests.
|
||||
The Angular `Jsonp` service both extends the `#{_Http}` service for JSONP and restricts us to `GET` requests.
|
||||
All other HTTP methods throw an error because JSONP is a read-only facility.
|
||||
|
||||
As always, we wrap our interaction with an Angular data access client service inside a dedicated service, here called `WikipediaService`.
|
||||
|
@ -638,7 +632,7 @@ block wikipedia-jsonp+
|
|||
.l-sub-section
|
||||
:marked
|
||||
We added the `debounceTime`, `distinctUntilChanged`, and `switchMap` operators to the RxJS `Observable` class
|
||||
in `add-rxjs-operators` as [described above](#rxjs)
|
||||
in `rxjs-operators` as [described above](#rxjs)
|
||||
|
||||
a#in-mem-web-api
|
||||
.l-main-section
|
||||
|
@ -694,4 +688,4 @@ block redirect-to-web-api
|
|||
p Here is the revised (and final) version of the #[code #[+adjExPath('app/main.ts')]] demonstrating these steps.
|
||||
+makeExample('server-communication/ts/app/main.ts', 'final', 'app/main.ts (final)')(format=".")
|
||||
|
||||
p See the full source code in the #[+liveExampleLink2('live example', 'server-communication')].
|
||||
p See the full source code in the #[+liveExampleLink2()].
|
||||
|
|
|
@ -14,8 +14,8 @@ block includes
|
|||
- [discover the <template> element](#template)
|
||||
- [understand the asterisk (\*) in **ngFor*](#asterisk)
|
||||
- [write our own structural directive](#unless)
|
||||
p
|
||||
| Try the #[+liveExampleLink2('live example', 'structural-directives')].
|
||||
|
||||
p Try the #[+liveExampleLink2()].
|
||||
|
||||
<a id="definition"></a>
|
||||
.l-main-section
|
||||
|
|
|
@ -496,9 +496,9 @@ a(href="#toc") 回到顶部
|
|||
|
||||
.s-why
|
||||
:marked
|
||||
**Why?** Clear service names such as `logger` do not require a suffix.
|
||||
**Why?** Clear service names such as `Logger` do not require a suffix.
|
||||
|
||||
**为何?**清楚的服务名,比如`logger`不需要后缀。
|
||||
**为何?**清楚的服务名,比如`Logger`不需要后缀。
|
||||
|
||||
.s-why.s-why-last
|
||||
:marked
|
||||
|
@ -537,7 +537,7 @@ table(width="100%")
|
|||
td
|
||||
code-example.
|
||||
@Injectable()
|
||||
export class LoggerService {}
|
||||
export class Logger {}
|
||||
td
|
||||
:marked
|
||||
logger.service.ts
|
||||
|
|
|
@ -33,7 +33,7 @@ block includes
|
|||
h1 Try it!
|
||||
h1 试试!
|
||||
p
|
||||
| Try the #[+liveExampleLink2('live example', 'quickstart')] which loads the sample app
|
||||
| Try the #[+liveExampleLink2()] which loads the sample app
|
||||
+ifDocsFor('ts')
|
||||
| in #[a(href="http://plnkr.co/" title="Plunker" target="_blank") plunker]
|
||||
| and displays the simple message:
|
||||
|
|
|
@ -268,7 +268,7 @@ code-example(format=".").
|
|||
|
||||
`AppComponent`的模板是这样的:
|
||||
|
||||
+makeExample('toh-3/ts/app/app.component.ts', 'hero-detail-template', 'app.component.ts (模板)')
|
||||
+makeExample('toh-3/ts/app/app.component.ts', 'hero-detail-template', 'app.component.ts (Template)')(format='.')
|
||||
:marked
|
||||
Thanks to the binding, the `HeroDetailComponent` should receive the hero from the `AppComponent` and display that hero's detail beneath the list.
|
||||
The detail should update every time the user picks a new hero.
|
||||
|
@ -302,9 +302,7 @@ code-example(format=".").
|
|||
|
||||
我们要把它列在元数据的`directives`数组中,这样Angular才会知道它。
|
||||
让我们把这个数组属性加在`@Component`配置对象的底部,紧跟在`template`和`styles`属性之后。
|
||||
|
||||
+makeExample('toh-3/ts/app/app.component.ts', 'directives')
|
||||
|
||||
+makeExample('toh-3/ts/app/app.component.ts', 'directives', 'app/app.component.ts (Directives)')
|
||||
|
||||
:marked
|
||||
### It works!
|
||||
|
@ -340,7 +338,7 @@ code-example(format=".").
|
|||
.file hero-detail.component.ts
|
||||
.file main.ts
|
||||
.file node_modules ...
|
||||
.file typings ...
|
||||
.file typings ...
|
||||
.file index.html
|
||||
.file package.json
|
||||
.file tsconfig.json
|
||||
|
|
|
@ -270,7 +270,7 @@ code-example(format="." language="bash").
|
|||
### Inject the *HeroService*
|
||||
### 注入 *HeroService*
|
||||
|
||||
Two lines replace the one line of *new*:
|
||||
Two lines replace the one line that created with *new*:
|
||||
|
||||
用这两行代码代替用`new`时的一行:
|
||||
1. we add a constructor.
|
||||
|
@ -292,15 +292,6 @@ code-example(format="." language="bash").
|
|||
|
||||
现在,Angular将会知道,当它创建`AppComponent`实例时,需要先提供一个`HeroService`的实例。
|
||||
|
||||
Angular has to get that instance from somewhere. That's the role of the Angular *Dependency Injector*.
|
||||
The **Injector** has a **container** of previously created services.
|
||||
Either it finds and returns a pre-existing `HeroService` from its container or it creates a new instance, adds
|
||||
it to the container, and returns it to Angular.
|
||||
|
||||
Angular得想办法获得这个实例。这就是Angular *依赖注入器(Dependency Injector)* 扮演的角色。
|
||||
这个 **注入器** 有一个包括以前创建过的所有服务的 **容器** 。
|
||||
它既可以从容器中查找并返回一个已存在的`HeroService`实例,又可以创建一个新的实例,把它添加到容器中,然后把它返回给Angular。
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about Dependency Injection in the [Dependency Injection](../guide/dependency-injection.html) chapter.
|
||||
|
@ -328,35 +319,7 @@ code-example(format="." language="html").
|
|||
:marked
|
||||
The `providers` array tells Angular to create a fresh instance of the `HeroService` when it creates a new `AppComponent`.
|
||||
The `AppComponent` can use that service to get heroes and so can every child component of its component tree.
|
||||
|
||||
`providers`数组告诉Angular,在创建一个新的`AppComponent`时,也要创建一个`HeroService`的新鲜实例。
|
||||
`AppComponent`可以用此服务来获取英雄列表,它组件树中的每一个子组件也可以这么做。
|
||||
<a id="child-component"></a>
|
||||
.l-sub-section
|
||||
:marked
|
||||
### Services and the component tree
|
||||
### 服务和组件树
|
||||
|
||||
Recall that the `AppComponent` creates an instance of `HeroDetail` by virtue of the
|
||||
`<my-hero-detail>` tag at the bottom of its template. That `HeroDetail` is a child of the `AppComponent`.
|
||||
|
||||
回忆一下,`AppComponent`在它的模板底部包含了一个`<my-hero-detail>`标签,于是创建了一个`HeroDetail`的实例。这个`HeroDetail`就叫做`AppComponent`的子组件。
|
||||
|
||||
If the `HeroDetailComponent` needed its parent component's `HeroService`,
|
||||
it would ask Angular to inject the service into its constructor which would look just like the one for `AppComponent`:
|
||||
|
||||
如果`HeroDetailComponent`需要访问来自它父组件的`HeroService`服务,它可以要求Angular把这个服务注入到自己的构造函数中 —— 就像在`AppComponent`中的做法一样。
|
||||
|
||||
+makeExample('toh-4/ts/app/app.component.1.ts', 'ctor', 'hero-detail.component.ts (构造函数)')
|
||||
:marked
|
||||
The `HeroDetailComponent` must *not* repeat its parent's `providers` array! Guess [why](#shadow-provider).
|
||||
|
||||
`HeroDetailComponent`不能再写一遍它父组件的`providers`数组!你猜这是[为什么](#shadow-provider)。
|
||||
|
||||
The `AppComponent` is the top level component of our application.
|
||||
There should be only one instance of that component and only one instance of the `HeroService` in our entire app.
|
||||
|
||||
`AppComponent`是我们应用的顶层组件。在我们的整个应用中,应该有唯一的一个顶层组件,应该有唯一的一个`HeroService`实例。
|
||||
a#child-component
|
||||
:marked
|
||||
### *getHeroes* in the *AppComponent*
|
||||
### *AppComponent* 中的 *getHeroes*
|
||||
|
@ -629,30 +592,3 @@ code-example(format="." language="html").
|
|||
|
||||
Back in the `AppComponent`, replace `heroService.getHeroes` with `heroService.getHeroesSlowly`
|
||||
and see how the app behaves.
|
||||
|
||||
回到`AppComponent`中,把`heroService.getHeroes`替换为`heroService.getHeroesSlowly`,看看应用的行为有什么变化。
|
||||
|
||||
.l-main-section
|
||||
<a id="shadow-provider"></a>
|
||||
:marked
|
||||
### Appendix: Shadowing the parent's service
|
||||
### 附件:遮蔽父组件的服务
|
||||
|
||||
We stated [earlier](#child-component) that if we injected the parent `AppComponent` `HeroService`
|
||||
into the `HeroDetailComponent`, *we must not add a providers array* to the `HeroDetailComponent` metadata.
|
||||
|
||||
我们在[前面](#child-component)说过,如果在父组件`AppComponent`中把`HeroService`服务注入到`HeroDetailComponent`,
|
||||
*我们就不应该在`HeroDetailComponent`的元数据中再添加一个`providers`数组*。
|
||||
|
||||
Why? Because that tells Angular to create a new instance of the `HeroService` at the `HeroDetailComponent` level.
|
||||
The `HeroDetailComponent` doesn't want its *own* service instance; it wants its *parent's* service instance.
|
||||
Adding the `providers` array creates a new service instance that shadows the parent instance.
|
||||
|
||||
为什么呢?因为那会告诉Angular在`HeroDetailComponent`这一层创建一个新的`HeroService`实例。
|
||||
显然,在这个例子中,`HeroDetailComponent`不会希望创建*自己的*服务实例,它想要的就是来自父组件的服务实例。
|
||||
给组件添加一个`providers`数组,就会创建一个新的服务实例,而它会遮蔽父组件中的同名实例。
|
||||
|
||||
Think carefully about where and when to register a provider.
|
||||
Understand the scope of that registration. Be careful not to create a new service instance at the wrong level.
|
||||
|
||||
务必想清楚,在哪里、在什么时候注册供应商。要理解注册的有效范围。小心点!不要在错误的级别上创建新的服务实例。
|
||||
|
|
|
@ -466,9 +466,13 @@ code-example(format="." language="bash").
|
|||
+makeExample('toh-5/ts/app/dashboard.component.ts', 'template-url', 'app/dashboard.component.ts (templateUrl)')(format=".")
|
||||
.l-sub-section
|
||||
:marked
|
||||
We specify the path _all the way back to the application root_. Angular doesn't support module-relative paths.
|
||||
We specify the path _all the way back to the application root_ — `app/` in this case —
|
||||
because Angular doesn't support relative paths _by default_.
|
||||
We _can_ switch to [component-relative paths](../cookbook/component-relative-paths) if we prefer.
|
||||
|
||||
我们指定的所有路径都是相对于该应用的根目录的。Angular不支持使用相对于当前模块的路径。
|
||||
我们指定的所有路径_都是相对于该应用的根目录(这里是`app/`)的_。
|
||||
因为Angular_默认_不支持使用相对于当前模块的路径。
|
||||
只要喜欢,我们也_可以_切换成[相对于组件的路径](../cookbook/component-relative-paths)模式。
|
||||
:marked
|
||||
Create that file with these contents:
|
||||
|
||||
|
|
Loading…
Reference in New Issue