According to the mockup, we have an array of numbers which appears on pages: Games (Home), Play Games and History. Below is my Home page after finishing:
So creating a component presenting array of numbers for reusing in pages is a good idea. Let generate this component:
ionic g component components/NumbersPanelIt will generate files:
src/app/components/numbers-panel/numbers-panel.component.html
src/app/components/numbers-panel/numbers-panel.component.spec.ts
src/app/components/numbers-panel/numbers-panel.component.ts
src/app/components/numbers-panel/numbers-panel.component.scss
Modify numbers-panel.component.ts as below:
Modify numbers-panel.component.html as below:import { Component, OnInit, Input } from '@angular/core';@Component({
selector: 'app-numbers-panel',
templateUrl: './numbers-panel.component.html',
styleUrls: ['./numbers-panel.component.scss']
})
export class NumbersPanelComponent implements OnInit {
@Input() numbers: any = [];
@Input() minval: number;
@Input() maxval: number;constructor() {
}ngOnInit() {
}
}
<div class="box-number-outline">Modify numbers-panel.component.scss as below:
<div class="box-number" *ngFor="let n of numbers">{{n}}</div>
</div>
.box-number-outline {To allow this component used in pages, let create a file src\app\components\components.module.ts and add below code (this file can be created with the command ionic g module components/components, however I got error 'Tree type is not supported' - ng cli error so I created it manually):
text-align: center;
}
.box-number {
width: 35px;
height: 25px;
background-color: purple;
display: inline-block;
border: solid 1px white;
color: white;
padding-top: 2px;
}
OK, we already had our component, let use it in Home page by declaring some things in src\app\home\home.module.ts:import { NgModule } from '@angular/core';
import { NumbersPanelComponent } from './numbers-panel/numbers-panel.component';
import { IonicModule } from '@ionic/angular';
import { CommonModule } from '@angular/common';@NgModule({
declarations: [NumbersPanelComponent],
imports: [CommonModule , IonicModule],
exports: [NumbersPanelComponent]
})export class ComponentsModule {}
Below is new code of src\app\home\home.page.ts:...
import { ComponentsModule } from '../components/components.module';...@NgModule({
imports: [
...
ComponentsModule,
…
And new code of src\app\home\home.page.html:import { Component, OnInit } from '@angular/core';
import { AuthService } from '../services/auth.service';
import { MyTablesService } from '../services/my-tables.service';
import { NavController, AlertController } from '@ionic/angular';@Component({
selector: 'app-home',
templateUrl: './home.page.html',
styleUrls: ['./home.page.scss'],
})
export class HomePage implements OnInit {
games: any = [];
history: any = [];constructor(
private auth: AuthService,
private tableService: MyTablesService,
private navCtrl: NavController,
private alertCtrl: AlertController
) { }ngOnInit() {
this.auth.loggedin().then(isLoggedin => {
if (!isLoggedin) {
this.navCtrl.goRoot('/login');
}
});
}ionViewWillEnter() {
this.loadGames();
}loadGames() {
this.tableService.getItems('games').then((d) => {
this.games = d;
this.tableService.getItems('history').then((h) => {
this.history = h;
for(let game of this.games) {
let gh = this.tableService.getItemByField(this.history, 'game_id', game.id);
if(gh.length > 0) {
gh.sort(this.tableService.sortDescByField('play_time'));
game.last_play_time = new Date(gh[0].play_time).toLocaleString('vi-VN');
game.last_result = JSON.parse(gh[0].result);
}
}
});
});
}newGame() {
this.navCtrl.goForward('/new-game');
}playGame(game_id) {
this.navCtrl.goForward(`/play-game/${game_id}`);
}async deleteGame(game_id) {
let alert = await this.alertCtrl.create({
message: 'Do you want to delete this game?',
buttons: [
{
text: 'Cancel',
role: 'cancel',
handler: () => {
}
},
{
text: 'OK',
handler: () => {
this.tableService.setItem('games', game_id, 'deleted', true);
}
}
]
});
await alert.present();
}
}
Pay your attention on the code <app-numbers-panel [numbers]="game.last_result"></app-numbers-panel> : ==> it will show the NumbersPanel component and put the last result of game to the component for displaying.<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title>Games</ion-title>
</ion-toolbar>
</ion-header><ion-content padding>
<ion-grid *ngFor="let game of games; let i = index;">
<div [ngClass]="(i % 2 == 0) ? 'grid_odd' : 'grid_even'" *ngIf="!game.deleted">
<ion-row>
<ion-col size="11">
<b>{{game.name}}</b>
</ion-col>
<ion-col size="1">
<div class="play_icon">
<ion-icon name="arrow-dropright-circle" style="zoom:1.2;" (click)=playGame(game.id)></ion-icon>
</div>
</ion-col>
</ion-row>
<ion-row>
<ion-col size="11">
<div class="last_play_time" *ngIf="game.last_play_time">
Last time: {{game.last_play_time}}
</div>
<div class="last_play_time" *ngIf="!game.last_play_time">
Haven't been played yet. Let play.
</div>
</ion-col>
<ion-col size="1">
<div class="delete_icon">
<ion-icon name="close-circle" style="zoom:1.2;" (click)=deleteGame(game.id)></ion-icon>
</div>
</ion-col>
</ion-row>
<ion-row>
<ion-col size="3">
<div class="last_result" *ngIf="game.last_play_time">
Last result:
</div>
</ion-col>
<ion-col size="9">
<app-numbers-panel [numbers]="game.last_result"></app-numbers-panel>
</ion-col>
</ion-row>
</div>
</ion-grid>
<ion-fab vertical="bottom" horizontal="end" slot="fixed">
<ion-fab-button color="danger" (click)="newGame()">
<ion-icon name="add"></ion-icon>
</ion-fab-button>
</ion-fab>
</ion-content>
On above html file, I also make alternate color for game rows, watch the code: [ngClass]="(i % 2 == 0) ? 'grid_odd' : 'grid_even'".
At this time, your Home page can list games but won't have any info about last playing result of games. So let generate PlayGame page and play, run the command:
ionic g page PlayGameModify src\app\play-game\play-game.page.ts as below:
Modify src\app\play-game\play-game.page.html as below:import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { History, MyTablesService } from '../services/my-tables.service';
import { delay } from 'q';@Component({
selector: 'app-play-game',
templateUrl: './play-game.page.html',
styleUrls: ['./play-game.page.scss'],
})
export class PlayGamePage implements OnInit {
game: any;
numbers: any = [];
history: History;
curTime: any;
played: boolean = false;constructor(
private activeRoute: ActivatedRoute,
private tableService: MyTablesService) {
this.curTime = new Date().toLocaleString('vi-VN');
this.history = new History();
let game_id = this.activeRoute.snapshot.paramMap.get('id');
this.tableService.getItem('games', game_id).then((d) => {
this.game = d;
for(let i=0; i < this.game.numbers; i++) {
this.numbers.push(-1);
}
});
}ngOnInit() {
}async startGame() {
for(let i = 1; i < 3; i++) {
for(let j = this.game.min_number; j <= this.game.max_number; j++) {
for(let k=0; k < this.game.numbers; k++) {
this.numbers[k] = j;
}
await delay(200);
}
}
for(let i=0; i < this.game.numbers; i++) {
this.numbers[i] = Math.floor(Math.random() * this.game.max_number) + this.game.min_number;
}this.saveHistory();
this.played = true;
}saveHistory() {
this.history.play_time = new Date();
this.history.game_id = this.game.id;
this.history.result = JSON.stringify(this.numbers);
this.tableService.addItem('history', this.history);
}
}
In the function startGame(), I roll numbers from its min to max and delay 200ms when changing numbers. It is done 2 times and finished by randomizing numbers between its min & max. This is how I make the animation for rolling numbers -:)<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title>Play Game</ion-title>
</ion-toolbar>
</ion-header><ion-content padding>
<div *ngIf="game" style ="text-align: center; padding: 10px;">
<b>{{game.name}}</b><br>
<i>{{curTime}}</i><br>
</div>
<app-numbers-panel [numbers]="numbers"></app-numbers-panel>
<div style ="text-align: center; padding: 10px;">
<ion-button size="default" shape="round" color="secondary" (click)="startGame()" [disabled]="played">
Start
</ion-button>
</div>
</ion-content>
Yeah! Now we can create games, play them and have a fun. See how I play a game:
After playing, we may need to see the history of playing games. It's time to code the History page.
Modify src\app\history\history.page.ts as below:
Modify src\app\history\history.page.html as below:import { Component, OnInit } from '@angular/core';
import { AuthService } from '../services/auth.service';
import { MyTablesService } from '../services/my-tables.service';
import { NavController } from '@ionic/angular';@Component({
selector: 'app-history',
templateUrl: './history.page.html',
styleUrls: ['./history.page.scss'],
})
export class HistoryPage implements OnInit {
games: any = [];
history: any = [];
items: any = [];
keywords: string = '';
showSearchBar: boolean = true;constructor(
private auth: AuthService,
private tableService: MyTablesService,
private navCtrl: NavController) { }ngOnInit() {
this.auth.loggedin().then(isLoggedin => {
if (!isLoggedin) {
this.navCtrl.goRoot('/login');
}
});
}
ionViewWillEnter() {
this.loadGames();
}
loadGames() {
this.tableService.getItems('games').then((d) => {
this.games = d;
this.items = d;
this.tableService.getItems('history').then((h) => {
this.history = h;
for(let game of this.games) {
let gh = this.tableService.getItemByField(this.history, 'game_id', game.id);
game.pcount = gh.length;
}
});
});
}searchGames() {
this.items = this.games.filter((item) => {
return item.name.toLowerCase().indexOf(this.keywords.toLowerCase()) > -1;
});
}toggleSarchBar() {
this.showSearchBar = !this.showSearchBar;
}showGameHistory(game_id) {
this.navCtrl.goForward(`/game-history/${game_id}`);
}
}
There is a tricky that I used to make the icon search can be clicked on the tool bar, that is wrapping the icon with <div class="button">. Without class="button", we cannot click on the icon. I don't know if it is normal behavior of Ionic 4 or a bug.<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title>History</ion-title>
<ion-buttons slot="end">
<div class="button">
<ion-icon name="search" style="zoom:1.2;" (click)="toggleSarchBar()"></ion-icon>
</div>
</ion-buttons>
</ion-toolbar>
</ion-header><ion-content padding>
<div *ngIf="showSearchBar" [hidden]="!showSearchBar">
<ion-searchbar [(ngModel)]="keywords" (ionChange)="searchGames()" placeholder="Filter games" debounce="1000"></ion-searchbar>
</div>
<ion-grid *ngFor="let game of items; let i = index;">
<div [ngClass]="(i % 2 == 0) ? 'grid_odd' : 'grid_even'" *ngIf="!game.deleted" (click)="showGameHistory(game.id)">
<b>{{game.name}}</b><br/>
<i>Created time: {{game.created_time}}</i><br/>
Played: {{game.pcount}} times
</div>
</ion-grid>
</ion-content>
The search bar (ion-searchbar) also is set debounce="1000" to avoid filter immediately when keying. This debounce="1000" means if you stop keying in 1 second, the input value will be fired to ionChange event.
The last page is GameHistory page to show all playing history of a game. Let generate it:
ionic g page GameHistoryThis page will use the NumbersPanel component, so let import ComponentsModule to its module file src\app\game-history\game-history.module.ts.
Basically, coding of this page is nearly same with other pages we done, except that it has new component for rating playing results. But I want to keep this for next article.
In next article, we may study how to make general CSS applied for all pages and code a rating component.
I hope that through Part 1 - Part 2 - Part 3, you can start to code your real application with Ionic 4. In case you want grab the source code, let check https://github.com/vnheros/GameLot on my GitHub.
See you. Any comment is welcome!