docs(toh/dart): convert toh-2 to dart (#1065)
This commit is contained in:
parent
b3d5f2bb65
commit
cac5c8977f
@ -0,0 +1,69 @@
|
||||
// #docregion ng-for
|
||||
<li *ngFor="#hero of heroes">
|
||||
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
||||
</li>
|
||||
// #enddocregion ng-for
|
||||
|
||||
// #docregion heroes-styled
|
||||
<h2>My Heroes</h2>
|
||||
<ul class="heroes">
|
||||
<li *ngFor="#hero of heroes">
|
||||
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
||||
</li>
|
||||
</ul>
|
||||
// #enddocregion heroes-styled
|
||||
|
||||
// #docregion selectedHero-click
|
||||
<li *ngFor="#hero of heroes" (click)="onSelect(hero)">
|
||||
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
||||
</li>
|
||||
// #enddocregion selectedHero-click
|
||||
|
||||
// #docregion selectedHero-details
|
||||
<h2>{{selectedHero.name}} details!</h2>
|
||||
<div><label>id: </label>{{selectedHero.id}}</div>
|
||||
<div>
|
||||
<label>name: </label>
|
||||
<input [(ngModel)]="selectedHero.name" placeholder="name">
|
||||
</div>
|
||||
// #enddocregion selectedHero-details
|
||||
|
||||
// #docregion ng-if
|
||||
<div *ngIf="selectedHero != null">
|
||||
<h2>{{selectedHero.name}} details!</h2>
|
||||
<div><label>id: </label>{{selectedHero.id}}</div>
|
||||
<div>
|
||||
<label>name: </label>
|
||||
<input [(ngModel)]="selectedHero.name" placeholder="name">
|
||||
</div>
|
||||
</div>
|
||||
// #enddocregion ng-if
|
||||
|
||||
// #docregion hero-array-1
|
||||
final List<Hero> heroes = mockHeroes;
|
||||
// #enddocregion hero-array-1
|
||||
|
||||
// #docregion heroes-template-1
|
||||
<h2>My Heroes</h2>
|
||||
<ul class="heroes">
|
||||
<li>
|
||||
<!-- each hero goes here -->
|
||||
</li>
|
||||
</ul>
|
||||
// #enddocregion heroes-template-1
|
||||
|
||||
// #docregion heroes-ngfor-1
|
||||
<li *ngFor="#hero of heroes">
|
||||
// #enddocregion heroes-ngfor-1
|
||||
|
||||
// #docregion class-selected-1
|
||||
[class.selected]="hero == selectedHero"
|
||||
// #enddocregion class-selected-1
|
||||
|
||||
// #docregion class-selected-2
|
||||
<li *ngFor="#hero of heroes"
|
||||
[class.selected]="hero == selectedHero"
|
||||
(click)="onSelect(hero)">
|
||||
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
||||
</li>
|
||||
// #enddocregion class-selected-2
|
114
public/docs/_examples/toh-2/dart/lib/app_component.dart
Normal file
114
public/docs/_examples/toh-2/dart/lib/app_component.dart
Normal file
@ -0,0 +1,114 @@
|
||||
// #docregion pt2
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
class Hero {
|
||||
final int id;
|
||||
String name;
|
||||
|
||||
Hero(this.id, this.name);
|
||||
}
|
||||
|
||||
@Component(
|
||||
selector: 'my-app',
|
||||
template: '''
|
||||
<h1>{{title}}</h1>
|
||||
<h2>My Heroes</h2>
|
||||
<ul class="heroes">
|
||||
<li *ngFor="#hero of heroes"
|
||||
[class.selected]="hero == selectedHero"
|
||||
(click)="onSelect(hero)">
|
||||
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
||||
</li>
|
||||
</ul>
|
||||
<div *ngIf="selectedHero != null">
|
||||
<h2>{{selectedHero.name}} details!</h2>
|
||||
<div><label>id: </label>{{selectedHero.id}}</div>
|
||||
<div>
|
||||
<label>name: </label>
|
||||
<input [(ngModel)]="selectedHero.name" placeholder="name"/>
|
||||
</div>
|
||||
</div>
|
||||
''',
|
||||
// #docregion styles-1
|
||||
styles: const [
|
||||
'''
|
||||
.selected {
|
||||
background-color: #CFD8DC !important;
|
||||
color: white;
|
||||
}
|
||||
.heroes {
|
||||
margin: 0 0 2em 0;
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
width: 10em;
|
||||
}
|
||||
.heroes li {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
left: 0;
|
||||
background-color: #EEE;
|
||||
margin: .5em;
|
||||
padding: .3em 0em;
|
||||
height: 1.6em;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.heroes li.selected:hover {
|
||||
color: white;
|
||||
}
|
||||
.heroes li:hover {
|
||||
color: #607D8B;
|
||||
background-color: #EEE;
|
||||
left: .1em;
|
||||
}
|
||||
.heroes .text {
|
||||
position: relative;
|
||||
top: -3px;
|
||||
}
|
||||
.heroes .badge {
|
||||
display: inline-block;
|
||||
font-size: small;
|
||||
color: white;
|
||||
padding: 0.8em 0.7em 0em 0.7em;
|
||||
background-color: #607D8B;
|
||||
line-height: 1em;
|
||||
position: relative;
|
||||
left: -1px;
|
||||
top: -4px;
|
||||
height: 1.8em;
|
||||
margin-right: .8em;
|
||||
border-radius: 4px 0px 0px 4px;
|
||||
}
|
||||
'''
|
||||
])
|
||||
// #enddocregion styles-1
|
||||
class AppComponent {
|
||||
final String title = 'Tour of Heroes';
|
||||
final List<Hero> heroes = mockHeroes;
|
||||
// #docregion selected-hero-1
|
||||
Hero selectedHero;
|
||||
// #enddocregion selected-hero-1
|
||||
|
||||
// #docregion on-select-1
|
||||
onSelect(Hero hero) {
|
||||
selectedHero = hero;
|
||||
}
|
||||
// #enddocregion on-select-1
|
||||
}
|
||||
// #enddocregion pt2
|
||||
|
||||
// #docregion hero-array
|
||||
final List<Hero> mockHeroes = [
|
||||
new Hero(11, "Mr. Nice"),
|
||||
new Hero(12, "Narco"),
|
||||
new Hero(13, "Bombasto"),
|
||||
new Hero(14, "Celeritas"),
|
||||
new Hero(15, "Magneta"),
|
||||
new Hero(16, "RubberMan"),
|
||||
new Hero(17, "Dynama"),
|
||||
new Hero(18, "Dr IQ"),
|
||||
new Hero(19, "Magma"),
|
||||
new Hero(20, "Tornado")
|
||||
];
|
||||
// #enddocregion hero-array
|
||||
|
||||
// #enddocregion pt2
|
15
public/docs/_examples/toh-2/dart/pubspec.yaml
Normal file
15
public/docs/_examples/toh-2/dart/pubspec.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
name: angular2_tour_of_heroes
|
||||
version: 0.0.1
|
||||
environment:
|
||||
sdk: '>=1.13.0 <2.0.0'
|
||||
dependencies:
|
||||
angular2: 2.0.0-beta.1
|
||||
browser: ^0.10.0
|
||||
dart_to_js_script_rewriter: ^0.1.0+4
|
||||
transformers:
|
||||
- angular2:
|
||||
platform_directives:
|
||||
- package:angular2/common.dart#COMMON_DIRECTIVES
|
||||
platform_pipes:
|
||||
- package:angular2/common.dart#COMMON_PIPES
|
||||
entry_points: web/main.dart
|
14
public/docs/_examples/toh-2/dart/web/index.html
Normal file
14
public/docs/_examples/toh-2/dart/web/index.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Angular 2 Tour of Heroes</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<script defer src="main.dart" type="application/dart"></script>
|
||||
<script defer src="packages/browser/dart.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<my-app>Loading...</my-app>
|
||||
</body>
|
||||
</html>
|
9
public/docs/_examples/toh-2/dart/web/main.dart
Normal file
9
public/docs/_examples/toh-2/dart/web/main.dart
Normal file
@ -0,0 +1,9 @@
|
||||
// #docregion pt2
|
||||
import 'package:angular2/bootstrap.dart';
|
||||
|
||||
import 'package:angular2_tour_of_heroes/app_component.dart';
|
||||
|
||||
main() {
|
||||
bootstrap(AppComponent);
|
||||
}
|
||||
// #enddocregion pt2
|
@ -1,14 +1,305 @@
|
||||
include ../_util-fns
|
||||
|
||||
:marked
|
||||
We're working on the Dart version of this case study.
|
||||
In the meantime, please see these resources:
|
||||
# It Takes Many Heroes
|
||||
Our story needs more heroes.
|
||||
We’ll expand our Tour of Heroes app to display a list of heroes,
|
||||
allow the user to select a hero, and display the hero’s details.
|
||||
|
||||
* [Master/Detail](/docs/ts/latest/tutorial/toh-pt2.html):
|
||||
The TypeScript version of this chapter
|
||||
Let’s take stock of what we’ll need to display a list of heroes.
|
||||
First, we need a list of heroes. We want to display those heroes in the view’s template,
|
||||
so we’ll need a way to do that.
|
||||
|
||||
* [Dart source code](https://github.com/angular/angular.io/tree/master/public/docs/_examples/toh-5/dart):
|
||||
A preliminary Dart version of the Tour of Heroes app,
|
||||
featuring the hero editor, a master/detail page,
|
||||
multiple components, services, and routing
|
||||
.callout.is-helpful
|
||||
header Source code
|
||||
:marked
|
||||
The complete source code for the example app in this chapter is
|
||||
[in GitHub](https://github.com/angular/angular.io/tree/master/public/docs/_examples/toh-2/dart).
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Where We Left Off
|
||||
Before we continue with Part 2 of the Tour of Heroes,
|
||||
let’s verify we have the following structure after [Part 1](./toh-pt1.html).
|
||||
If not, we’ll need to go back to Part 1 and figure out what we missed.
|
||||
|
||||
.filetree
|
||||
.file angular2_tour_of_heroes
|
||||
.children
|
||||
.file lib
|
||||
.children
|
||||
.file app_component.dart
|
||||
.file web
|
||||
.children
|
||||
.file index.html
|
||||
.file main.dart
|
||||
.file pubspec.yaml
|
||||
:marked
|
||||
### Keep the app transpiling and running
|
||||
We want to start the Dart compiler, have it watch for changes, and start our server. We'll do this by typing
|
||||
|
||||
code-example(format="." language="bash").
|
||||
pub serve
|
||||
|
||||
:marked
|
||||
This will keep the application running while we continue to build the Tour of Heroes.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Displaying Our Heroes
|
||||
### Creating heroes
|
||||
Let’s create a list of ten heroes at the bottom of `app_component.dart`.
|
||||
|
||||
+makeExample('toh-2/dart/lib/app_component.dart', 'hero-array', 'app_component.dart (Hero list)')(format=".")
|
||||
|
||||
:marked
|
||||
The `mockHeroes` list is of type `Hero`, the class defined in part one,
|
||||
to create a list of heroes.
|
||||
We aspire to fetch this list of heroes from a web service, but let’s take small steps
|
||||
first and display mock heroes.
|
||||
|
||||
### Exposing heroes
|
||||
Let’s create a public property in `AppComponent` that exposes the heroes for binding.
|
||||
|
||||
+makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'hero-array-1', 'app_component.dart (Hero list property)')
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
We could have defined the heroes list here in this component class.
|
||||
But we know that ultimately we’ll get the heroes from a data service.
|
||||
Because we know where we are heading, it makes sense to separate the hero data
|
||||
from the class implementation from the start.
|
||||
:marked
|
||||
### Displaying heroes in a template
|
||||
Our component has `heroes`. Let’s create an unordered list in our template to display them.
|
||||
We’ll insert the following chunk of HTML below the title and above the hero details.
|
||||
|
||||
+makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'heroes-template-1', 'app_component.dart (Heroes template)')(format=".")
|
||||
|
||||
:marked
|
||||
Now we have a template that we can fill with our heroes.
|
||||
|
||||
### Listing heroes with ngFor
|
||||
|
||||
We want to bind the list of `heroes` in our component to our template, iterate over them,
|
||||
and display them individually.
|
||||
We’ll need some help from Angular to do this. Let’s do this step by step.
|
||||
|
||||
First modify the `<li>` tag by adding the built-in directive `*ngFor`.
|
||||
|
||||
+makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'heroes-ngfor-1', 'app_component.dart (ngFor)')
|
||||
|
||||
.alert.is-critical
|
||||
:marked
|
||||
The leading asterisk (`*`) in front of `ngFor` is a critical part of this syntax.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
The (`*`) prefix to `ngFor` indicates that the `<li>` element and its children
|
||||
constitute a master template.
|
||||
|
||||
The `ngFor` directive iterates over the `heroes` list returned by the `AppComponent.heroes` property
|
||||
and stamps out instances of this template.
|
||||
|
||||
The quoted text assigned to `ngFor` means
|
||||
“*take each hero in the `heroes` list, store it in the local `hero` variable,
|
||||
and make it available to the corresponding template instance*”.
|
||||
|
||||
The `#` prefix before "hero" identifies the `hero` as a local template variable.
|
||||
We can reference this variable within the template to access a hero’s properties.
|
||||
|
||||
Learn more about `ngFor` and local template variables in the
|
||||
[Displaying Data](../guide/displaying-data.html#ngFor) and
|
||||
[Template Syntax](../guide/template-syntax.html#ngFor) chapters.
|
||||
|
||||
:marked
|
||||
Now we insert some content between the `<li>` tags
|
||||
that uses the `hero` template variable to display the hero’s properties.
|
||||
|
||||
+makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'ng-for', 'app_component.dart (ngFor template)')(format=".")
|
||||
|
||||
:marked
|
||||
When the browser refreshes, we see a list of heroes!
|
||||
|
||||
### Styling our heroes
|
||||
Our list of heroes looks pretty bland.
|
||||
We want to make it visually obvious to a user which hero we are hovering over and which hero is selected.
|
||||
|
||||
Let’s add some styles to our component by setting the `styles` argument of the `@Component` annotation
|
||||
to the following CSS classes:
|
||||
|
||||
+makeExample('toh-2/dart/lib/app_component.dart', 'styles-1', 'app_component.dart (Styling)')(format=".")
|
||||
|
||||
:marked
|
||||
Notice that we again use the triple-quote notation for multi-line strings.
|
||||
|
||||
When we assign styles to a component they are scoped to that specific component.
|
||||
Our styles will only apply to our `AppComponent` and won't "leak" to the outer HTML.
|
||||
|
||||
Our template for displaying the heroes should now look like this:
|
||||
|
||||
+makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'heroes-styled', 'app_component.dart (Styled heroes)')
|
||||
|
||||
:marked
|
||||
That's a lot of styles! We can put them inline as shown here, or we can move them out to their own file which will make it easier to code our component.
|
||||
We'll do this in a later chapter. For now let's keep rolling.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Selecting a Hero
|
||||
We have a list of heroes and we have a single hero displayed in our app.
|
||||
The list and the single hero are not connected in any way.
|
||||
We want the user to select a hero from our list, and have the selected hero appear in the details view.
|
||||
This UI pattern is widely known as "master-detail".
|
||||
In our case, the master is the heroes list and the detail is the selected hero.
|
||||
|
||||
Let’s connect the master to the detail through a `selectedHero` component property bound to a click event.
|
||||
|
||||
### Click event
|
||||
We modify the `<li>` by inserting an Angular event binding to its click event.
|
||||
|
||||
+makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'selectedHero-click', 'app_component.dart (Capturing the click event)')(format=".")
|
||||
|
||||
:marked
|
||||
Focus on the event binding
|
||||
code-example(format="." language="bash").
|
||||
(click)="onSelect(hero)"
|
||||
:marked
|
||||
The parentheses identify the `<li>` element’s `click` event as the target.
|
||||
The expression to the right of the equal sign calls the `AppComponent` method, `onSelect()`,
|
||||
passing the local template variable `hero` as an argument.
|
||||
That’s the same `hero` variable we defined previously in the `ngFor`.
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about Event Binding in the
|
||||
[User Input](../guide/user-input.html) and
|
||||
[Templating Syntax](../guide/template-syntax.html#event-binding) chapters.
|
||||
:marked
|
||||
### Add the click handler
|
||||
Our event binding refers to an `onSelect` method that doesn’t exist yet.
|
||||
We’ll add that method to our component now.
|
||||
|
||||
What should that method do? It should set the component’s selected hero to the hero that the user clicked.
|
||||
|
||||
Our component doesn’t have a “selected hero” yet either. We’ll start there.
|
||||
|
||||
### Expose the selected hero
|
||||
We no longer need the static `hero` property of the `AppComponent`.
|
||||
**Replace** it with this simple `selectedHero` property:
|
||||
|
||||
+makeExample('toh-2/dart/lib/app_component.dart', 'selected-hero-1', 'app_component.dart (selectedHero)')
|
||||
|
||||
:marked
|
||||
We’ve decided that none of the heroes should be selected before the user picks a hero so
|
||||
we won’t initialize the `selectedHero` as we were doing with `hero`.
|
||||
|
||||
Now **add an `onSelect` method** that sets the `selectedHero` property to the `hero` the user clicked.
|
||||
+makeExample('toh-2/dart/lib/app_component.dart', 'on-select-1', 'app_component.dart (onSelect)')(format=".")
|
||||
|
||||
:marked
|
||||
We will be showing the selected hero's details in our template.
|
||||
At the moment, it is still referring to the old `hero` property.
|
||||
Let’s fix the template to bind to the new `selectedHero` property.
|
||||
|
||||
+makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'selectedHero-details', 'app_component.dart (Binding to the selectedHero\'s name)')(format=".")
|
||||
:marked
|
||||
### Hide the empty detail with ngIf
|
||||
|
||||
When our app loads we see a list of heroes, but a hero is not selected.
|
||||
The `selectedHero` is `undefined`.
|
||||
That’s why we'll see the following error in the browser’s console:
|
||||
|
||||
code-example(language="html").
|
||||
EXCEPTION: TypeError: Cannot read property 'name' of undefined in [null]
|
||||
|
||||
:marked
|
||||
Remember that we are displaying `selectedHero.name` in the template.
|
||||
This name property does not exist because `selectedHero` itself is undefined.
|
||||
|
||||
We'll address this problem by keeping the hero detail out of the DOM until there is a selected hero.
|
||||
|
||||
We wrap the HTML hero detail content of our template with a `<div>`.
|
||||
Then we add the `ngIf` built-in directive and set it to the `selectedHero` property of our component.
|
||||
|
||||
+makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'ng-if', 'app_component.dart (ngIf)')(format=".")
|
||||
|
||||
.alert.is-critical
|
||||
:marked
|
||||
Remember that the leading asterisk (`*`) in front of `ngIf` is
|
||||
a critical part of this syntax.
|
||||
:marked
|
||||
When there is no `selectedHero`, the `ngIf` directive removes the hero detail HTML from the DOM.
|
||||
There will be no hero detail elements and no bindings to worry about.
|
||||
|
||||
When the user picks a hero, `selectedHero` isn't `null` anymore and
|
||||
`ngIf` puts the hero detail content into the DOM and evaluates the nested bindings.
|
||||
.l-sub-section
|
||||
:marked
|
||||
`ngIf` and `ngFor` are called “structural directives” because they can change the
|
||||
structure of portions of the DOM.
|
||||
In other words, they give structure to the way Angular displays content in the DOM.
|
||||
|
||||
Learn more about `ngIf`, `ngFor` and other structural directives in the
|
||||
[Structural Directives](../guide/structural-directives.html) and
|
||||
[Template Syntax](../guide/template-syntax.html#directives) chapters.
|
||||
|
||||
:marked
|
||||
The browser refreshes and we see the list of heroes but not the selected hero detail.
|
||||
The `ngIf` keeps it out of the DOM as long as the `selectedHero` is undefined.
|
||||
When we click on a hero in the list, the selected hero displays in the hero details.
|
||||
Everything is working as we expect.
|
||||
|
||||
### Styling the selection
|
||||
|
||||
We see the selected hero in the details area below but we can’t quickly locate that hero in the list above.
|
||||
We can fix that by applying the `selected` CSS class to the appropriate `<li>` in the master list.
|
||||
For example, when we select Magneta from the heroes list,
|
||||
we can make it pop out visually by giving it a subtle background color as shown here.
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/toh/heroes-list-selected.png' alt="Selected hero")
|
||||
:marked
|
||||
We’ll add a property binding on `class` for the `selected` class to the template. We'll set this to an expression that compares the current `selectedHero` to the `hero`.
|
||||
|
||||
The key is the name of the CSS class (`selected`). The value is `true` if the two heroes match and `false` otherwise.
|
||||
We’re saying “*apply the `selected` class if the heroes match, remove it if they don’t*”.
|
||||
+makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'class-selected-1', 'app_component.dart (Setting the CSS class)')(format=".")
|
||||
:marked
|
||||
Notice in the template that the `class.selected` is surrounded in square brackets (`[]`).
|
||||
This is the syntax for a Property Binding, a binding in which data flows one way
|
||||
from the data source (the expression `hero == selectedHero`) to a property of `class`.
|
||||
+makeExample('toh-2/dart-snippets/app_component_snippets_pt2.dart', 'class-selected-2', 'app_component.dart (Styling each hero)')(format=".")
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn more about [Property Binding](../guide/template-syntax.html#property-binding)
|
||||
in the Template Syntax chapter.
|
||||
|
||||
:marked
|
||||
The browser reloads our app.
|
||||
We select the hero Magneta and the selection is clearly identified by the background color.
|
||||
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/toh/heroes-list-1.png' alt="Output of heroes list app")
|
||||
|
||||
:marked
|
||||
We select a different hero and the tell-tale color switches to that hero.
|
||||
|
||||
Here's the complete `app_component.dart` as it stands now:
|
||||
|
||||
+makeExample('toh-2/dart/lib/app_component.dart', 'pt2', 'app_component.dart')
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## The Road We’ve Travelled
|
||||
Here’s what we achieved in this chapter:
|
||||
|
||||
* Our Tour of Heroes now displays a list of selectable heroes
|
||||
* We added the ability to select a hero and show the hero’s details
|
||||
* We learned how to use the built-in directives `ngIf` and `ngFor` in a component’s template
|
||||
|
||||
### The Road Ahead
|
||||
Our Tour of Heroes has grown, but it’s far from complete.
|
||||
We can't put the entire app into a single component.
|
||||
We need to break it up into sub-components and teach them to work together
|
||||
as we learn in the [next chapter](toh-pt3.html).
|
||||
|
Loading…
x
Reference in New Issue
Block a user