$ ng new <app-name>
src/app
$ ng new google-suggest --skip-install
$ cd google-suggest
$ ls src/app
app.component.css app.component.spec.ts app.module.ts
app.component.html app.component.ts
app.component.ts
, app.component.html
, app.component.css
are component class, template and CSS fileapp.module.ts
is the “root module” of the app$ ng build --prod
dist/
$ ng serve --host 0.0.0.0
ng serve
is only for development not for deployment$ ng new google-suggest --minimal
$ ng generate component <component-name>
$ ng generate component search-box
$ ng generate component display
$ ls src/app
app.component.css app.component.ts search-box/
app.component.html app.module.ts
app.component.spec.ts display/
<!-- search-box.component.html -->
<form action="http://www.google.com/search">
<input type="text" name="q"><input type="submit">
</form>
<!-- display.component.html -->
<div>Suggestion here</div>
<!-- app.component.html -->
<app-search-box></app-search-box>
<app-display></app-display>
app-search-box
, app-display
, …<app-search-box>
tag corresponds to SearchBoxComponent
?@Component
decorator:// search-box.component.ts
@Component({
selector: 'app-search-box',
templateUrl: './search-box.component.html',
styleUrls: ['./search-box.component.css']
})
export class SearchBoxComponent
title
property of AppComponent
class in its template?// app.component.ts
title = "google-suggest";
<!-- app.component.html -->
<h1>{{ title }}</h1>
{{ expression }}
expression
is replaced with the result of the expressionexpression
should have no side effect// search-box.component.ts
defaultQuery = "UCLA";
<!-- search-box.component.html -->
<input type="text" name="q" [value]="defaultQuery">
[property]="expression"
expression
property
expression
changes, the property
value is dynamically updated<!-- search-box.component.html -->
<input type="submit" (click)="showAlert();">
(event)="statement;"
statement
when event
is triggeredstatement
may have side effect// search-box.component.ts
showAlert() { alert("No USC Please!"); }
<input type="submit" (click)="showAlert();">
<app-search-box>
can also
<!-- app.component.html -->
<app-search-box [query]="title" (input)="handleInput($event);">
</app-search-box>
title
value of AppComponent
to query
property of SearchBoxComponent
handleInput
method of AppComponent
when input
event is thrown by SearchBoxComponent
Angular component is like a user-defined HTML DOM element!
<input type="submit" onclick="callback();">
<search-box [query]="'UCLA'" (click)="callback();"></search-box>
Almost one-to-one mapping between the two
<p>
<app-display>
attr="val"
[prop]="expr"
onevent="f();"
(event)="stmt;"
<!-- app.component.html -->
<app-search-box [defaultQuery]="title"></app-search-box>
title
value (of AppComponent
) to defaultQuery
property (of SearchBoxComponent
)defaultQuery
is an input of SearchBoxComponent
@Input()
decorator to allow property binding// search-box.component.ts
import { Input } from '@angular/core';
@Input() defaultQuery: string;
<input type="text" name="q" value="query">
vs<input type="text" name="q" [value]="query">
vs<input type="text" name="q" [value]="'query'">
<!-- app.component.html -->
<app-search-box (input)="handleInput($event);"></app-search-box>
handleInput()
of AppComponent
as the input
event handler from SearchBoxComponent
$event
is the standard DOM event
object in this caseSearchBoxComponent
, like input
, “bubble up” through the component<!-- app.component.html -->
<app-search-box (advice)="handleAdvice($event);"></app-search-box>
advice
event is thrown, call handleAdvice($event)
of AppComponent
EventEmitter
object and assign it to a property
@Output()
decorator to make it available for event bindingemit(obj)
on the property
obj
is passed as the $event
object// search-box.component.ts
import { EventEmitter, Output } from '@angular/core';
...
@Output() advice = new EventEmitter<string[]>();
...
this.advice.emit(["Yes UCLA", "No USC"]);
$event
Object<!-- app.component.html -->
<app-search-box (advice)="handleAdvice($event);"></app-search-box>
$event
is Angular event object$event
object can be
e.g., <input type="submit" (click)="showAlert($event);">
EventEmitter
e.g., <app-search-box (advice)="handleAdvice($event);"></app-search-box>
*ngIf
, *ngFor
, *ngSwitch
*ngIf
<img [src]="imgUrl" *ngIf="imgUrl">
*ngIf="expression"
expression
is “true”*ngFor
<ul>
<li *ngFor="let item of items">{{ item.name }}</li>
</ul>
*ngFor="let a of list"
list
a
: template input variablengSwitch
<ng-container [ngSwitch]="media.type">
<img [src]="media.url" *ngSwitchCase="'image'">
<video [src]="media.url" *ngSwitchCase="'video'"></video>
<embed [src]="media.url" *ngSwitchDefault>
</ng-container>
<e [ngSwitch]="expression">
<e1 *ngSwitchCase="case_expression1">
...
<en *ngSwitchDefault>
</e>
expression == case_expression
default
element(s) if no match<ng-container>
to group multiple elements@Input
, @Output
, EventEmitter
AppComponent
and two children
SearchBoxComponent
DisplayComponent
SearchBoxComponent
and DisplayComponent
?SearchBoxComponent
DisplayComponent
DisplayComponent
takes one input property<app-display [suggestions]="listOfSuggestions">
SearchBoxComponent
provides two output events<app-search-box (userInput)="handleUserInput($event);"
(submit)="handleSubmit($event);">
$event
objects:
userInput
event: current “query” in the input boxsubmit
event: DOM Event
object, so that parent can “veto” query submission if neededDisplayComponent
<!-- display.component.html -->
<ul>
<li *ngFor="let suggestion of suggestions">{{suggestion}}</li>
</ul>
// display.component.ts
import { Input } from '@angular/core';
@Input() suggestions: string[];
SearchBoxComponent
<!-- search-box.component.html -->
<form action="http://www.google.com/search">
<input type="text" name="q" (input)="handleInput($event);">
<input type="submit" (click)="handleSubmit($event)">
</form>
// search-box.component.ts
import { EventEmitter, Output } from '@angular/core';
query: string = "";
@Output() userInput = new EventEmitter<string>();
@Output() submit = new EventEmitter<Event>();
SearchBoxComponent
// search-box.component.ts
handleInput(event) {
this.query = event.target.value;
if (this.noUSC(this.query)) {
this.userInput.emit(this.query);
} else {
alert("No USC query please!");
}
}
handleSubmit(event) {
if (this.noUSC(this.query)) {
this.submit.emit(event);
} else {
alert("No USC query please!");
event.preventDefault();
}
}
noUSC(query) { return !(/(^| )USC($| )/i.test(query)); }
userInput
event, send query to Google suggest server and display result in DisplayComponent
userInput
Event<!-- app.component.html -->
<app-search-box (userInput)="getSuggestions($event)"></app-search-box>
<app-display [suggestions]="suggestions"></app-display>
// app.component.ts
suggestions: string[] = [];
getSuggestions(query: string) {
// for now, we just show user input as suggestions
this.suggestions = [query];
}
getSuggestions()
functionality into SuggestionService
getSuggestions()
is used only by AppComponent
SuggestionService
API
getSuggestions(query): Promise<string[]>
$ ng generate service suggestion
$ ls -l
suggestion.service.spec.ts suggestion.service.ts
SuggestionService
: Implementation// suggestion.service.ts
async getSuggestions(query: string): Promise<string[]> {
let res = await fetch("http://oak.cs.ucla.edu/classes/cs144/examples/google-suggest.php?q="+encodeURI(query));
let text = await res.text();
let parser = new DOMParser();
let xml = parser.parseFromString(text,"text/xml");
let s = xml.getElementsByTagName('suggestion');
let suggestions = [];
for (let i = 0; i < s.length; i++) {
suggestions.push(s[i].getAttribute("data"));
}
return suggestions; // automatically converted to Promise with async
}
A
and B
, need to use SuggestionService
who should “create” and “own” SuggestionService
? A
or B
?constructor(..., serv: NeededService, ...)
// app.component.ts
import { SuggestionService } from './suggestion.service';
...
public suggestionService: SuggestionService;
constructor(suggestionService: SuggestionService) {
this.suggestionService = suggestionService;
}
// app.component.ts
import { SuggestionService } from './suggestion.service';
...
constructor(public suggestionService: SuggestionService) {}
// app.component.ts
import { SuggestionService } from './suggestion.service';
constructor(public suggestionService: SuggestionService) {}
async getSuggestions(query) {
if (query.length > 0) {
this.suggestions = await this.suggestionService.getSuggestions(query);
}
}