Skip to content
This repository was archived by the owner on Dec 4, 2017. It is now read-only.

Commit 3e7ecfc

Browse files
committed
docs(cb-rating-component): cookbook about creating a rating component
1 parent 5fd6ae3 commit 3e7ecfc

File tree

18 files changed

+334
-0
lines changed

18 files changed

+334
-0
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/// <reference path='../_protractor/e2e.d.ts' />
2+
'use strict';
3+
describe('Rating component', function () {
4+
5+
beforeAll(function () {
6+
browser.get('');
7+
});
8+
9+
it('should show 5 stars for windstorm', function () {
10+
const windstormRating = element.all(by.tagName('my-hero-rating')).get(0);
11+
const windstormStars = windstormRating.all(by.css('.glyphicon-star'));
12+
expect(windstormStars.count()).toBe(5);
13+
});
14+
15+
it('should show 1 star for bombasto', function() {
16+
const bombastoRating = element.all(by.tagName('my-hero-rating')).get(1);
17+
const bombastoStars = bombastoRating.all(by.css('.glyphicon-star'));
18+
expect(bombastoStars.count()).toBe(1);
19+
});
20+
21+
it('should change the rate on click', function() {
22+
const bombastoRating = element.all(by.tagName('my-hero-rating')).get(1);
23+
const bombastoFourthStar = bombastoRating.all(by.css('.glyphicon')).get(3);
24+
bombastoFourthStar.click().then(function() {
25+
const bombastoStars = bombastoRating.all(by.css('.glyphicon-star'));
26+
expect(bombastoStars.count()).toBe(4);
27+
28+
const div = element.all(by.tagName('div')).get(0);
29+
expect(div.getText()).toEqual('Bombasto rate is 4');
30+
});
31+
});
32+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
**/*.js
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// #docregion
2+
import { Component } from '@angular/core';
3+
4+
@Component({
5+
selector: 'my-app',
6+
template: `
7+
<label>Windstorm: </label>
8+
<my-hero-rating></my-hero-rating>
9+
`
10+
})
11+
export class AppComponent { }
12+
// #enddocregion
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Component } from '@angular/core';
2+
3+
@Component({
4+
selector: 'my-app',
5+
template: `
6+
<p>
7+
<label>Windstorm: </label>
8+
<my-hero-rating rate="5"></my-hero-rating>
9+
</p>
10+
<p>
11+
<label>Bombasto: </label>
12+
<my-hero-rating [(rate)]="bombasto"></my-hero-rating>
13+
</p>
14+
<div>Bombasto rate is {{bombasto}}</div>
15+
`
16+
})
17+
export class AppComponent {
18+
bombasto = 1;
19+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { NgModule } from '@angular/core';
2+
import { BrowserModule } from '@angular/platform-browser';
3+
4+
import { AppComponent } from './app.component';
5+
import { HeroRatingComponent } from './rating.component';
6+
7+
@NgModule({
8+
imports: [ BrowserModule ],
9+
declarations: [ AppComponent, HeroRatingComponent ],
10+
bootstrap: [ AppComponent ]
11+
})
12+
export class AppModule {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
2+
3+
import { AppModule } from './app.module';
4+
5+
platformBrowserDynamic().bootstrapModule(AppModule);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// #docregion
2+
import { Component } from '@angular/core';
3+
4+
@Component({
5+
selector: 'my-hero-rating',
6+
template: '<div>Rating</div>'
7+
})
8+
export class HeroRatingComponent { }
9+
// #enddocregion
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// #docregion
2+
import { Component } from '@angular/core';
3+
4+
@Component({
5+
selector: 'my-hero-rating',
6+
// #docregion template
7+
template: `
8+
<template ngFor [ngForOf]="range" let-index="index">
9+
<span class="sr-only">(*)</span>
10+
<i class="glyphicon glyphicon-star"></i>
11+
</template>
12+
`
13+
// #enddocregion template
14+
})
15+
export class HeroRatingComponent { }
16+
// #enddocregion
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// #docregion
2+
import { Component, Input } from '@angular/core';
3+
4+
@Component({
5+
selector: 'my-hero-rating',
6+
// #docregion template
7+
template: `
8+
<template ngFor [ngForOf]="range" let-index="index">
9+
<span class="sr-only">({{ index < rate ? '*' : ' ' }})</span>
10+
<i class="glyphicon {{index < rate ? 'glyphicon-star' : 'glyphicon-star-empty'}}"></i>
11+
</template>
12+
`
13+
// #enddocregion template
14+
})
15+
export class HeroRatingComponent {
16+
@Input() rate: number;
17+
// #docregion update-method
18+
update(value: number): void {
19+
this.rate = value;
20+
}
21+
// #enddocregion
22+
}
23+
// #enddocregion
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// #docregion
2+
import { Component, EventEmitter, Input, Output } from '@angular/core';
3+
4+
@Component({
5+
selector: 'my-hero-rating',
6+
// #docregion rating-template
7+
template: `
8+
<template ngFor [ngForOf]="range" let-index="index">
9+
<span class="sr-only">({{ index < rate ? '*' : ' ' }})</span>
10+
<i class="glyphicon {{index < rate ? 'glyphicon-star' : 'glyphicon-star-empty'}}" (click)="update(index + 1)"></i>
11+
</template>
12+
`
13+
// #enddocregion rating-template
14+
})
15+
export class HeroRatingComponent {
16+
// #docregion range-attribute
17+
range = new Array(5);
18+
// #enddocregion range-attribute
19+
20+
// #docregion rate-input
21+
@Input() rate: number;
22+
// #enddocregion rate-input
23+
// #docregion rate-output
24+
@Output() rateChange = new EventEmitter<number>();
25+
// #enddocregion rate-output
26+
27+
// #docregion update-method
28+
update(value: number): void {
29+
this.rate = value;
30+
this.rateChange.emit(value);
31+
}
32+
// #enddocregion update-method
33+
}
34+
// #enddocregion

public/docs/_examples/cb-rating-component/ts/example-config.json

Whitespace-only changes.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<base href="/">
5+
<title>Rating component</title>
6+
<meta name="viewport" content="width=device-width, initial-scale=1">
7+
<!-- #docregion style -->
8+
<link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css">
9+
<link rel="stylesheet" href="styles.css">
10+
<!-- #enddocregion style -->
11+
12+
<!-- Polyfill(s) for older browsers -->
13+
<script src="node_modules/core-js/client/shim.min.js"></script>
14+
15+
<script src="node_modules/zone.js/dist/zone.js"></script>
16+
<script src="node_modules/reflect-metadata/Reflect.js"></script>
17+
<script src="node_modules/systemjs/dist/system.src.js"></script>
18+
19+
<script src="systemjs.config.js"></script>
20+
<script>
21+
System.import('app').catch(function(err){ console.error(err); });
22+
</script>
23+
</head>
24+
25+
<body>
26+
<my-app>Loading app...</my-app>
27+
</body>
28+
29+
</html>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"description": "Rating Component",
3+
"files":[
4+
"!**/*.d.ts",
5+
"!**/*.js",
6+
"!**/*.[1,2,3].*"
7+
],
8+
"tags":["cookbook"]
9+
}

