refactor(docs-infra): fix template eslint issues in aio examples (#44557)

the aio examples have various eslint issues regarding template rules, those
are currently turned off and TODO comments have been added to them in the
examples eslintrc, fix such issues and remove the respective TODO comments

this also includes examples refactoring to use buttons for better accessibility,
this change tries to make the smallest amound of changes to the examples' behaviors
and designs/UI

PR Close #44557
This commit is contained in:
Dario Piotrowicz 2021-12-23 11:28:40 +01:00 committed by Dylan Hunn
parent 226fac67bd
commit 5498a35789
70 changed files with 501 additions and 307 deletions

View file

@ -13,12 +13,12 @@
"plugin:@angular-eslint/template/recommended"
],
"rules": {
"@angular-eslint/template/accessibility-alt-text": "off", // TODO: Fix the code violating this rule and enable it.
"@angular-eslint/template/accessibility-elements-content": "off", // TODO: Fix the code violating this rule and enable it.
"@angular-eslint/template/accessibility-label-has-associated-control": "off", // TODO: Fix the code violating this rule and enable it.
"@angular-eslint/template/accessibility-alt-text": "error",
"@angular-eslint/template/accessibility-elements-content": "error",
"@angular-eslint/template/accessibility-label-has-associated-control": "error",
"@angular-eslint/template/accessibility-table-scope": "error",
"@angular-eslint/template/accessibility-valid-aria": "error",
"@angular-eslint/template/click-events-have-key-events": "off", // TODO: Fix the code violating this rule and enable it.
"@angular-eslint/template/click-events-have-key-events": "error",
"@angular-eslint/template/eqeqeq": "off",
"@angular-eslint/template/mouse-events-have-key-events": "error",
"@angular-eslint/template/no-autofocus": "error",

View file

@ -51,7 +51,7 @@
<p></p>
<div *ngIf="showImage">
<!-- #docregion src -->
<img [src]="movie.imageurl">
<img [src]="movie.imageurl" [alt]="movie.title">
<!-- #enddocregion src -->
</div>

View file

@ -61,6 +61,7 @@
[style.height.px]="50"
[style.margin.px]="2"
[src]="movie.imageurl"
[alt]="movie.title"
[title]="movie.title">
</td>
<td>{{movie.title}}</td>

View file

@ -1,9 +1,9 @@
<ul class="heroes">
<li *ngFor="let hero of heroes"
[@shrinkOut]="'in'" (click)="removeHero(hero.id)">
<div class="inner">
[@shrinkOut]="'in'">
<button class="inner" (click)="removeHero(hero.id)">
<span class="badge">{{ hero.id }}</span>
<span>{{ hero.name }}</span>
</div>
<span class="name">{{ hero.name }}</span>
</button>
</li>
</ul>
</ul>

View file

@ -19,11 +19,11 @@ import { Hero } from './hero';
template: `
<ul class="heroes">
<li *ngFor="let hero of heroes"
[@flyInOut]="'in'" (click)="removeHero(hero.id)">
<div class="inner">
[@flyInOut]="'in'">
<button class="inner" (click)="removeHero(hero.id)">
<span class="badge">{{ hero.id }}</span>
<span>{{ hero.name }}</span>
</div>
<span class="name">{{ hero.name }}</span>
</button>
</li>
</ul>
`,

View file

@ -19,12 +19,11 @@ import { Hero } from './hero';
selector: 'app-hero-list-groups',
template: `
<ul class="heroes">
<li *ngFor="let hero of heroes"
[@flyInOut]="'in'" (click)="removeHero(hero.id)">
<div class="inner">
<li *ngFor="let hero of heroes" [@flyInOut]="'in'">
<button class="inner" (click)="removeHero(hero.id)">
<span class="badge">{{ hero.id }}</span>
<span>{{ hero.name }}</span>
</div>
<span class="name">{{ hero.name }}</span>
</button>
</li>
</ul>
`,
@ -33,7 +32,7 @@ import { Hero } from './hero';
animations: [
trigger('flyInOut', [
state('in', style({
width: 120,
width: '*',
transform: 'translateX(0)', opacity: 1
})),
transition(':enter', [
@ -41,7 +40,7 @@ import { Hero } from './hero';
group([
animate('0.3s 0.1s ease', style({
transform: 'translateX(0)',
width: 120
width: '*'
})),
animate('0.3s ease', style({
opacity: 1

View file

@ -1,25 +1,56 @@
.heroes {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 15em;
}
.heroes li {
overflow:hidden;
margin: .5em 0;
display: flex;
align-items: center;
width: 100%;
overflow: hidden;
}
.heroes li > .inner {
cursor: pointer;
.heroes .inner {
flex: 1;
background-color: #EEE;
padding: .3rem 0;
height: 1.6rem;
margin: .5em;
padding: 0;
border-radius: 4px;
display: flex;
align-items: stretch;
}
.heroes li:hover > .inner {
color: black;
background-color: #DDD;
transform: translateX(.1em);
.heroes button.inner {
cursor: pointer;
font-size: inherit;
}
.heroes button.inner:hover {
color: #2c3a41;
background-color: #e6e6e6;
left: .1em;
}
.heroes button.inner:active {
background-color: #525252;
color: #fafafa;
}
.heroes button.inner.selected {
background-color: black;
color: white;
}
.heroes button.inner.selected:hover {
background-color: #505050;
color: white;
}
.heroes button.inner.selected:active {
background-color: black;
color: white;
}
.heroes .badge {
@ -27,17 +58,17 @@
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color: #3d5157;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
min-width: 16px;
text-align: right;
background-color: #405061;
line-height: 1em;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
.heroes .name {
min-width: max-content;
padding: 0.5rem 0;
}
label {
display: block;
padding-bottom: .5rem;

View file

@ -11,7 +11,7 @@
<li *ngFor="let hero of heroes" class="hero">
<div class="inner">
<span class="badge">{{ hero.id }}</span>
<span>{{ hero.name }}</span>
<span class="name">{{ hero.name }}</span>
</div>
</li>
</ul>

View file

@ -1,7 +1,8 @@
<!--#docregion binding -->
<li>{{hero.name}}</li>
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
<li (click)="selectHero(hero)"></li>
<button (click)="selectHero(hero)">
{{hero.name}}
</button>
<!--#enddocregion binding -->
<!--#docregion structural -->

View file

@ -3,8 +3,10 @@
<p><em>Select a hero from the list to see details.</em></p>
<ul>
<li *ngFor="let hero of heroes" (click)="selectHero(hero)">
{{hero.name}}
<li *ngFor="let hero of heroes">
<button (click)="selectHero(hero)">
{{hero.name}}
</button>
</li>
</ul>

View file

@ -4,13 +4,21 @@ ul {
li {
list-style-type: none;
padding: 1rem;
background-color: aliceblue;
border: 1px solid #444;
margin-bottom: .5rem;
display: flex;
}
li:hover {
li button {
background-color: aliceblue;
flex: 1;
padding: 1rem;
margin: 0;
text-align: left;
border-radius: 0;
}
li button:hover, li button:active {
background-color: #444;
color: white;
cursor: pointer;

View file

@ -7,45 +7,68 @@
}
/* #enddocregion heroes */
.heroes {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 15em;
}
.heroes li {
display: flex;
}
.heroes button {
flex: 1;
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
padding: 0;
height: 1.6em;
border-radius: 4px;
display: flex;
align-items: stretch;
height: 1.8em;
}
.heroes li.selected:hover {
background-color: #BBD8DC !important;
color: white;
}
.heroes li:hover {
color: #607D8B;
background-color: #DDD;
.heroes button:hover {
color: #2c3a41;
background-color: #e6e6e6;
left: .1em;
}
.heroes .text {
position: relative;
top: -3px;
.heroes button:active {
background-color: #525252;
color: #fafafa;
}
.heroes button.selected {
background-color: black;
color: white;
}
.heroes button.selected:hover {
background-color: #505050;
color: white;
}
.heroes button.selected:active {
background-color: black;
color: white;
}
.heroes .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color: #607D8B;
background-color: #405061;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
.selected {
background-color: #CFD8DC !important;
color: white;
.heroes .name {
align-self: center;
}

View file

@ -1,17 +1,19 @@
<h1>{{title}}</h1>
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="let hero of heroes"
[class.selected]="hero === selectedHero"
(click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
<li *ngFor="let hero of heroes">
<button [class.selected]="hero === selectedHero" (click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span>
<span class="name">{{hero.name}}</span>
</button>
</li>
</ul>
<div *ngIf="selectedHero">
<h2>{{selectedHero.name}} details!</h2>
<div><label>id: </label>{{selectedHero.id}}</div>
<div>id: {{selectedHero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="selectedHero.name" placeholder="name"/>
<label>
name: <input [(ngModel)]="selectedHero.name" placeholder="name"/>
</label>
</div>
</div>

View file

@ -34,6 +34,9 @@
<h4>Click to see event target class:</h4>
<!-- eslint-disable @angular-eslint/template/click-events-have-key-events -->
<div class="parent-div" (click)="onClickMe($event)" clickable>Click me (parent)
<div class="child-div">Click me too! (child) </div>
</div>
@ -47,3 +50,5 @@
<div (click)="onSave()" clickable>
<button (click)="onSave()">Save with propagation</button>
</div>
<!-- eslint-enable @angular-eslint/template/click-events-have-key-events -->

View file

@ -1,7 +1,7 @@
<div class="detail">
<p>This is the ItemDetailComponent</p>
<!-- #docregion line-through -->
<img src="{{itemImageUrl}}" [style.display]="displayNone">
<img src="{{itemImageUrl}}" alt="{{item.name}}" [style.display]="displayNone">
<span [style.text-decoration]="lineThrough">{{ item.name }}
</span>
<button (click)="delete()">Delete</button>

View file

@ -10,7 +10,7 @@ describe('Hierarchical dependency injection', () => {
income: '',
// queries
heroEl: element.all(by.css('app-heroes-list li')).get(0), // first hero
heroEl: element.all(by.css('app-heroes-list li button')).get(0), // first hero
heroCardEl: element(by.css('app-heroes-list app-hero-tax-return')), // first hero tax-return
taxReturnNameEl: element.all(by.css('app-heroes-list app-hero-tax-return #name')).get(0),
incomeInputEl: element.all(by.css('app-heroes-list app-hero-tax-return input')).get(0),

View file

@ -2,7 +2,7 @@
<div class="msg" [class.canceled]="message==='Canceled'">{{message}}</div>
<fieldset>
<span id=name>{{taxReturn.name}}</span>
<label id=tid>TID: {{taxReturn.tid}}</label>
<span id=tid>TID: {{taxReturn.tid}}</span>
</fieldset>
<fieldset>
<label>
@ -10,7 +10,7 @@
</label>
</fieldset>
<fieldset>
<label>Tax: {{taxReturn.tax}}</label>
<span>Tax: {{taxReturn.tax}}</span>
</fieldset>
<fieldset>
<button (click)="onSaved()">Save</button>

View file

@ -11,8 +11,8 @@ import { HeroesService } from './heroes.service';
<div>
<h3>Hero Tax Returns</h3>
<ul>
<li *ngFor="let hero of heroes | async"
(click)="showTaxReturn(hero)">{{hero.name}}
<li *ngFor="let hero of heroes | async">
<button (click)="showTaxReturn(hero)">{{hero.name}}</button>
</li>
</ul>
<app-hero-tax-return
@ -22,7 +22,13 @@ import { HeroesService } from './heroes.service';
</app-hero-tax-return>
</div>
`,
styles: [ 'li {cursor: pointer;}' ]
styles: [`
li button {
font-size: inherit;
margin: 0.3rem;
padding: 0.5rem;
}
`]
})
export class HeroesListComponent {
heroes: Observable<Hero[]>;

View file

@ -9,21 +9,20 @@
}
.heroes li {
position: relative;
display: flex;
width: 100%;
}
.heroes li:hover {
left: .1em;
}
.heroes a {
.heroes button.delete {
color: black;
display: block;
font-size: 1.2rem;
background-color: #eee;
margin: .5em 0;
padding: .5em 0;
border-radius: 4px;
border-radius: 0 4px 4px 0;
}
.heroes a:hover {
@ -40,8 +39,18 @@
border-radius: 4px 0 0 4px;
}
.heroes button.edit {
flex: 1;
text-align: left;
padding: 0;
margin-right: 0;
display: flex;
align-items: center;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
button.delete {
position: absolute;
right: -8px;
top: 5px;
background-color: gray;
@ -53,6 +62,4 @@ button.delete {
.heroes input {
max-width: 12rem;
padding: .25rem;
position: absolute;
top: 8px;
}

View file

@ -14,7 +14,7 @@
<ul class="heroes">
<li *ngFor="let hero of heroes">
<a (click)="edit(hero)">
<button class="edit" (click)="edit(hero)">
<span class="badge">{{ hero.id || -1 }}</span>
<span *ngIf="hero!==editHero">{{hero.name}}</span>
<input type="text"
@ -22,7 +22,7 @@
[(ngModel)]="hero.name"
(blur)="update()"
(keyup.enter)="update()">
</a>
</button>
<button class="delete" title="delete hero"
(click)="delete(hero)">x</button>
</li>

View file

@ -27,11 +27,11 @@
<!--#enddocregion i18n-attribute-solo-id-->
<!--#docregion i18n-title-->
<img [src]="logo" title="Angular logo">
<img [src]="logo" title="Angular logo" alt="Angular logo">
<!--#enddocregion i18n-title-->
<!--#docregion i18n-duplicate-custom-id-->
<h3 i18n="@@myId">Hello</h3>
<!-- ... -->
<p i18n="@@myId">Good bye</p>
<!--#enddocregion i18n-duplicate-custom-id-->
<!--#enddocregion i18n-duplicate-custom-id-->

View file

@ -10,7 +10,7 @@
<br />
<!--#docregion i18n-title-translate-->
<img [src]="logo" i18n-title title="Angular logo" />
<img [src]="logo" i18n-title title="Angular logo" alt="Angular logo"/>
<!--#enddocregion i18n-title-translate-->
<br>
<button (click)="inc(1)">+</button> <button (click)="inc(-1)">-</button>

View file

@ -11,7 +11,7 @@
<!-- #docregion component-property -->
<p>{{title}}</p>
<div><img src="{{itemImageUrl}}"></div>
<div><img alt="item" src="{{itemImageUrl}}"></div>
<!-- #enddocregion component-property -->
<h3>Evaluating template expressions </h3>
@ -35,7 +35,7 @@
<h3>Component context, properties of app.component.ts:</h3>
<!-- #docregion component-context -->
<h4>{{recommended}}</h4>
<img [src]="itemImageUrl2">
<img alt="item 2" [src]="itemImageUrl2">
<!-- #enddocregion component-context -->
</div>

View file

@ -1,5 +1,4 @@
<a id="top"></a>
<h1>Lifecycle Hooks</h1>
<h1 id="top">Lifecycle Hooks</h1>
<a href="#hooks">Peek-a-boo: (most) lifecycle hooks</a>
<a href="#spy">Spy: directive with OnInit & OnDestroy</a>
<a href="#onchanges">OnChanges</a>
@ -8,36 +7,29 @@
<a href="#after-content">AfterContentInit & AfterContentChecked</a>
<a href="#counter">Counter: OnChanges + Spy directive</a>
<a id="hooks"></a>
<peek-a-boo-parent></peek-a-boo-parent>
<peek-a-boo-parent id="hooks"></peek-a-boo-parent>
<a href="#top">back to top</a>
<hr />
<a id="spy"></a>
<spy-parent></spy-parent>
<spy-parent id="spy"></spy-parent>
<a href="#top">back to top</a>
<hr />
<a id="onchanges"></a>
<on-changes-parent></on-changes-parent>
<on-changes-parent id="onchanges"></on-changes-parent>
<a href="#top">back to top</a>
<hr />
<a id="docheck"></a>
<do-check-parent></do-check-parent>
<do-check-parent id="docheck"></do-check-parent>
<a href="#top">back to top</a>
<hr />
<a id="after-view"></a>
<after-view-parent></after-view-parent>
<after-view-parent id="after-view"></after-view-parent>
<a href="#top">back to top</a>
<hr />
<a id="after-content"></a>
<after-content-parent></after-content-parent>
<after-content-parent id="after-content"></after-content-parent>
<a href="#top">back to top</a>
<hr />
<a id="counter"></a>
<counter-parent></counter-parent>
<counter-parent id="counter"></counter-parent>
<a href="#top">back to top</a>

View file

@ -1,5 +1,5 @@
<h2>{{title}}</h2>
<label for="power-input"><label>Power: </label>
<label for="power-input">Power: </label>
<input type="text" id="power-input" [(ngModel)]="power">
<label for="hero-name">Hero.name: </label>
<input type="text" id="hero-name" [(ngModel)]="hero.name">

View file

@ -11,7 +11,7 @@ import { Observable } from 'rxjs';
selector: 'app-zippy',
template: `
<div class="zippy">
<div (click)="toggle()">Toggle</div>
<button (click)="toggle()">Toggle</button>
<div [hidden]="!visible">
<ng-content></ng-content>
</div>

View file

@ -1,5 +1,4 @@
<a id="toc"></a>
<h1>Pipes</h1>
<h1 id="toc">Pipes</h1>
<a href="#happy-birthday1">Happy Birthday v1</a>
<a href="#birthday-date-pipe">Birthday DatePipe</a>
<a href="#happy-birthday2">Happy Birthday v2</a>
@ -14,13 +13,11 @@
<hr>
<a id="happy-birthday1"></a>
<h2>Hero Birthday v1</h2>
<h2 id="happy-birthday1">Hero Birthday v1</h2>
<app-hero-birthday></app-hero-birthday>
<hr>
<a id="birthday-date-pipe"></a>
<h2>Birthday DatePipe</h2>
<h2 id="birthday-date-pipe">Birthday DatePipe</h2>
<!-- #docregion hero-birthday-template -->
<p>The hero's birthday is {{ birthday | date }}</p>
<!-- #enddocregion hero-birthday-template-->
@ -30,13 +27,11 @@
<!-- #enddocregion format-birthday-->
<hr>
<a id="happy-birthday2"></a>
<h2>Hero Birthday v2</h2>
<h2 id="happy-birthday2">Hero Birthday v2</h2>
<app-hero-birthday2></app-hero-birthday2>
<hr>
<a id="birthday-pipe-chaining"></a>
<h2>Birthday Pipe Chaining</h2>
<h2 id="birthday-pipe-chaining">Birthday Pipe Chaining</h2>
<p>
<!-- #docregion chained-birthday -->
The chained hero's birthday is
@ -55,31 +50,24 @@
{{ ( birthday | date:'fullDate' ) | uppercase}}
</p>
<hr>
<a id="power-booster"></a>
<app-power-booster></app-power-booster>
<app-power-booster id="power-booster"></app-power-booster>
<hr>
<a id="power-boost-calc"></a>
<app-power-boost-calculator>loading</app-power-boost-calculator>
<app-power-boost-calculator id="power-boost-calc">loading</app-power-boost-calculator>
<hr>
<a id="flying-heroes"></a>
<app-flying-heroes></app-flying-heroes>
<app-flying-heroes id="flying-heroes"></app-flying-heroes>
<hr>
<a id="flying-heroes-impure"></a>
<app-flying-heroes-impure></app-flying-heroes-impure>
<app-flying-heroes-impure id="flying-heroes-impure"></app-flying-heroes-impure>
<hr>
<a id="hero-message"></a>
<!-- async examples at the top so can see them in action -->
<app-hero-async-message></app-hero-async-message>
<app-hero-async-message id="hero-message"></app-hero-async-message>
<hr>
<a id="hero-list"></a>
<app-hero-list></app-hero-list>
<app-hero-list id="hero-list"></app-hero-list>
<hr>
<a id="pipe-precedence"></a>
<app-precedence></app-precedence>
<app-precedence id="pipe-precedence"></app-precedence>
<hr>

View file

@ -4,7 +4,7 @@
<h1>Property Binding with Angular</h1>
<h2>Binding the src property of an image:</h2>
<!-- #docregion property-binding -->
<img [src]="itemImageUrl">
<img alt="item" [src]="itemImageUrl">
<!-- #enddocregion property-binding -->
<hr />
@ -47,8 +47,8 @@
<h2>Property binding and interpolation</h2>
<!-- #docregion property-binding-interpolation -->
<p><img src="{{itemImageUrl}}"> is the <i>interpolated</i> image.</p>
<p><img [src]="itemImageUrl"> is the <i>property bound</i> image.</p>
<p><img alt="Interpolated item" src="{{itemImageUrl}}"> is the <i>interpolated</i> image.</p>
<p><img alt="Property Bound item" [src]="itemImageUrl"> is the <i>property bound</i> image.</p>
<p><span>"{{interpolationTitle}}" is the <i>interpolated</i> title.</span></p>
<p>"<span [innerHTML]="propertyTitle"></span>" is the <i>property bound</i> title.</p>

View file

@ -3,8 +3,8 @@ import { browser, element, by } from 'protractor';
describe('Reactive forms', () => {
const nameEditor = element(by.css('app-name-editor'));
const profileEditor = element(by.css('app-profile-editor'));
const nameEditorLink = element(by.cssContainingText('app-root > nav > a', 'Name Editor'));
const profileEditorLink = element(by.cssContainingText('app-root > nav > a', 'Profile Editor'));
const nameEditorButton = element(by.cssContainingText('app-root > nav > button', 'Name Editor'));
const profileEditorButton = element(by.cssContainingText('app-root > nav > button', 'Profile Editor'));
beforeAll(() => browser.get(''));
@ -14,7 +14,7 @@ describe('Reactive forms', () => {
const nameText = 'John Smith';
beforeAll(async () => {
await nameEditorLink.click();
await nameEditorButton.click();
});
beforeEach(async () => {
@ -66,12 +66,12 @@ describe('Reactive forms', () => {
};
beforeAll(async () => {
await profileEditorLink.click();
await profileEditorButton.click();
});
beforeEach(async () => {
await browser.get('');
await profileEditorLink.click();
await profileEditorButton.click();
});
it('should be invalid by default', async () => {

View file

@ -1,3 +1,4 @@
nav a {
nav button {
padding: 1rem;
font-size: inherit;
}

View file

@ -1,8 +1,8 @@
<h1>Reactive Forms</h1>
<nav>
<a (click)="toggleEditor('name')">Name Editor</a>
<a (click)="toggleEditor('profile')">Profile Editor</a>
<button (click)="toggleEditor('name')">Name Editor</button>
<button (click)="toggleEditor('profile')">Profile Editor</button>
</nav>
<app-name-editor *ngIf="showNameEditor"></app-name-editor>

View file

@ -1,5 +0,0 @@
<h3>Dashboard</h3>
<p>Session ID: {{ sessionId | async }}</p>
<a id="anchor"></a>
<p>Token: {{ token | async }}</p>

View file

@ -1,7 +1,7 @@
<h3>Dashboard</h3>
<p>Session ID: {{ sessionId | async }}</p>
<a id="anchor"></a>
<div id="anchor"></div>
<p>Token: {{ token | async }}</p>
Preloaded Modules

View file

@ -12,9 +12,9 @@ import { Title } from '@angular/platform-browser';
</p>
<ul>
<li><a (click)="setTitle('Good morning!')">Good morning</a>.</li>
<li><a (click)="setTitle('Good afternoon!')">Good afternoon</a>.</li>
<li><a (click)="setTitle('Good evening!')">Good evening</a>.</li>
<li><button (click)="setTitle('Good morning!')">Good morning</button></li>
<li><button (click)="setTitle('Good afternoon!')">Good afternoon</button></li>
<li><button (click)="setTitle('Good evening!')">Good evening</button></li>
</ul>
`,
})

View file

@ -5,27 +5,63 @@
padding: 0;
width: 15em;
}
.heroes li {
display: flex;
}
.heroes button {
flex: 1;
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
padding: 0;
height: 1.6em;
border-radius: 4px;
display: flex;
align-items: stretch;
height: 1.8em;
}
.heroes button:hover {
color: #2c3a41;
background-color: #e6e6e6;
left: .1em;
}
.heroes button:active {
background-color: #525252;
color: #fafafa;
}
.heroes button.selected {
background-color: black;
color: white;
}
.heroes button.selected:hover {
background-color: #505050;
color: white;
}
.heroes button.selected:active {
background-color: black;
color: white;
}
.heroes .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color: #607D8B;
background-color: #405061;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
.heroes .name {
align-self: center;
}

View file

@ -2,8 +2,11 @@
<div>
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="let hero of heroes | async" (click)="selectedHero=hero">
<span class="badge">{{hero.id}}</span> {{hero.name}}
<li *ngFor="let hero of heroes | async">
<button (click)="selectedHero=hero">
<span class="badge">{{hero.id}}</span>
<span class="name">{{hero.name}}</span>
</button>
</li>
</ul>
<div *ngIf="selectedHero">

View file

@ -1,6 +1,5 @@
<!-- #docplaster -->
<a id="toc"></a>
<h1>Template Syntax</h1>
<h1 id="toc">Template Syntax</h1>
<a href="#interpolation">Interpolation</a><br>
<a href="#expression-context">Expression context</a><br>
<a href="#statement-context">Statement context</a><br>
@ -47,7 +46,7 @@
<h3>
{{title}}
<img src="{{heroImageUrl}}" style="height:30px">
<img alt="{{hero.name}}" src="{{heroImageUrl}}" style="height:30px">
</h3>
<!-- "The sum of 1 + 1 is 2" -->
@ -120,7 +119,7 @@
<!--<img src="https://wpclipart.com/cartoon/people/hero/hero_silhoutte_T.png">-->
<!-- Public Domain terms of use: https://wpclipart.com/terms.html -->
<div class="special">Mental Model</div>
<img src="assets/images/hero.png">
<img [alt]="hero.name" src="assets/images/hero.png">
<button disabled>Save</button>
<br><br>
@ -140,7 +139,7 @@
<div>
<!-- #docregion property-binding-syntax-1 -->
<img [src]="heroImageUrl">
<img [alt]="hero.name" [src]="heroImageUrl">
<app-hero-detail [hero]="currentHero"></app-hero-detail>
<div [ngClass]="{'special': isSpecial}"></div>
<!-- #enddocregion property-binding-syntax-1 -->
@ -181,6 +180,7 @@ button</button>
<a class="to-toc" href="#toc">top</a>
<!-- eslint-disable @angular-eslint/template/accessibility-alt-text -->
<!-- property vs. attribute -->
<hr><h2 id="prop-vs-attrib">Property vs. Attribute (img examples)</h2>
<!-- examine the following <img> tag in the browser tools -->
@ -191,7 +191,7 @@ button</button>
<img [src]="iconUrl"/>
<img [attr.src]="villainImageUrl"/>
<!-- eslint-enable @angular-eslint/template/accessibility-alt-text -->
<a class="to-toc" href="#toc">top</a>
<!-- buttons -->
@ -211,7 +211,7 @@ button</button>
<!-- property binding -->
<hr><h2 id="property-binding">Property Binding</h2>
<img [src]="heroImageUrl">
<img [alt]="hero.name" [src]="heroImageUrl">
<button [disabled]="isUnchanged">Cancel is disabled</button>
<div [ngClass]="classes">[ngClass] binding to the classes property</div>
<app-hero-detail [hero]="currentHero"></app-hero-detail>
@ -221,8 +221,14 @@ button</button>
<app-hero-detail prefix="You are my" [hero]="currentHero"></app-hero-detail>
<p><img src="{{heroImageUrl}}"> is the <i>interpolated</i> image.</p>
<p><img [src]="heroImageUrl"> is the <i>property bound</i> image.</p>
<p>
<img src="{{heroImageUrl}}" alt="interpolated image of {{currentHero.name}}">
is the <i>interpolated</i> image.
</p>
<p>
<img [src]="heroImageUrl" [alt]="'property bounded image of ' + currentHero.name">
is the <i>property bound</i> image.
</p>
<p><span>"{{title}}" is the <i>interpolated</i> title.</span></p>
<p>"<span [innerHTML]="title"></span>" is the <i>property bound</i> title.</p>
@ -320,6 +326,8 @@ button</button>
[hero]="currentHero">
</app-big-hero-detail>
<!-- eslint-disable @angular-eslint/template/click-events-have-key-events -->
<div class="parent-div" (click)="onClickMe($event)" clickable>Click me
<div class="child-div">Click me too!</div>
</div>
@ -334,6 +342,8 @@ button</button>
<button (click)="onSave()">Save w/ propagation</button>
</div>
<!-- eslint-enable @angular-eslint/template/click-events-have-key-events -->
<a class="to-toc" href="#toc">top</a>
<hr><h2 id="two-way">Two-way Binding</h2>
@ -577,7 +587,7 @@ without NgModel
<!-- inputs and output -->
<hr><h2 id="inputs-and-outputs">Inputs and Outputs</h2>
<img [src]="iconUrl"/>
<img alt="icon" [src]="iconUrl"/>
<button (click)="onSave()">Save</button>
<app-hero-detail [hero]="currentHero" (deleteRequest)="deleteHero($event)">
@ -608,7 +618,7 @@ without NgModel
<div>
<!-- pipe price to USD and display the $ symbol -->
<label>Price: </label>{{product.price | currency:'USD':'symbol'}}
<span>Price: </span>{{product.price | currency:'USD':'symbol'}}
</div>
<a class="to-toc" href="#toc">top</a>

View file

@ -10,7 +10,7 @@ import { Hero } from './hero';
styles: ['button {margin-left: 8px} div {margin: 8px 0} img {height:24px}'],
template: `
<div>
<img src="{{heroImageUrl}}">
<img src="{{heroImageUrl}}" alt="{{hero.name}}">
<span [style.text-decoration]="lineThrough">
{{prefix}} {{hero.name}}
</span>
@ -38,7 +38,7 @@ export class HeroDetailComponent {
selector: 'app-big-hero-detail',
template: `
<div class="detail">
<img src="{{heroImageUrl}}">
<img src="{{heroImageUrl}}" alt="{{hero.name}}">
<div><b>{{hero.name}}</b></div>
<div>Name: {{hero.name}}</div>
<div>Emotion: {{hero.emotion}}</div>

View file

@ -7,7 +7,7 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
<div>
<button (click)="dec()" title="smaller">-</button>
<button (click)="inc()" title="bigger">+</button>
<label [style.font-size.px]="size">FontSize: {{size}}px</label>
<span [style.font-size.px]="size">FontSize: {{size}}px</span>
</div>`
})
export class SizerComponent {

View file

@ -4,6 +4,7 @@
text-align: center;
color: #eee;
max-height: 120px;
width: 100%;
min-width: 120px;
background-color: #607D8B;
border-radius: 2px;

View file

@ -7,9 +7,9 @@ import { Hero } from '../model/hero';
@Component({
selector: 'dashboard-hero',
template: `
<div (click)="click()" class="hero">
<button (click)="click()" class="hero">
{{hero.name | uppercase}}
</div>
</button>
`,
styleUrls: [ './dashboard-hero.component.css' ]
})

View file

@ -184,7 +184,7 @@ export class ParentComponent { }
@Component({
selector: 'io-comp',
template: '<div class="hero" (click)="click()">Original {{hero.name}}</div>'
template: '<button class="hero" (click)="click()">Original {{hero.name}}</button>'
})
export class IoComponent {
@Input() hero!: Hero;

View file

@ -2,7 +2,7 @@
<div *ngIf="hero">
<h2><span>{{hero.name | titlecase}}</span> Details</h2>
<div>
<label>id: </label>{{hero.id}}</div>
<span>id: </span>{{hero.id}}</div>
<div>
<label for="name">name: </label>
<input id="name" [(ngModel)]="hero.name" placeholder="name" />

View file

@ -2,57 +2,70 @@
background-color: #CFD8DC !important;
color: white;
}
.heroes {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 15em;
}
.heroes li {
display: flex;
}
.heroes button {
flex: 1;
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
padding: 0;
height: 1.6em;
border-radius: 4px;
display: flex;
align-items: stretch;
height: 1.8em;
}
.heroes li:hover {
color: #607D8B;
background-color: #DDD;
.heroes button:hover {
color: #2c3a41;
background-color: #e6e6e6;
left: .1em;
}
.heroes li.selected:hover {
background-color: #BBD8DC !important;
.heroes button:active {
background-color: #525252;
color: #fafafa;
}
.heroes button.selected {
background-color: black;
color: white;
}
.heroes .text {
position: relative;
top: -3px;
.heroes button.selected:hover {
background-color: #505050;
color: white;
}
.heroes button.selected:active {
background-color: black;
color: white;
}
.heroes .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color: #607D8B;
background-color: #405061;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
button {
font-family: Arial, sans-serif;
background-color: #eee;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #cfd8dc;
.heroes .name {
align-self: center;
}

View file

@ -1,8 +1,9 @@
<h2 highlight="gold">My Heroes</h2>
<ul class="heroes">
<li *ngFor="let hero of heroes | async "
[class.selected]="hero === selectedHero"
(click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
<li *ngFor="let hero of heroes | async ">
<button [class.selected]="hero === selectedHero" (click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span>
<span class="name">{{hero.name}}</span>
</button>
</li>
</ul>

View file

@ -56,11 +56,11 @@ describe('HeroListComponent', () => {
it('should select hero on click', fakeAsync(() => {
const expectedHero = HEROES[1];
const li = page.heroRows[1];
const btn = page.heroRows[1].querySelector('button');
// In older browsers, such as IE, you might need a CustomEvent instead. See
// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
li.dispatchEvent(new Event('click'));
btn!.dispatchEvent(new Event('click'));
tick();
// `.toEqual` because selectedHero is clone of expectedHero; see FakeHeroService
expect(comp.selectedHero).toEqual(expectedHero);
@ -68,11 +68,11 @@ describe('HeroListComponent', () => {
it('should navigate to selected hero detail on click', fakeAsync(() => {
const expectedHero = HEROES[1];
const li = page.heroRows[1];
const btn = page.heroRows[1].querySelector('button');
// In older browsers, such as IE, you might need a CustomEvent instead. See
// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
li.dispatchEvent(new Event('click'));
btn!.dispatchEvent(new Event('click'));
tick();
// should have navigated

View file

@ -66,7 +66,7 @@ function initialPageTests() {
function selectHeroTests() {
it(`selects ${targetHero.name} from hero list`, async () => {
const hero = element(by.cssContainingText('li span.badge', targetHero.id.toString()));
const hero = element(by.cssContainingText('button span.badge', targetHero.id.toString()));
await hero.click();
// Nothing specific to expect other than lack of exceptions.
});
@ -74,7 +74,7 @@ function selectHeroTests() {
it(`has selected ${targetHero.name}`, async () => {
const page = getPageElts();
const expectedText = `${targetHero.id} ${targetHero.name}`;
expect(await page.selected.getText()).toBe(expectedText);
expect((await page.selected.getText()).replace('\n', ' ')).toBe(expectedText);
});
it('shows selected hero details', async () => {
@ -101,7 +101,7 @@ function updateHeroTests() {
it(`shows updated hero name in list`, async () => {
const page = getPageElts();
const hero = Hero.fromString(await page.selected.getText());
const hero = Hero.fromString((await page.selected.getText()).replace('\n', ' '));
const newName = targetHero.name + nameSuffix;
expect(hero.id).toEqual(targetHero.id);
expect(hero.name).toEqual(newName);
@ -122,8 +122,8 @@ async function expectHeading(hLevel: number, expectedText: string): Promise<void
function getPageElts() {
return {
heroes: element.all(by.css('app-root li')),
selected: element(by.css('app-root li.selected')),
heroes: element.all(by.css('app-root li button')),
selected: element(by.css('app-root li button.selected')),
heroDetail: element(by.css('app-root > div, app-root > app-heroes > div'))
};
}

View file

@ -12,7 +12,8 @@
<!-- #enddocregion li -->
<!-- #docregion selectedHero-click -->
<li *ngFor="let hero of heroes" (click)="onSelect(hero)">
<li *ngFor="let hero of heroes">
<button (click)="onSelect(hero)">
<!-- #enddocregion selectedHero-click -->
<!-- #docregion class-selected -->

View file

@ -5,48 +5,63 @@
padding: 0;
width: 15em;
}
.heroes li {
display: flex;
}
.heroes button {
flex: 1;
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
padding: 0;
height: 1.6em;
border-radius: 4px;
display: flex;
align-items: stretch;
height: 1.8em;
}
.heroes li:hover {
.heroes button:hover {
color: #2c3a41;
background-color: #e6e6e6;
left: .1em;
}
.heroes li.selected {
.heroes button:active {
background-color: #525252;
color: #fafafa;
}
.heroes button.selected {
background-color: black;
color: white;
}
.heroes li.selected:hover {
.heroes button.selected:hover {
background-color: #505050;
color: white;
}
.heroes li.selected:active {
.heroes button.selected:active {
background-color: black;
color: white;
}
.heroes .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color:#405061;
background-color: #405061;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
input {
padding: .5rem;
.heroes .name {
align-self: center;
}

View file

@ -2,10 +2,11 @@
<h2>My Heroes</h2>
<ul class="heroes">
<!-- #docregion li -->
<li *ngFor="let hero of heroes"
[class.selected]="hero === selectedHero"
(click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
<li *ngFor="let hero of heroes">
<button [class.selected]="hero === selectedHero" (click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span>
<span class="name">{{hero.name}}</span>
</button>
</li>
<!-- #enddocregion li -->
</ul>
@ -15,7 +16,7 @@
<!-- #docregion selectedHero-details -->
<h2>{{selectedHero.name | uppercase}} Details</h2>
<div><span>id: </span>{{selectedHero.id}}</div>
<div>id: {{selectedHero.id}}</div>
<div>
<label for="hero-name">Hero name: </label>
<input id="hero-name" [(ngModel)]="selectedHero.name" placeholder="name">

View file

@ -66,7 +66,7 @@ function initialPageTests() {
function selectHeroTests() {
it(`selects ${targetHero.name} from hero list`, async () => {
const hero = element(by.cssContainingText('li span.badge', targetHero.id.toString()));
const hero = element(by.cssContainingText('li button', targetHero.id.toString() + targetHero.name.toString()));
await hero.click();
// Nothing specific to expect other than lack of exceptions.
});
@ -74,7 +74,7 @@ function selectHeroTests() {
it(`has selected ${targetHero.name}`, async () => {
const page = getPageElts();
const expectedText = `${targetHero.id} ${targetHero.name}`;
expect(await page.selected.getText()).toBe(expectedText);
expect((await page.selected.getText()).replace('\n', ' ')).toBe(expectedText);
});
it('shows selected hero details', async () => {
@ -101,7 +101,7 @@ function updateHeroTests() {
it(`shows updated hero name in list`, async () => {
const page = getPageElts();
const hero = Hero.fromString(await page.selected.getText());
const hero = Hero.fromString(await (await page.selected.getText()).replace('\n', ' '));
const newName = targetHero.name + nameSuffix;
expect(hero.id).toEqual(targetHero.id);
expect(hero.name).toEqual(newName);
@ -122,8 +122,8 @@ async function expectHeading(hLevel: number, expectedText: string): Promise<void
function getPageElts() {
return {
heroes: element.all(by.css('app-root li')),
selected: element(by.css('app-root li.selected')),
heroes: element.all(by.css('app-root li button')),
selected: element(by.css('app-root li button.selected')),
heroDetail: element(by.css('app-root > div, app-root > app-heroes > app-hero-detail > div'))
};
}

View file

@ -6,43 +6,61 @@
width: 15em;
}
.heroes li {
display: flex;
}
.heroes button {
flex: 1;
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
padding: 0;
height: 1.6em;
border-radius: 4px;
display: flex;
align-items: stretch;
height: 1.8em;
}
.heroes li:hover {
.heroes button:hover {
color: #2c3a41;
background-color: #e6e6e6;
left: .1em;
}
.heroes li.selected {
.heroes button:active {
background-color: #525252;
color: #fafafa;
}
.heroes button.selected {
background-color: black;
color: white;
}
.heroes li.selected:hover {
.heroes button.selected:hover {
background-color: #505050;
color: white;
}
.heroes li.selected:active {
.heroes button.selected:active {
background-color: black;
color: white;
}
.heroes .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color:#405061;
background-color: #405061;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
.heroes .name {
align-self: center;
}

View file

@ -1,10 +1,11 @@
<h2>My Heroes</h2>
<ul class="heroes">
<li *ngFor="let hero of heroes"
[class.selected]="hero === selectedHero"
(click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
<li *ngFor="let hero of heroes">
<button [class.selected]="hero === selectedHero" (click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span>
<span class="name">{{hero.name}}</span>
</button>
</li>
</ul>

View file

@ -74,7 +74,7 @@ function selectHeroTests() {
it(`has selected ${targetHero.name}`, async () => {
const page = getPageElts();
const expectedText = `${targetHero.id} ${targetHero.name}`;
expect(await page.selected.getText()).toBe(expectedText);
expect((await page.selected.getText()).replace('\n', ' ')).toBe(expectedText);
});
it('shows selected hero details', async () => {
@ -106,7 +106,7 @@ function updateHeroTests() {
it(`shows updated hero name in list`, async () => {
const page = getPageElts();
const hero = Hero.fromString(await page.selected.getText());
const hero = Hero.fromString((await page.selected.getText()).replace('\n', ' '));
const newName = targetHero.name + nameSuffix;
expect(hero.id).toEqual(targetHero.id);
expect(hero.name).toEqual(newName);
@ -127,8 +127,8 @@ async function expectHeading(hLevel: number, expectedText: string): Promise<void
function getPageElts() {
return {
heroes: element.all(by.css('app-root li')),
selected: element(by.css('app-root li.selected')),
heroes: element.all(by.css('app-root li button')),
selected: element(by.css('app-root li button.selected')),
heroDetail: element(by.css('app-root > div, app-root > app-heroes > app-hero-detail > div'))
};
}

View file

@ -5,49 +5,63 @@
padding: 0;
width: 15em;
}
.heroes li {
display: flex;
}
.heroes button {
flex: 1;
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
padding: 0;
height: 1.6em;
border-radius: 4px;
display: flex;
align-items: stretch;
height: 1.8em;
}
.heroes li:hover {
.heroes button:hover {
color: #2c3a41;
background-color: #e6e6e6;
left: .1em;
}
.heroes li:active {
.heroes button:active {
background-color: #525252;
color: #fafafa;
}
.heroes li.selected {
.heroes button.selected {
background-color: black;
color: white;
}
.heroes li.selected:hover {
.heroes button.selected:hover {
background-color: #505050;
color: white;
}
.heroes li.selected:active {
.heroes button.selected:active {
background-color: black;
color: white;
}
.heroes .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color:#405061;
background-color: #405061;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
.heroes .name {
align-self: center;
}

View file

@ -1,10 +1,11 @@
<h2>My Heroes</h2>
<!-- #docregion list -->
<ul class="heroes">
<li *ngFor="let hero of heroes"
[class.selected]="hero === selectedHero"
(click)="onSelect(hero)">
<span class="badge">{{hero.id}}</span> {{hero.name}}
<li *ngFor="let hero of heroes">
<button (click)="onSelect(hero)" [class.selected]="hero === selectedHero">
<span class="badge">{{hero.id}}</span>
<span class="name">{{hero.name}}</span>
</button>
</li>
</ul>
<!-- #enddocregion list -->

View file

@ -41,7 +41,7 @@
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color:#405061;
background-color: #405061;
line-height: 1em;
position: relative;
left: -1px;

View file

@ -50,7 +50,7 @@ input {
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color:#405061;
background-color: #405061;
line-height: 1em;
position: relative;
left: -1px;
@ -80,6 +80,7 @@ button.delete {
background-color: white;
color: #525252;
font-size: 1.1rem;
margin: 0;
padding: 1px 10px 3px 10px;
}

View file

@ -20,17 +20,17 @@ describe('Two-way binding e2e tests', () => {
expect(await plus2Button.getText()).toBe('+');
});
it('should change font size labels', async () => {
it('should change font size texts', async () => {
await minusButton.click();
expect(await element.all(by.css('label')).get(0).getText()).toEqual('FontSize: 15px');
expect(await element.all(by.css('span')).get(0).getText()).toEqual('FontSize: 15px');
expect(await element.all(by.css('input')).get(0).getAttribute('value')).toEqual('15');
await plusButton.click();
expect(await element.all(by.css('label')).get(0).getText()).toEqual('FontSize: 16px');
expect(await element.all(by.css('span')).get(0).getText()).toEqual('FontSize: 16px');
expect(await element.all(by.css('input')).get(0).getAttribute('value')).toEqual('16');
await minus2Button.click();
expect(await element.all(by.css('label')).get(2).getText()).toEqual('FontSize: 15px');
expect(await element.all(by.css('span')).get(1).getText()).toEqual('FontSize: 15px');
});
it('should display De-sugared two-way binding', async () => {

View file

@ -1,5 +1,5 @@
<div>
<button (click)="dec()" title="smaller">-</button>
<button (click)="inc()" title="bigger">+</button>
<label [style.font-size.px]="size">FontSize: {{size}}px</label>
<span [style.font-size.px]="size">FontSize: {{size}}px</span>
</div>

View file

@ -176,8 +176,9 @@ describe('Universal', () => {
expect(await button.getCssValue('padding')).toBe('5px 10px');
expect(await button.getCssValue('border-radius')).toBe('4px');
// Styles defined in heroes.component.css
expect(await button.getCssValue('left')).toBe('194px');
expect(await button.getCssValue('top')).toBe('-32px');
expect(await button.getCssValue('right')).toBe('0px');
expect(await button.getCssValue('top')).toBe('0px');
expect(await button.getCssValue('bottom')).toBe('0px');
}
const addButton = element(by.buttonText('add'));

View file

@ -5,6 +5,7 @@
padding: 0;
width: 15em;
}
.heroes li {
position: relative;
cursor: pointer;
@ -64,10 +65,12 @@ button:hover {
}
button.delete {
position: relative;
left: 194px;
top: -32px;
position: absolute;
right: 0;
top: 0;
bottom: 0;
background-color: gray !important;
color: white;
margin: 0;
}

View file

@ -6,7 +6,7 @@ import { Hero } from '../hero';
selector: 'hero-detail',
template: `
<h2>{{hero.name}} details!</h2>
<div><label>id: </label>{{hero.id}}</div>
<div>id: {{hero.id}}</div>
<button (click)="onDelete()">Delete</button>
`
})

View file

@ -5,7 +5,7 @@ import { Component } from '@angular/core';
selector: 'hero-detail',
template: `
<h2>Windstorm details!</h2>
<div><label>id: </label>1</div>
<div>id: 1</div>
`
})
export class HeroDetailComponent { }

View file

@ -1,5 +1,5 @@
<div class="phone-images">
<img ng-src="{{img}}" class="phone"
<img ng-src="{{img}}" class="phone" alt="Phone {{$ctrl.phone.name}} - thumbnail {{$index}}"
ng-class="{selected: img === $ctrl.mainImageUrl}"
ng-repeat="img in $ctrl.phone.images" />
</div>
@ -10,7 +10,7 @@
<ul class="phone-thumbs">
<li ng-repeat="img in $ctrl.phone.images">
<img ng-src="{{img}}" ng-click="$ctrl.setImage(img)" />
<img ng-src="{{img}}" ng-click="$ctrl.setImage(img)" alt="Phone {{$ctrl.phone.name}} - thumbnail {{$index}}"/>
</li>
</ul>

View file

@ -1,9 +1,9 @@
<!-- #docregion -->
<div *ngIf="phone">
<div class="phone-images">
<img [src]="img" class="phone"
<img [src]="img" class="phone" alt="Phone {{ phone.name }} - thumbnail {{ index }}"
[ngClass]="{'selected': img === mainImageUrl}"
*ngFor="let img of phone.images" />
*ngFor="let img of phone.images; let index = index;" />
</div>
<h1>{{phone.name}}</h1>
@ -11,8 +11,9 @@
<p>{{phone.description}}</p>
<ul class="phone-thumbs">
<li *ngFor="let img of phone.images">
<img [src]="img" (click)="setImage(img)" />
<li *ngFor="let img of phone.images; let index = index">
<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->
<img [src]="img" (click)="setImage(img)" alt="Phone {{ phone.name }} - thumbnail {{ index }}"/>
</li>
</ul>

View file

@ -1,9 +1,9 @@
<!-- #docregion -->
<div *ngIf="phone">
<div class="phone-images">
<img [src]="img" class="phone"
<img [src]="img" class="phone" [alt]="'phone' + index"
[ngClass]="{'selected': img === mainImageUrl}"
*ngFor="let img of phone.images" />
*ngFor="let img of phone.images; let index = index" />
</div>
<h1>{{phone.name}}</h1>
@ -11,8 +11,9 @@
<p>{{phone.description}}</p>
<ul class="phone-thumbs">
<li *ngFor="let img of phone.images">
<img [src]="img" (click)="setImage(img)" />
<li *ngFor="let img of phone.images let index = index">
<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->
<img [src]="img" [alt]="'phone thumb' + index" (click)="setImage(img)" />
</li>
</ul>

View file

@ -89,14 +89,14 @@ This example from the `HeroListComponent` template uses three of these forms.
<code-example path="architecture/src/app/hero-list.component.1.html" header="src/app/hero-list.component.html (binding)" region="binding"></code-example>
* The `{{hero.name}}` [*interpolation*](guide/interpolation)
displays the component's `hero.name` property value within the `<li>` element.
* The `[hero]` [*property binding*](guide/property-binding) passes the value of
`selectedHero` from the parent `HeroListComponent` to the `hero` property of the child `HeroDetailComponent`.
* The `(click)` [*event binding*](guide/user-input#binding-to-user-input-events) calls the component's `selectHero` method when the user clicks a hero's name.
* The `{{hero.name}}` [*interpolation*](guide/interpolation)
displays the component's `hero.name` property value within the `<button>` element.
Two-way data binding (used mainly in [template-driven forms](guide/forms))
combines property and event binding in a single notation.
Here's an example from the `HeroDetailComponent` template that uses two-way data binding with the `ngModel` directive.

View file

@ -237,7 +237,7 @@ The following table summarizes the targets for the different binding types.
Directive&nbsp;property
</td>
<td>
<code>src</code>, <code>hero</code>, and <code>ngClass</code> in the following:
<code>alt</code>, <code>src</code>, <code>hero</code>, and <code>ngClass</code> in the following:
<code-example path="template-syntax/src/app/app.component.html" region="property-binding-syntax-1"></code-example>
<!-- For more information, see [Property Binding](guide/property-binding). -->
</td>

View file

@ -117,15 +117,26 @@ and update the hero detail.
### Add a click event binding
Add a click event binding to the `<li>` like this:
Add a `<button>` with a click event binding in the `<li>` like this:
<code-example path="toh-pt2/src/app/heroes/heroes.component.1.html" region="selectedHero-click" header="heroes.component.html (template excerpt)"></code-example>
This is an example of Angular's [event binding](guide/event-binding) syntax.
The parentheses around `click` tell Angular to listen for the `<li>` element's `click` event.
When the user clicks in the `<li>`, Angular executes the `onSelect(hero)` expression.
The parentheses around `click` tell Angular to listen for the `<button>` element's `click` event.
When the user clicks in the `<button>`, Angular executes the `onSelect(hero)` expression.
<div class="callout is-helpful">
<header>Clickable elements</header>
Note that we added the click event binding on a new `<button>` element. While we could have added
the event binding on the `<li>` element directly, it is better for accessibility purposes to use
the native `<button>` element to handle clicks.
For more details on accessibility, see [Accessibility in Angular](guide/accessibility).
</div>
In the next section, define an `onSelect()` method in `HeroesComponent` to
display the hero that was defined in the `*ngFor` expression.
@ -207,7 +218,7 @@ To apply the `.selected` class to the `<li>` when the user clicks it, use class
Angular's [class binding](guide/attribute-binding#class-binding) can add and remove a CSS class conditionally.
Add `[class.some-css-class]="some-condition"` to the element you want to style.
Add the following `[class.selected]` binding to the `<li>` in the `HeroesComponent` template:
Add the following `[class.selected]` binding to the `<button>` in the `HeroesComponent` template:
<code-example path="toh-pt2/src/app/heroes/heroes.component.1.html" region="class-selected" header="heroes.component.html (toggle the 'selected' CSS class)"></code-example>