Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: rewrite event binding section and add example #26162

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
71 changes: 71 additions & 0 deletions aio/content/examples/event-binding/e2e/src/app.e2e-spec.ts
@@ -0,0 +1,71 @@
'use strict'; // necessary for es6 output in node

import { browser, element, by, protractor } from 'protractor';

describe('Event binding example', function () {

beforeEach(function () {
browser.get('');
});

let saveButton = element.all(by.css('button')).get(0);
let onSaveButton = element.all(by.css('button')).get(1);
let myClick = element.all(by.css('button')).get(2);
let deleteButton = element.all(by.css('button')).get(3);
let saveNoProp = element.all(by.css('button')).get(4);
let saveProp = element.all(by.css('button')).get(5);


it('should display Event Binding with Angular', function () {
expect(element(by.css('h1')).getText()).toEqual('Event Binding');
});

it('should display 6 buttons', function() {
expect(saveButton.getText()).toBe('Save');
expect(onSaveButton.getText()).toBe('on-click Save');
expect(myClick.getText()).toBe('click with myClick');
expect(deleteButton.getText()).toBe('Delete');
expect(saveNoProp.getText()).toBe('Save, no propagation');
expect(saveProp.getText()).toBe('Save with propagation');
});

it('should support user input', function () {
let input = element(by.css('input'));
let bindingResult = element.all(by.css('h4')).get(1);
expect(bindingResult.getText()).toEqual('Result: teapot');
input.sendKeys('abc');
expect(bindingResult.getText()).toEqual('Result: teapotabc');
});

it('should hide the item img', async () => {
let deleteButton = element.all(by.css('button')).get(3);
await deleteButton.click();
browser.switchTo().alert().accept();
expect(element.all(by.css('img')).get(0).getCssValue('display')).toEqual('none');
});

it('should show two alerts', async () => {
let parentDiv = element.all(by.css('.parent-div'));
let childDiv = element.all(by.css('div > div')).get(1);
await parentDiv.click();
browser.switchTo().alert().accept();
expect(childDiv.getText()).toEqual('Click me too! (child)');
await childDiv.click();
expect(browser.switchTo().alert().getText()).toEqual('Click me. Event target class is child-div');
browser.switchTo().alert().accept();
});

it('should show 1 alert from Save, no prop, button', async () => {
await saveNoProp.click();
expect(browser.switchTo().alert().getText()).toEqual('Saved. Event target is Save, no propagation');
browser.switchTo().alert().accept();
});

it('should show 2 alerts from Save w/prop button', async () => {
await saveProp.click();
expect(browser.switchTo().alert().getText()).toEqual('Saved.');
browser.switchTo().alert().accept();
expect(browser.switchTo().alert().getText()).toEqual('Saved.');
browser.switchTo().alert().accept();
});
});
Empty file.
25 changes: 25 additions & 0 deletions aio/content/examples/event-binding/src/app/app.component.css
@@ -0,0 +1,25 @@
.group {
background-color: #dae8f9;
padding: 1rem;
margin: 1rem 0;
}

.parent-div {
background-color: #bdd1f7;
border: solid 1px rgb(25, 118, 210);
padding: 1rem;
}

.parent-div:hover {
background-color: #8fb4f9;
}

.child-div {
margin-top: 1rem;
background-color: #fff;
padding: 1rem;
}

.child-div:hover {
background-color: #eee;
}
53 changes: 53 additions & 0 deletions aio/content/examples/event-binding/src/app/app.component.html
@@ -0,0 +1,53 @@
<h1 id="event-binding">Event Binding</h1>

<div class="group">
<h3>Target event</h3>
<!-- #docregion event-binding-1 -->
<button (click)="onSave($event)">Save</button>
<!-- #enddocregion event-binding-1 -->

<!-- #docregion event-binding-2 -->
<button on-click="onSave($event)">on-click Save</button>
<!-- #enddocregion event-binding-2 -->

<!-- #docregion custom-directive -->
<h4>myClick is an event on the custom ClickDirective:</h4>
<button (myClick)="clickMessage=$event" clickable>click with myClick</button>
{{clickMessage}}
<!-- #enddocregion custom-directive -->

</div>

<div class="group">
<h3>$event and event handling statements</h3>
<h4>Result: {{currentItem.name}}</h4>

<!-- #docregion event-binding-3-->
<input [value]="currentItem.name"
(input)="currentItem.name=$event.target.value" >
without NgModel
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should "without NgModel" be included in the snippet?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, those threw me, too. In the example, they are each showing the technique and it's comparing a handful of them side by side.

<!-- #enddocregion event-binding-3-->
</div>

<div class="group">
<h3>Binding to a nested component</h3>
<h4>Custom events with EventEmitter</h4>
<!-- #docregion event-binding-to-component -->
<app-item-detail (deleteRequest)="deleteItem($event)" [item]="currentItem"></app-item-detail>
<!-- #enddocregion event-binding-to-component -->


<h4>Click to see event target class:</h4>
<div class="parent-div" (click)="onClickMe($event)" clickable>Click me (parent)
<div class="child-div">Click me too! (child) </div>
</div>

