docs(core): in template syntax guide, make SVG example more clear (#31356)
add e2e test for SVG template example fix template syntax example app - linting errors - runtime exceptions - template type errors - deprecated type casting - deprecated currency pipe example Relates to #30559 PR Close #31356
This commit is contained in:
parent
95a9d67599
commit
09970d52e8
@ -2,42 +2,49 @@
|
|||||||
|
|
||||||
import { browser, element, by } from 'protractor';
|
import { browser, element, by } from 'protractor';
|
||||||
|
|
||||||
// Not yet complete
|
// TODO Not yet complete
|
||||||
describe('Template Syntax', function () {
|
describe('Template Syntax', () => {
|
||||||
|
|
||||||
beforeAll(function () {
|
beforeAll(() => {
|
||||||
browser.get('');
|
browser.get('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to use interpolation with a hero', function () {
|
it('should be able to use interpolation with a hero', () => {
|
||||||
let heroInterEle = element.all(by.css('h2+p')).get(0);
|
const heroInterEle = element.all(by.css('h2+p')).get(0);
|
||||||
expect(heroInterEle.getText()).toEqual('My current hero is Hercules');
|
expect(heroInterEle.getText()).toEqual('My current hero is Hercules');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to use interpolation with a calculation', function () {
|
it('should be able to use interpolation with a calculation', () => {
|
||||||
let theSumEles = element.all(by.cssContainingText('h3~p', 'The sum of'));
|
const theSumEles = element.all(by.cssContainingText('h3~p', 'The sum of'));
|
||||||
expect(theSumEles.count()).toBe(2);
|
expect(theSumEles.count()).toBe(2);
|
||||||
expect(theSumEles.get(0).getText()).toEqual('The sum of 1 + 1 is 2');
|
expect(theSumEles.get(0).getText()).toEqual('The sum of 1 + 1 is 2');
|
||||||
expect(theSumEles.get(1).getText()).toEqual('The sum of 1 + 1 is not 4');
|
expect(theSumEles.get(1).getText()).toEqual('The sum of 1 + 1 is not 4');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to use class binding syntax', function () {
|
it('should be able to use class binding syntax', () => {
|
||||||
let specialEle = element(by.cssContainingText('div', 'Special'));
|
const specialEle = element(by.cssContainingText('div', 'Special'));
|
||||||
expect(specialEle.getAttribute('class')).toMatch('special');
|
expect(specialEle.getAttribute('class')).toMatch('special');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to use style binding syntax', function () {
|
it('should be able to use style binding syntax', () => {
|
||||||
let specialButtonEle = element(by.cssContainingText('div.special~button', 'button'));
|
const specialButtonEle = element(by.cssContainingText('div.special~button', 'button'));
|
||||||
expect(specialButtonEle.getAttribute('style')).toMatch('color: red');
|
expect(specialButtonEle.getAttribute('style')).toMatch('color: red');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should two-way bind to sizer', async () => {
|
it('should two-way bind to sizer', async () => {
|
||||||
let div = element(by.css('div#two-way-1'));
|
const div = element(by.css('div#two-way-1'));
|
||||||
let incButton = div.element(by.buttonText('+'));
|
const incButton = div.element(by.buttonText('+'));
|
||||||
let input = div.element(by.css('input'));
|
const input = div.element(by.css('input'));
|
||||||
let initSize = await input.getAttribute('value');
|
const initSize = await input.getAttribute('value');
|
||||||
incButton.click();
|
incButton.click();
|
||||||
expect(input.getAttribute('value')).toEqual((+initSize + 1).toString());
|
expect(input.getAttribute('value')).toEqual((+initSize + 1).toString());
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
|
it('should change SVG rectangle\'s fill color on click', async () => {
|
||||||
|
const div = element(by.css('app-svg'));
|
||||||
|
const colorSquare = div.element(by.css('rect'));
|
||||||
|
const initialColor = await colorSquare.getAttribute('fill');
|
||||||
|
colorSquare.click();
|
||||||
|
expect(colorSquare.getAttribute('fill')).not.toEqual(initialColor);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
<a href="#safe-navigation-operator">Safe navigation operator <i>?.</i></a><br>
|
<a href="#safe-navigation-operator">Safe navigation operator <i>?.</i></a><br>
|
||||||
<a href="#non-null-assertion-operator">Non-null assertion operator <i>!.</i></a><br>
|
<a href="#non-null-assertion-operator">Non-null assertion operator <i>!.</i></a><br>
|
||||||
<a href="#enums">Enums</a><br>
|
<a href="#enums">Enums</a><br>
|
||||||
|
<a href="#svg-templates">SVG Templates</a><br>
|
||||||
|
|
||||||
<!-- Interpolation and expressions -->
|
<!-- Interpolation and expressions -->
|
||||||
<hr><h2 id="interpolation">Interpolation</h2>
|
<hr><h2 id="interpolation">Interpolation</h2>
|
||||||
@ -442,7 +443,7 @@ button</button>
|
|||||||
|
|
||||||
<!-- #docregion without-NgModel -->
|
<!-- #docregion without-NgModel -->
|
||||||
<input [value]="currentHero.name"
|
<input [value]="currentHero.name"
|
||||||
(input)="currentHero.name=$event.target.value" >
|
(input)="updateCurrentHeroName($event)">
|
||||||
<!-- #enddocregion without-NgModel -->
|
<!-- #enddocregion without-NgModel -->
|
||||||
without NgModel
|
without NgModel
|
||||||
<br>
|
<br>
|
||||||
@ -752,7 +753,7 @@ bindon-ngModel
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<!-- pipe price to USD and display the $ symbol -->
|
<!-- pipe price to USD and display the $ symbol -->
|
||||||
<label>Price: </label>{{product.price | currency:'USD':true}}
|
<label>Price: </label>{{product.price | currency:'USD':'symbol'}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a class="to-toc" href="#toc">top</a>
|
<a class="to-toc" href="#toc">top</a>
|
||||||
@ -857,3 +858,9 @@ The null hero's name is {{nullHero && nullHero.name}}
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<a class="to-toc" href="#toc">top</a>
|
<a class="to-toc" href="#toc">top</a>
|
||||||
|
|
||||||
|
<hr><h2 id="svg-templates">SVG Templates</h2>
|
||||||
|
<!-- #docregion svg-templates -->
|
||||||
|
<app-svg></app-svg>
|
||||||
|
<!-- #enddocregion svg-templates -->
|
||||||
|
<a class="to-toc" href="#toc">top</a>
|
||||||
|
@ -5,7 +5,7 @@ import { AfterViewInit, Component, ElementRef, OnInit, QueryList, ViewChildren }
|
|||||||
|
|
||||||
import { Hero } from './hero';
|
import { Hero } from './hero';
|
||||||
|
|
||||||
export enum Color {Red, Green, Blue};
|
export enum Color {Red, Green, Blue}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Giant grab bag of stuff to drive the chapter
|
* Giant grab bag of stuff to drive the chapter
|
||||||
@ -29,7 +29,7 @@ export class AppComponent implements AfterViewInit, OnInit {
|
|||||||
trackChanges(this.heroesWithTrackBy, () => this.heroesWithTrackByCount++);
|
trackChanges(this.heroesWithTrackBy, () => this.heroesWithTrackByCount++);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewChildren('noTrackBy') heroesNoTrackBy: QueryList<ElementRef>;
|
@ViewChildren('noTrackBy') heroesNoTrackBy: QueryList<ElementRef>;
|
||||||
@ViewChildren('withTrackBy') heroesWithTrackBy: QueryList<ElementRef>;
|
@ViewChildren('withTrackBy') heroesWithTrackBy: QueryList<ElementRef>;
|
||||||
|
|
||||||
actionName = 'Go for it';
|
actionName = 'Go for it';
|
||||||
@ -66,6 +66,10 @@ export class AppComponent implements AfterViewInit, OnInit {
|
|||||||
|
|
||||||
currentHero: Hero;
|
currentHero: Hero;
|
||||||
|
|
||||||
|
updateCurrentHeroName(event: Event) {
|
||||||
|
this.currentHero.name = (event.target as any).value;
|
||||||
|
}
|
||||||
|
|
||||||
deleteHero(hero?: Hero) {
|
deleteHero(hero?: Hero) {
|
||||||
this.alert(`Delete ${hero ? hero.name : 'the hero'}.`);
|
this.alert(`Delete ${hero ? hero.name : 'the hero'}.`);
|
||||||
}
|
}
|
||||||
@ -105,13 +109,13 @@ export class AppComponent implements AfterViewInit, OnInit {
|
|||||||
|
|
||||||
get nullHero(): Hero { return null; }
|
get nullHero(): Hero { return null; }
|
||||||
|
|
||||||
onClickMe(event?: KeyboardEvent) {
|
onClickMe(event?: MouseEvent) {
|
||||||
let evtMsg = event ? ' Event target class is ' + (<HTMLElement>event.target).className : '';
|
const evtMsg = event ? ' Event target class is ' + (event.target as HTMLElement).className : '';
|
||||||
this.alert('Click me.' + evtMsg);
|
this.alert('Click me.' + evtMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSave(event?: KeyboardEvent) {
|
onSave(event?: MouseEvent) {
|
||||||
let evtMsg = event ? ' Event target is ' + (<HTMLElement>event.target).textContent : '';
|
const evtMsg = event ? ' Event target is ' + (event.target as HTMLElement).textContent : '';
|
||||||
this.alert('Saved.' + evtMsg);
|
this.alert('Saved.' + evtMsg);
|
||||||
if (event) { event.stopPropagation(); }
|
if (event) { event.stopPropagation(); }
|
||||||
}
|
}
|
||||||
@ -140,9 +144,9 @@ export class AppComponent implements AfterViewInit, OnInit {
|
|||||||
setCurrentClasses() {
|
setCurrentClasses() {
|
||||||
// CSS classes: added/removed per current state of component properties
|
// CSS classes: added/removed per current state of component properties
|
||||||
this.currentClasses = {
|
this.currentClasses = {
|
||||||
'saveable': this.canSave,
|
saveable: this.canSave,
|
||||||
'modified': !this.isUnchanged,
|
modified: !this.isUnchanged,
|
||||||
'special': this.isSpecial
|
special: this.isSpecial
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// #enddocregion setClasses
|
// #enddocregion setClasses
|
||||||
@ -164,7 +168,7 @@ export class AppComponent implements AfterViewInit, OnInit {
|
|||||||
// #enddocregion trackByHeroes
|
// #enddocregion trackByHeroes
|
||||||
|
|
||||||
// #docregion trackById
|
// #docregion trackById
|
||||||
trackById(index: number, item: any): number { return item['id']; }
|
trackById(index: number, item: any): number { return item.id; }
|
||||||
// #enddocregion trackById
|
// #enddocregion trackById
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { BigHeroDetailComponent, HeroDetailComponent } from './hero-detail.component';
|
import { BigHeroDetailComponent, HeroDetailComponent } from './hero-detail.component';
|
||||||
import { ClickDirective, ClickDirective2 } from './click.directive';
|
import { ClickDirective, ClickDirective2 } from './click.directive';
|
||||||
import { HeroFormComponent } from './hero-form.component';
|
import { HeroFormComponent } from './hero-form.component';
|
||||||
import { heroSwitchComponents } from './hero-switch.components';
|
import { heroSwitchComponents } from './hero-switch.components';
|
||||||
import { SizerComponent } from './sizer.component';
|
import { SizerComponent } from './sizer.component';
|
||||||
import { SvgComponent } from './svg.component';
|
import { SvgComponent } from './svg.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/* tslint:disable use-output-property-decorator directive-class-suffix */
|
/* tslint:disable directive-selector directive-class-suffix */
|
||||||
// #docplaster
|
// #docplaster
|
||||||
import { Directive, ElementRef, EventEmitter, Output } from '@angular/core';
|
import { Directive, ElementRef, EventEmitter, Output } from '@angular/core';
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Component, Input, ViewChild } from '@angular/core';
|
import { Component, Input, ViewChild } from '@angular/core';
|
||||||
import { NgForm } from '@angular/forms';
|
import { NgForm } from '@angular/forms';
|
||||||
|
|
||||||
import { Hero } from './hero';
|
import { Hero } from './hero';
|
||||||
|
|
||||||
@ -15,10 +15,11 @@ export class HeroFormComponent {
|
|||||||
@Input() hero: Hero;
|
@Input() hero: Hero;
|
||||||
@ViewChild('heroForm', {static: false}) form: NgForm;
|
@ViewChild('heroForm', {static: false}) form: NgForm;
|
||||||
|
|
||||||
|
// tslint:disable-next-line:variable-name
|
||||||
private _submitMessage = '';
|
private _submitMessage = '';
|
||||||
|
|
||||||
get submitMessage() {
|
get submitMessage() {
|
||||||
if (!this.form.valid) {
|
if (this.form && !this.form.valid) {
|
||||||
this._submitMessage = '';
|
this._submitMessage = '';
|
||||||
}
|
}
|
||||||
return this._submitMessage;
|
return this._submitMessage;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<svg>
|
<svg>
|
||||||
<g>
|
<g>
|
||||||
<rect x="0" y="0" width="100" height="100" [attr.fill]="fill" (click)="changeColor()" />
|
<rect x="0" y="0" width="100" height="100" [attr.fill]="fillColor" (click)="changeColor()" />
|
||||||
<text x="120" y="50">click the rectangle to change the fill color</text>
|
<text x="120" y="50">click the rectangle to change the fill color</text>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 196 B After Width: | Height: | Size: 201 B |
@ -6,12 +6,12 @@ import { Component } from '@angular/core';
|
|||||||
styleUrls: ['./svg.component.css']
|
styleUrls: ['./svg.component.css']
|
||||||
})
|
})
|
||||||
export class SvgComponent {
|
export class SvgComponent {
|
||||||
fill = 'rgb(255, 0, 0)';
|
fillColor = 'rgb(255, 0, 0)';
|
||||||
|
|
||||||
changeColor() {
|
changeColor() {
|
||||||
const r = Math.floor(Math.random() * 256);
|
const r = Math.floor(Math.random() * 256);
|
||||||
const g = Math.floor(Math.random() * 256);
|
const g = Math.floor(Math.random() * 256);
|
||||||
const b = Math.floor(Math.random() * 256);
|
const b = Math.floor(Math.random() * 256);
|
||||||
this.fill = `rgb(${r}, ${g}, ${b})`;
|
this.fillColor = `rgb(${r}, ${g}, ${b})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2276,12 +2276,14 @@ The `$any()` cast function works anywhere in a binding expression where a method
|
|||||||
|
|
||||||
## SVG in templates
|
## SVG in templates
|
||||||
|
|
||||||
It is possible to use SVG as valid templates in Angular. All of the template syntax below is applicable to both SVG and HTML.
|
It is possible to use SVG as valid templates in Angular. All of the template syntax below is
|
||||||
Learn more in the SVG [1.1](https://www.w3.org/TR/SVG11/) and [2.0](https://www.w3.org/TR/SVG2/) specifications.
|
applicable to both SVG and HTML. Learn more in the SVG [1.1](https://www.w3.org/TR/SVG11/) and
|
||||||
|
[2.0](https://www.w3.org/TR/SVG2/) specifications.
|
||||||
|
|
||||||
Why would you use SVG as template, instead of simply adding it as image to your application?
|
Why would you use SVG as template, instead of simply adding it as image to your application?
|
||||||
|
|
||||||
When you use an SVG as the template, you are able to use directives and bindings just like with HTML templates. This means that you will be able to dynamically generate interactive graphics.
|
When you use an SVG as the template, you are able to use directives and bindings just like with HTML
|
||||||
|
templates. This means that you will be able to dynamically generate interactive graphics.
|
||||||
|
|
||||||
Refer to the sample code snippet below for a syntax example:
|
Refer to the sample code snippet below for a syntax example:
|
||||||
|
|
||||||
@ -2293,4 +2295,5 @@ Add the below code to your `svg.component.svg` file:
|
|||||||
<code-example path="template-syntax/src/app/svg.component.svg" header="src/app/svg.component.svg">
|
<code-example path="template-syntax/src/app/svg.component.svg" header="src/app/svg.component.svg">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
Here you can see the use of a `click()` event binding and the property binding syntax (`[attr.fill]="fill"`).
|
Here you can see the use of a `click()` event binding and the property binding syntax
|
||||||
|
(`[attr.fill]="fillColor"`).
|
||||||
|
Loading…
x
Reference in New Issue
Block a user