STEP:
Prerequist:
(1) NodeJS: download the latest version
(2) npm:
[sudo] npm install -g npm
(3) CLI:
[sudo] npm uninstall -g angular-cli @angular/cli
npm cache clean
[sudo] npm install -g @angular/cli
1. install the angular CLI tool
npm install -g @angular/cli
2. clean up and create a new project
ng new <project_name>
3. install bootstrap in this project
npm install --save bootstrap
.angular-cli.json
"styles" : [
"../node_modules/bootstrap/dist/css/bootstrap.min.css"
"styles.css"
]
4. http://localhost:4200
ng serve
如果这个port already in use:
lost -i : 4200
kill -q <PID>
5. create a new component without create the test files
ng g c <component_name> --spec false
6. create a new sub component without create the test files
ng g c <folder_name>/<sub_folder_name> --spec false
会generate automatically a component of 3 files:
* _________.component.ts
* _________.component.html
* _________.component.css
它会自动在app.module.ts里加这个component在@NgModule的declaration里。
Bindings
1.
* Property Binding : [ ]
* Event Binding : ( )
* Two-way Binding : [( )]
2. Two-way Binding Example
server.component.ts
export class ServerComponent implements OnInit {
ServerName = "initServerName";
}
server.component.html
<input type="text"
class="form-control"
[(ngModel)] = "serverName">
<p>{{ serverName }}</p>
FormsModule is Required for Two-way Binding!
Directives
1. output data conditionally
*ngIf
server.component.html
<p *ngIf="serverCreated; else noServer">......</p>
<ng-template #noServer>
<p>No Server is created!</p>
</ng-template>
server.component.ts
export class ServerComponent implements OnInit {
serverCreated = false;
onCreateServer() {
this.serverCreated = true;
}
}
2. Outputting List by ngFor
*ngFor
server.component.html
<app-server *ngFor="let server of servers"></app-server>
3. Unlike structural directives, attribute directives don't add or remove elements, they only change the element they were placed on.
*ngIf --------- structural directive
*ngFor ------- Outputting List by ngFor
[ngStyle] ----- attribute directive
4. "[ ]" wants to bind some properties to this element, this property's name happens to be "ngStyle".
[ngStyle]
server.component.html
<p [ngStyle] = "{backgroundColor: getColor() }"> ...... </p>
server.component.ts
getColor() {
return this.serverStatus === "online" ? "green" : "red";
}
Attribute Directives VS Structural Directives
1. Attribute Directives
* Look like a normal HTML attribute (possibly with data binding or event binding)
* Only affect / change the element they are added to
2. Structural Directives
* Look like a normal HTML attribute but have a leading * (for desugaring)
* Affect a whole area in the DOM (elements get added / removed)
Example for Custom Attribute Directive
- src
- app
- basic-highlight
- basic-highlight.directive.ts
- app.component.css
- app.component.html
- app.component.ts
- app.module.ts
1. app.component.ts
export class AppComponent {
addNumbers = [1, 3, 5];
evenNumbers = [2, 4];
onlyOdd = false;
}
2. app.component.css
.container {
margin-top : 30px;
}
.odd {
color: red;
}
3. app.component.html
<ul class="list-group">
<div *ngIf="onlyOdd">
<li class="list-group-item"
[ngClass] = "{odd: odd % 2 ! == 0}"
[ngStyle] = "{background: odd % 2 !== 0? 'yellow' : 'transparent'}"
*ngFor = "let odd of oddNumbers">
{{ odd }} </li></div>
<div *ngIf="!onlyOdd">
<li class="list-group-item"
[ngClass] = "{odd: even % 2 ! == 0}"
[ngStyle] = "{background: even % 2 !== 0? 'yellow' : 'transparent'}"
*ngFor = "let even of evenNumbers">
{{ even }} </li></div>
<p appBasicHighlight>Style me with basic directive!</p>
- use "onlyOdd=false" to make condition of *ngIf
- [ngClass] = "{odd: odd % 2 ! == 0}
* [ngClass] is for changing the attribute of .css file
* the 1st odd is the class attribute of .css file
* the 2nd odd comes from the *ngFor in the same HTML element
- <p appBasicHighlight>
appBasicHighlight is an attribute directive like [ngStyle], [ngClass], but our custom attribute directive.
4. basic-highlighting.directive.ts
import {Directive, ElementRef, OnInit} from '@angular/core';
@Directive ({
selector: '[appBasicHighLighting]'
})
export class BasicHighLightDirective implements OnInit {
constructor (private elementRef : ElementRef) {}
ngOnInit() {
this.elementRef.nativeElement.style.backgroundColor = 'green';
}
}
5. app.module.ts
import {BasicHighLightDirective} from './basic-highlight/basic-highlight.directive';
@NgModule ({
declarations: [
AppComponent,
BasicHighLightDirective
],
......
Better Example for Custom Attribute Directive ------ Renderer
- src
- app
- basic-highlight
- basic-highlight.directive.ts
- better-highlight
- better-highlight.directive.ts
- app.component.css
- app.component.html
- app.component.ts
- app.module.ts
6. better-highlight.directive.ts
import {Directive, RendererV2, OnInit, ElementRef} from '@angular/core';
@Directive ({
selector: '[appBetterHighLight]'
})
export class BetterHighLightDirective implements OnInit {
constructor (private elRef: ElementRef,
private renderer: Renderer) {}
ngOnInit() {
this.renderer.setStyle( this.elRef.nativeElement,
'background-color', 'blue', false, false);
}
}
- this.renderer.setStyle( this.elRef.nativeElement, 'background-color', 'blue', false, false);
better way to manipulate DOM
Always use Renderer to manipulate DOM!
HostListener => to Listen to Host Events
=> to react to any events
(Host: the element where the attribute directive places)
import {Directive,RendererV2, OnInit, ElementRef} from '@angular/core';
@Directive({
selector:'[appBetterHighLight]'
})
export class BetterHighLightDirective implements OnInit {
constructor (private elRef: ElementRef,
private renderer: Renderer) {}
ngOnInit() { }
@HostListener('mouseenter') mouseover(eventDate: Event) {
this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'blue', false, false);
}
@HostListener('mouseleave') mouseleave(eventDate: Event) {
this.renderer.setStyle(this.elRef.nativeElement, 'background-color', 'transparent', false, false);
}
}
* dynamic way to change the background color from transparent to blue when the mouse is over and back to transparent when the mouse is left.
* @HostListener takes events name as arguments (string). These events, names are supported by DOM site.
Communication between Components with @Output & @Input
Example 01:
show Recipe if 'recipe' is selected on the menu bar, otherwise show shopping list if 'shopping-list' is selected.
@Output()
header.component -----------------------> app.component
header.component.html
<a ... (click)="onSelected('recipe')">Recipes</a>
header.component.ts
@Output() featureSelected = new EventEmitter<string>();
onSelected(feature: string) {
this.featureSelected.emit(feature);
}
@Output() to let its parent component know this element.
app.component.html
<app-header (featureSelected) = "onNavigate($event)">
(featureSelected) is the event element get from its child component header.component.ts and ($event) is sent from header.component.ts (.emit).
app.component.ts
loadedFeature = 'recipe';
onNavigate(feature: string) {
this.loadedFeature = feature;
}
app.component.html
<app-recipe *ngIf="loadedFeature === 'recipe'"></app-recipe>
<app-shopping-list *ngIf="loadedFeature === 'shopping-list'"></app-shopping-list>
Example 02:
recipe-list is composed with recipe-items. So use recipe-item to show each item of recipe-list.
@Input()
recipe-list -----------------------> recipe-item
recipe-list.component.ts
recipes: Recipe[] = {
new Recipe( ...... ),
new Recipe( ...... )
}
recipe-list.component.html
<app-recipe-item
*ngFor = "let recipeEl of recipes"
[recipe] = "recipeEl">
</app-recipe-item>
recipe-item.component.ts
@Input() recipe: Recipe;
recipe-item.component.html
{{ recipe.name }}
{{ recipe.description }}
Example 03:
select one item from the recipe list and show its details in recipe detail area.
@Output() @Output()
recipe-item ---------------> recipe-list---------------> recipes ----
@Input()
-----------> recipe-detail
recipe-item.component.html
<a href="#" (click)="onSelected()" ></a>
{{ recipe.name }} {{ recipe.description }}
recipe-item.component.ts
@Output recipeSelected = new EventEmitter<void>();
@Input recipe: Recipe;
onSelected() {
this.recipeSelected.emit();
}
recipe-list.component.html
<app-recipe-item
*ngFor="let recipeEl of recipes"
[recipe] = "recipeEl"
(recipeSelected) = "onRecipeSelected (recipeEl)">
</app-recipe-item>
recipe-list.component.ts
@Output() recipeSelected = new EventEmitter<Recipe>();
onRecipeSelected (recipe: Recipe) {
this.recipeWasSelected.emit (recipe);
}
recipes.component.html
<app-recipe-list
(recipeWasSelected) = "selectedRecipe = $event"> </app-recipe-list>
<app-recipe-detail
*ngIf="selectedRecipe; else infoText"
[recipe] = "selectedRecipe"></app-recipe-detail>
<ng-template #infoText>
<p>Please select a Recipe!</p>
</ng-template>
(#infoText ------ local reference)
([recipe] = "selectedRecipe" -------- property bind)
recipes.component.ts
selectedRecipe: Recipe;
recipe-detail.component.ts
@Input() recipe: Recipe;
recipe-detail.component.html
{{ recipe.name }}
{{ recipe.imagePath }}
@ViewChild for <input> Form Element
shopping-edit.component.html
<input #nameInput>
<input #amountInput>
<button (click)="onAddedItem()"> Add </button>
shopping-edit.component.ts
@ViewChild('nameInput') nameInputRef: ElementRef;
@ViewChild('amountInput') amountInputRef: ElementRef;
@Output() ingredientAdded = new EventEmitter<Ingredient>();
onAddItem() {
const ingName = this.nameInputRef.nativeElementRef.value;
const ingAmountRef = this.amountRef.nativeElementRef.value;
const newIngredient = new Ingredient(ingName, ingAmout);
this.ingredientAdded.emit(newIngredient);
}
shopping-list.component.html
<app-shopping-edit
(ingredientAdded) = "onIngredientAdded($event)"> ......
</app-shopping-edit>
shopping-list.component.ts
ingredients: Ingredient[] = [
new Ingredient('Apple', 5),
new Ingredient('Tomatos', 10)
];
onIngredientAdded(ingredient: Ingredient) {
this.ingredients.push(ingredient);
}
Custom Attribute Directive
Build and use a Dropdown Directive
Before:
for opening a dropdown button
recipe-detail.component.html
<div class="btn-group open> ...... </div>
After:
use Attribute Directive (custom attribute)
{CLI}: ng g d ./shared/dropdown.directive.ts
dropdown.directive.ts
import {Directive, HostListener, HostBinding} from '@angular/core';
@Directive ({
selector: '[appDropdown]'
})
export class DropdownDirective {
@HostBinding('class.open') isOpen = false;
@HostListener('click') toggleOpen() {
this.isOpen = !this.isOpen;
}
}
recipe-detail.component.html
<div class="btn-group appdropdown> ...... </div>
app.module.ts
@NgModule ({
declarations: [
DropdownDirective
]
})