<h3>Saves only once:</h3>
<div (click)="onSave()" clickable>
<button (click)="onSave($event)">Save, no propagation</button>
</div>

<h3>Saves twice:</h3>
<div (click)="onSave()" clickable>
<button (click)="onSave()">Save with propagation</button>
</div>
27 changes: 27 additions & 0 deletions aio/content/examples/event-binding/src/app/app.component.spec.ts
@@ -0,0 +1,27 @@
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(`should have as title 'Featured product:'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('Featured product:');
}));
it('should render title in a p tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('p').textContent).toContain('Featured product:');
}));
});
29 changes: 29 additions & 0 deletions aio/content/examples/event-binding/src/app/app.component.ts
@@ -0,0 +1,29 @@
import { Component } from '@angular/core';
import { Item } from './item';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {

currentItem = { name: 'teapot'} ;
clickMessage = '';

onSave(event: KeyboardEvent) {
const evtMsg = event ? ' Event target is ' + (<HTMLElement>event.target).textContent : '';
alert('Saved.' + evtMsg);
if (event) { event.stopPropagation(); }
}

deleteItem(item: Item) {
alert(`Delete the ${item}.`);
}

onClickMe(event: KeyboardEvent) {
const evtMsg = event ? ' Event target class is ' + (<HTMLElement>event.target).className : '';
alert('Click me.' + evtMsg);
}

}
22 changes: 22 additions & 0 deletions aio/content/examples/event-binding/src/app/app.module.ts
@@ -0,0 +1,22 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';


import { AppComponent } from './app.component';
import { ItemDetailComponent } from './item-detail/item-detail.component';
import { ClickDirective } from './click.directive';


@NgModule({
declarations: [
AppComponent,
ItemDetailComponent,
ClickDirective
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
18 changes: 18 additions & 0 deletions aio/content/examples/event-binding/src/app/click.directive.ts
@@ -0,0 +1,18 @@
/* tslint:disable use-output-property-decorator directive-class-suffix */
import { Directive, ElementRef, EventEmitter, Output } from '@angular/core';

@Directive({selector: '[myClick]'})
export class ClickDirective {
@Output('myClick') clicks = new EventEmitter<string>(); // @Output(alias) propertyName = ...

toggle = false;

constructor(el: ElementRef) {
el.nativeElement
.addEventListener('click', (event: Event) => {
this.toggle = !this.toggle;
this.clicks.emit(this.toggle ? 'Click!' : '');
});
}
}

@@ -0,0 +1,11 @@
.detail {
border: 1px solid rgb(25, 118, 210);
padding: 1rem;
margin: 1rem 0;
}

img {
max-width: 100px;
display: block;
padding: 1rem 0;
}
@@ -0,0 +1,9 @@
<div class="detail">
<p>This is the ItemDetailComponent</p>
<!-- #docregion line-through -->
<img src="{{itemImageUrl}}" [style.display]="displayNone">
<span [style.text-decoration]="lineThrough">{{ item.name }}
</span>
<button (click)="delete()">Delete</button>
<!-- #enddocregion line-through -->
</div>
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { ItemDetailComponent } from './item-detail.component';

describe('ItemDetailComponent', () => {
let component: ItemDetailComponent;
let fixture: ComponentFixture<ItemDetailComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ItemDetailComponent ]
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(ItemDetailComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,30 @@
/* tslint:disable use-input-property-decorator use-output-property-decorator */
import { Component, EventEmitter, Input, Output } from '@angular/core';

import { Item } from '../item';

@Component({
selector: 'app-item-detail',
styleUrls: ['./item-detail.component.css'],
templateUrl: './item-detail.component.html'
})
export class ItemDetailComponent {

@Input() item;
itemImageUrl = 'assets/teapot.svg';
lineThrough = '';
displayNone = '';
@Input() prefix = '';

// #docregion deleteRequest
// This component makes a request but it can't actually delete a hero.
@Output() deleteRequest = new EventEmitter<Item>();

delete() {
this.deleteRequest.emit(this.item.name);
this.displayNone = this.displayNone ? '' : 'none';
this.lineThrough = this.lineThrough ? '' : 'line-through';
}
// #enddocregion deleteRequest

}
4 changes: 4 additions & 0 deletions aio/content/examples/event-binding/src/app/item.ts
@@ -0,0 +1,4 @@
export class Item {
name: '';
}

1 change: 1 addition & 0 deletions aio/content/examples/event-binding/src/assets/teapot.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions aio/content/examples/event-binding/src/index.html
@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>EventBinding</title>
<base href="/">

<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>
12 changes: 12 additions & 0 deletions aio/content/examples/event-binding/src/main.ts
@@ -0,0 +1,12 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));
10 changes: 10 additions & 0 deletions aio/content/examples/event-binding/stackblitz.json
@@ -0,0 +1,10 @@
{
"description": "Event Binding",
"files": [
"!**/*.d.ts",
"!**/*.js",
"!**/*.[1,2].*"
],
"file": "src/app/app.component.ts",
"tags": ["Event Binding"]
}