public/docs/ts/latest/cookbook/_data.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@
4343
"intro": "Render dynamic forms with FormGroup"
4444
},
4545

46+
"rating-component": {
47+
"title": "Rating Component",
48+
"intro": "Learn how to write a rating component"
49+
},
50+
4651
"rc4-to-rc5": {
4752
"title": "RC4 to RC5 Migration",
4853
"intro": "Migrate your RC4 app to RC5 in minutes."
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
include ../_util-fns
2+
3+
:marked
4+
In this cookbook we will show how to build a custom rating directive with two-way databinding.
5+
<a id="top"></a>
6+
## Table of contents
7+
8+
[A static rating component](#static-component)
9+
10+
[Adding inputs](#inputs)
11+
12+
[Reacting to clicks](#clicks)
13+
14+
[Adding outputs](#outputs)
15+
16+
:marked
17+
**See the [live example](/resources/live-examples/cb-rating-component/ts/plnkr.html)**.
18+
19+
.l-main-section
20+
<a id="static-component"></a>
21+
:marked
22+
## A static rating component
23+
24+
The first thing we are going to do is to create an static rating component. Let's start with the basic skeleton.
25+
26+
+makeExample('cb-rating-component/ts/app/rating.component.1.ts', null, 'rating.component.ts (skeleton)')(format=".")
27+
28+
:marked
29+
Our next step would be to print our 5 stars. To do that, we need to output some HTML.
30+
For each star, we want to output two elements: We want the star itself which we can see and click, and also an invisible star that we will use for accessibility.
31+
This means that we need `ngFor` to generate not only one element, but two.
32+
33+
What we can do here, is to de-sugar `ngFor` into its complete version. That will allow us to to generate as many elements as we need.
34+
35+
+makeExample('cb-rating-component/ts/app/rating.component.2.ts', 'template', 'rating.component.ts (template)')(format=".")
36+
37+
.l-sub-section
38+
:marked
39+
Learn more about this syntax at the [Template Syntax](../guide/template-syntax.html#!#star-template) chapter and [Structural Directives](../guides/structural-directives.html#!#asterisk).
40+
41+
:marked
42+
So here we say that we want a `<template>` element (the element itself won't output any markup) with the `ngFor` directive in it.
43+
We give to it the collection we want to repeat, `range`, and also that we want a reference to the current index in the collection.
44+
Notice that we aren't getting a reference to the current item on the `range` collection, we don't need it. We just need to generate that template as many times as we need.
45+
46+
We need to create that range collection now.
47+
48+
+makeExample('cb-rating-component/ts/app/rating.component.ts', 'range-attribute', 'rating.component.ts (class)')(format=".")
49+
50+
:marked
51+
Let's update our app component to see it in action.
52+
53+
+makeExample('cb-rating-component/ts/app/app.component.1.ts', null, 'app.component.ts')(format=".")
54+
55+
:marked
56+
If we check our browser now, we can see it working.
57+
58+
figure.image-display
59+
img(src="/resources/images/cookbooks/rating-component/static-component.png" alt="Static Component")
60+
61+
:marked
62+
And also the desired markup.
63+
64+
figure.image-display
65+
img(src="/resources/images/cookbooks/rating-component/generated-output.png" alt="Generated Output")
66+
67+
<a id="inputs"></a>
68+
:marked
69+
## Adding inputs
70+
71+
But not all heroes are as awesome as Windstorm! We should be able to initialize our rating component with a different value. That means that we need an input.
72+
73+
+makeExample('cb-rating-component/ts/app/rating.component.ts', 'rate-input', 'rating.component.ts (class)')(format=".")
74+
75+
:marked
76+
With the input we just need to apply a class or another.
77+
78+
+makeExample('cb-rating-component/ts/app/rating.component.3.ts', 'template', 'rating.component.ts (template)')(format=".")
79+
80+
:marked
81+
Now that we have the input, we can use it with static or dynamic values.
82+
83+
code-example(language="html", format=".")
84+
&lt;my-hero-rating rate="3"&gt;&lt;/my-hero-rating&gt;
85+
&lt;my-hero-rating [rate]="rate"&gt;&lt;/my-hero-rating&gt;
86+
87+
<a id="clicks"></a>
88+
:marked
89+
## Reacting to clicks
90+
91+
Bombasto is not a bad hero, let's make the rating clickable.
92+
93+
+makeExample('cb-rating-component/ts/app/rating.component.ts', 'rating-template', 'rating.component.ts (template)')(format=".")
94+
95+
:marked
96+
By clicking a star, we update our internal `rate` with the new value.
97+
98+
+makeExample('cb-rating-component/ts/app/rating.component.3.ts', 'update-method', 'rating.component.ts (class)')(format=".")
99+
100+
figure.image-display
101+
img(src="/resources/images/cookbooks/rating-component/click.gif" alt="Static Click")
102+
103+
<a id="outputs"></a>
104+
:marked
105+
## Adding outputs
106+
107+
When clicking the rating component, we want the updated rate back, so we need an output.
108+
109+
+makeExample('cb-rating-component/ts/app/rating.component.ts', 'rate-output', 'rating.component.ts (class)')(format=".")
110+
111+
:marked
112+
And we push the new rate in a click.
113+
114+
+makeExample('cb-rating-component/ts/app/rating.component.ts', 'update-method', 'rating.component.ts (class)')(format=".")
115+
116+
:marked
117+
That allows us to do two-way databinding.
118+
119+
code-example(language="html", format=".")
120+
&lt;my-hero-rating [(rate)]="rate"&gt;&lt;/my-hero-rating&gt;
121+
122+
:marked
123+
Here is the complete source for the rating component.
124+
125+
+makeExample('cb-rating-component/ts/app/rating.component.ts', null, 'rating.component.ts')(format=".")
126+
127+
:marked
128+
[Back to top](#top)
Loading
Loading
Loading

0 commit comments

Comments
 (0)