Last updated: November 15, 2020 8:44 PM
Angular 8 Demo Project Log and Experience Part 1.
This is a log for a Angular Demo project. Through this project, it’s a good chance to review the Angular concept and learn deeper.
This part 1 focus on the basic concept about binding:
- data binding
- event binding
- template binding
- @Input (property binding)
- @Output (event binding)
Step 1: Create new project
ng new my-app
Step 2: Init AppComponent
interface TopMenu {
title: string;
readonly link?: string;
}
export class AppComponent {
title = 'App title';
menus: TopMenu[] = [{ title: 'HOT', link: '' }, { title: 'TOY', link: '' }];
}
<ul>
<li *ngFor="let menu of menus">
<a href="#"> {{menu.title}} </a>
</li>
</ul>
To let the menus layout as a flat menu, need to use css to config. The main setting is flex-direction
and white-space
.
To avoid the scrollbar in the top menus, use ::-webkit-scrollbar
.
ul {
padding: 0;
margin: 0;
display: flex;
flex-direction: row;
}
ul li {
display: inline-block;
margin: 0 5px;
white-space: nowrap;
}
a {
text-decoration: none;
}
::-webkit-scrollbar {
display: none;
}
Step 3: ngFor and ngIf, data binding and event binding
*ngFor
- Syntax:
let menu of menus
- Get Index:
let menu of menus; let i = index
- Get First or Last Element:
let menu of menus; let first = first; let last = last
- Get Even or Odd Elements:
let menu of menus; let odd = odd; let even = even
- Improve performance:
let menu of menus; trackBy: trackElement
- Data Binding: [], e.g.
[class.active]="i === selectedIndex"
- Event Binding: (), e.g.
(click)="handleSelection(i)"
*ngIf
syntax 1:
<div *ngIf="condition">View exposed when the condition is true.</div>
syntax 2:
<div *ngIf="condition else elseContent">
Content exposed when the condition is true.
</div>
<ng-template #elseContent
>Content exposed when the condition is false.</ng-template
>
syntax 3:
<div *ngIf="condition; then thenTemplate; else elseContent"></div>
<ng-template #thenContent
>Content exposed when the condition is true.</ng-template
>
<ng-template #elseContent
>Content exposed when the condition is false.</ng-template
>
Modify the clicked item style
When the index is selected, use the active css style. How to monitor the index is selected? Use the event binding, (onclick)
to handle the selection, this function pass the index as a parameter.
<ul>
<li *ngFor="let menu of menus">
<a href="#"
[class.active]="i === selectedIndex"
(click)="handleSelection(i)"
> {{menu.title}} </a>
</li>
</ul>
.active {
color: red;
}
export class AppComponent {
selectedId = -1;
handleSelection(index: number) {
this.selectedIndex = index;
this.tabSelected.emit(this.menus[this.selectedIndex]);
}
}
To improve the ngFor, we can add:
<li
*ngFor="let menu of menus; let i = index; trackBy: menu ? menu.title : null"
></li>
Step 4: Refactor Component: create ScrollableTabComponent
To make the logic clearly, move the logic codes from AppComponent to ScrollableTabComponent, chang the AppComponent to call the ScrollableTabComponent.
<app-scrollable-tab></app-scrollable-tab>
Remember to add ScrollableTabComponent to the app.module.ts
in imports
array.
Step 5: add feature in ScrollableTabComponent
We can use ngStyle
to define the style in html.
<li>
<span
class="indicator"
[ngStyle]="{ 'background-color': indicatorColor }"
*ngIf="menu.link === selectedTabLink else elseTemplate"
></span>
</li>
<ng-template #elseTemplate>
<span>Just test</span>
</ng-template>
ul li {
display: flex;
flex-direction: column;
justify-content: space-between;
align-content: center;
margin: 0 5px;
padding: 0;
white-space: nowrap;
}
.indicator {
/* background-color: brown; */
height: 2px;
/* width: 2rem; */
margin-top: 2px;
}
Template Binding
Several ways to realize Template Binding:
<div [class.className]="condition">...</div>
<div [ngClass]="{'One': true, 'Two': true, 'Three': false}">...</div>
<div [ngStyle]="{'color':someColor, 'font-size':fontSize}">...</div>
For example, we want to set background color, font color, font weight from upper component, then we can code like this.
In
scollable-tab.component.html
, we add[ngStyle]="{ 'background-color': backgroundColor }
in tag<ul>
to set the background color of the list. And backgroundColor is the input variable from father component.
@Input (property binding) and @Output (event binding)
Use @Input
and @Output
to decorate the property and event.
The @Input
decorator indicates that the property value will be passed in from the component’s parent (in this case, the product list component).
Define a property named tabSelected with an @Output
decorator and an instance of event emitter. This makes it possible for the product alert component to emit an event when the value of the notify property changes.
We have created a menu in ScrollableTabComponent
, we want to reuse it somewhere. So, we add a decorator @Input
before the menu, indicating the data of this menu field is set by father component.
export class ScrollableTabComponent {
@Input() selectedTabLink: string;
@Input() menus: TopMenu[] = [];
@Input() backgroundColor = '#fff';
@Input() titleActiveColor = 'yellow';
@Input() titleColor = 'blue';
@Input() indicatorColor = 'brown';
@Output() tabSelected = new EventEmitter();
handleSelection(index: number) {
this.selectIndex = index;
this.tabSelected.emit(this.menus[index]);
}
}
<ul [ngStyle]="{ 'background-color': backgroundColor }">
<li *ngFor="let menu of menus; let i = index">
<a
[ngStyle]="{ color: i === selectIndex ? titleActiveColor : titleColor }"
(click)="handleSelection(i)"
>
{{ menu.title }}
</a>
<span
class="indicator"
[ngStyle]="{ 'background-color': indicatorColor }"
*ngIf="menu.link === selectedTabLink"
></span>
</li>
</ul>
Call the child component in parent component. Use all the bindings:
- Template Binding
- Property Binding
- Event Binding
<app-scrollable-tab
[menus]="topMenus$ | async"
(tabSelected)="handleTabSelected($event)"
[backgroundColor]="'#fff'"
titleColor="#3f3f3f"
titleActiveColor="red"
indicatorColor="red"
[selectedTabLink]="selectedTabLink$ | async"
>
</app-scrollable-tab>
handleTabSelected(topMenu: TopMenu) {
// this.router.navigate(['home', topMenu.link]);
console.log(topMenu);
}