docs(component-relative-paths): new cookbook

This commit is contained in:
Ward Bell 2016-05-20 15:14:13 -07:00
parent da48db3c77
commit f9fea00824
17 changed files with 313 additions and 50 deletions

View File

@ -0,0 +1,27 @@
// gulp run-e2e-tests --filter=cb-set-document-title
describe('Set Document Title', function () {
beforeAll(function () {
browser.get('');
});
it('should set the document title', function () {
var titles = [
'Good morning!',
'Good afternoon!',
'Good evening!'
];
element.all( by.css( 'ul li a' ) ).each(
function iterator( element, i ) {
element.click();
expect( browser.getTitle() ).toEqual( titles[ i ] );
}
);
});
});

View File

@ -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 {}

View File

@ -0,0 +1,5 @@
import { bootstrap } from '@angular/platform-browser-dynamic';
import { AppComponent } from './app.component';
bootstrap(AppComponent);

View File

@ -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;
}

View File

@ -0,0 +1,4 @@
<!-- #docregion -->
<div class={{class}}>
{{type}}<br>{{path}}
</div>

View File

@ -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';
}

View File

@ -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>

View File

@ -0,0 +1,8 @@
{
"description": "Module-relative Paths",
"files": [
"!**/*.d.ts",
"!**/*.js"
],
"tags": [ "cookbook" ]
}

View File

@ -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",

View File

@ -0,0 +1 @@
!= partial("../../../_includes/_ts-temp")

View File

@ -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"

View File

@ -0,0 +1 @@
!= partial("../../../_includes/_ts-temp")

View File

@ -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"

View File

@ -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 &mdash; and greater folder depth &mdash; 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.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 &mdash; the developers of the application &mdash;
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).

View File

@ -311,55 +311,9 @@ code-example(format='').
block module-id
:marked
We'd *prefer* to write this:
+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:
`EXCEPTION: Failed to load quest-summary.component.html`
Why can't Angular calculate the HTML and CSS URLs from the component file's location?
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.
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.
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.
### *moduleId*
We can change the way Angular calculates the full URL be setting the component metadata's `moduleId` property.
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.
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.
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.
Here's an example in which we set the metadata `moduleId` to `module.id`.
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','', 'app/quest-summary.component.ts')
:marked
Learn more about `moduleId` in the [Component-Relative Paths](../cookbook/component-relative-paths.html) chapter.
.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`.

View File

@ -282,7 +282,9 @@ 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_ &mdash; `app/` in this case &mdash;
because Angular doesn't support relative paths _by default_.
We _can_ switch to [component-relative paths](../cookbook/component-relative-paths) if we prefer.
:marked
Create that file with these contents:
+makeExample('toh-5/ts/app/dashboard.component.html', null, 'dashboard.component.html')(format=".")