[ตอนที่ 9] Social App Workshop ด้วย ASP.NET Core 3 กับ Angular 9 สร้าง User Interface สวย ๆ

Line
Facebook
Twitter
Google

สารบัญเนื้อหา

  1. สร้าง Interfaces โดยใช้ Typescript
  2. สร้าง Angular Services
  3. ส่งข้อมูล Members ไปยัง Member List Component
  4. สร้าง Member Card เพื่อแสดงในรายการ Members
  5. เพิ่ม Effect Animation ใน Member Card
  6. ใช้ Auth0 JwtModule ในการส่ง Token เมื่อดึงข้อมูลโดยอัตโนมัติ
  7. สร้าง Detail View Component เพื่อแสดงรายละเอียดของ Members
  8. ออกแบบ Template เพื่อแสดงรายละเอียดของ Members
  9. ใช้งาน Tap ในการแสดงรายละเอียดของ Members
  10. ใช้งาน Route Resolvers เพื่อรับส่งข้อมูล
  11. เพิ่ม Photo Gallery

1. สร้าง Interfaces โดยใช้ Typescript

สร้าง Interfaces สำหรับโครงสร้างของ Users และ Photos โดยจัดเก็บภายใต้ _models ดังนี้

_models/photo.cs

export interface Photo {
    id: number;
    url: string;
    description: string;
    dateAdded: Date;
    isMain: boolean;
}

_models/user.ts

import { Photo } from './photo';

export interface User {
    id: number;
    username: string;
    knowAs: string;
    age: number;
    gender: string;
    created: Date;
    lastActive: Date;
    photoUrl: string;
    city: string;
    country: string;
    interests?: string;
    introduction?: string;
    lookingFor?: string;
    photos?: Photo[];
}

2. สร้าง Angular Services

เพิ่ม Environment ใน environments/environment.ts

export const environment = {
  production: false,
  apiUrl: 'http://localhost:5000/api/'
};

แล้วทำการปรับปรุง Url ของ API ใน auth.service.ts เป็น

baseUrl = environment.apiUrl + 'auth/';

สร้าง User Services ภายใต้ _services ชื่อ user.service.ts

import { Injectable } from '@angular/core';
import { environment } from 'src/environments/environment';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { User } from '../_models/user';

const httpOptions = {
  headers: new HttpHeaders({
    Authorization: 'Bearer ' + localStorage.getItem('token')
  })
};

@Injectable({
  providedIn: 'root'
})
export class UserService {
  baseUrl = environment.apiUrl;

  constructor(private http: HttpClient) { }

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>(this.baseUrl + 'users', httpOptions);
  }

  getUser(id): Observable<User> {
    return this.http.get<User>(this.baseUrl + 'users/' + id, httpOptions);
  }

}

3. ส่งข้อมูล Members ไปยัง Member List Component

ปรับปรุงโค้ดใน member-list.component.ts เพื่อโหลดข้อมูลจาก API ผ่าน Service ที่ได้สร้างไว้ในหัวข้อก่อนหน้านี้

import { Component, OnInit } from '@angular/core';
import { User } from '../_models/user';
import { UserService } from '../_services/user.service';
import { AlertifyService } from '../_services/alertify.service';

@Component({
  selector: 'app-member-list',
  templateUrl: './member-list.component.html',
  styleUrls: ['./member-list.component.css']
})
export class MemberListComponent implements OnInit {
  users: User[];

  constructor(private userService: UserService, private alertify: AlertifyService) { }

  ngOnInit() {
    this.loadUsers();
  }

  loadUsers() {
    this.userService.getUsers().subscribe((users: User[]) => {
      this.users = users;
    }, error => {
      this.alertify.error(error);
    });
  }

}

ปรัปปรุงโค้ดใน member-list.component.html เพื่อแสดงข้อมูล Users ที่ดึงได้จาก API

<div class="container">
  <div class="row">
    <div class="col-lg-2 col-md-3 col-sm-6">
      <p *ngFor="let user of users">{{user.knownAs}}</p>
    </div>
  </div>
</div>

4. สร้าง Member Card เพื่อแสดงในรายการ Members

เนื่องจากเราต้องมีสิ่งที่เกี่ยวกับ member หลาย ๆ อย่าง แต่ตอนนี้ components ที่สร้างไว้มี member-list component อยู่ เราจึงควรที่จะสร้างหมวดหมู่จัดเก็บสิ่งเดียวกันไว้ด้วยกัน ซึ่งในที่นี้เราจะสร้างโฟลเดอร์ไว้จัดเก็บ component ที่เกี่ยวกับ member ไว้ด้วยกัน โดยการสร้างโฟลเดอร์ชื่อ members และย้าย member-list เข้าไปไว้ในนั้นซะ

***เมื่อย้ายแล้วต้องเปลี่ยน path ที่ import ให้ถูกต้องทุกจุดด้วยนะ (แนะนำให้ vs-code refactoring ให้ทีเดียวเลย)

สร้าง component เพิ่มชื่อ member-card และจัดเก็บไว้ใน members โดยเมื่อสร้างแล้ว เราต้องทำการ import ใน app.module.ts เอง

member-card.component.ts

import { Component, OnInit, Input } from '@angular/core';
import { User } from 'src/app/_models/user';

@Component({
  selector: 'app-member-card',
  templateUrl: './member-card.component.html',
  styleUrls: ['./member-card.component.css']
})
export class MemberCardComponent implements OnInit {
  @Input() user: User;

  constructor() { }

  ngOnInit() {
  }

}

member-card.component.html

<div class="card mb-4">
  <div class="card-img-wrapper">
    <img src="{{user.photoUrl}}" alt="{{user.knownAs}}" class="card-img-top">
  </div>
  <div class="card-body p1">
    <h6 class="card-title text-center mb-1">
      <i class="fa fa-user"></i> {{user.knownAs}}, {{user.age}}
    </h6>
    <p class="card-text text-muted text-center">{{user.city}}</p>
  </div>
</div>

ปรับปรุงโค้ดใน member-list.component.html

<div class="container mt-5">
  <div class="row">
    <div *ngFor="let user of users" class="col-lg-2 col-md-3 col-sm-6">
      <app-member-card [user]="user"></app-member-card>
    </div>
  </div>
</div>

ทดสอบรัน และจะพบหน้าจอดังนี้

5. เพิ่ม Effect Animation ใน Member Card

เพิ่ม effect ให้ member card เราดูน่าใช้งานมากขึ้น โดยกำหนดใน member-card.component.css

.card:hover img {
    transform: scale(1.2, 1.2);
    transition-duration: 500ms;
    transition-timing-function: ease-out;
    opacity: 0.7;
}

.card img {
    transform: scale(1.0, 1.0);
    transition-duration: 500ms;
    transition-timing-function: ease-out;
}

.card-img-wrapper {
    overflow: hidden;
}

เพิ่มปุ่มดูรายละเอียด ปุ่มถูกใจ และปุ่มส่งข้อความส่วนตัว โดยเพิ่มเติมและปรับปรุงโค้ด ดังนี้

member-card.component.html

<div class="card mb-4">
  <div class="card-img-wrapper">
    <img src="{{user.photoUrl}}" alt="{{user.knownAs}}" class="card-img-top">
    <ul class="list-inline member-icons anumate text-center">
      <li class="list-inline-item"><button class="btn btn-primary btn-sm"><i class="fa fa-user"></i></button></li>
      <li class="list-inline-item"><button class="btn btn-primary btn-sm"><i class="fa fa-heart"></i></button></li>
      <li class="list-inline-item"><button class="btn btn-primary btn-sm"><i class="fa fa-envelope"></i></button></li>
    </ul>
  </div>
  <div class="card-body p1">
    <h6 class="card-title text-center mb-1">
      <i class="fa fa-user"></i> {{user.knownAs}}, {{user.age}}
    </h6>
    <p class="card-text text-muted text-center">{{user.city}}</p>
  </div>
</div>

member-card.component.css

.card:hover img {
    transform: scale(1.2, 1.2);
    transition-duration: 500ms;
    transition-timing-function: ease-out;
    opacity: 0.7;
}

.card img {
    transform: scale(1.0, 1.0);
    transition-duration: 500ms;
    transition-timing-function: ease-out;
}

.card-img-wrapper {
    overflow: hidden;
    position: relative;
}

.member-icons {
    position: absolute;
    bottom: -30%;
    left: 0;
    right: 0;
    margin-right: auto;
    margin-left: auto;
    opacity: 0;
}

.card-img-wrapper:hover .member-icons {
    bottom: 0;
    opacity: 1;
}

.animate {
    transition: all 0.3s ease-in-out;
}

ทดสอบรัน และดูผลที่แตกต่าง

6. ใช้ Auth0 JwtModule ในการส่ง Token เมื่อดึงข้อมูลโดยอัตโนมัติ

ในการเรียก API ตอนนี้เราทำการใส่ token เข้าไปใน header เองเมื่อทำการเรียก ในขั้นตอนนี้เราจะใช้ตัวช่วยในการใส่ token ให้ ซึ่งก็คือ auth0 นั่นเอง

import JwtModule จาก Auth0 ใน app.module.ts

import { JwtModule } from '@auth0/angular-jwt';

เพิ่มฟังก์ชัน tokenGetter ไว้ที่ส่วนบนของ app.module.ts

export function tokenGetter() {
   return localStorage.getItem('token');
}

ปรับปรุงการ import JwtModule เป็น

JwtModule.forRoot({
         config: {
            tokenGetter,
            whitelistedDomains: ['localhost:5000'],
            blacklistedRoutes: ['localhost:5000/api/auth']
         }
})

app.module.ts ที่ปรับปรุงแล้ว

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { RouterModule } from '@angular/router';
import { JwtModule } from '@auth0/angular-jwt';

import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { AuthService } from './_services/auth.service';
import { NavComponent } from './nav/nav.component';
import { HomeComponent } from './home/home.component';
import { RegisterComponent } from './register/register.component';
import { ErrorInterceptorProvider } from './_services/error.interceptor';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ListsComponent } from './lists/lists.component';
import { MessagesComponent } from './messages/messages.component';
import { appRoutes } from './routes';
import { MemberListComponent } from './members/member-list/member-list.component';
import { MemberCardComponent } from './members/member-card/member-card.component';

export function tokenGetter() {
   return localStorage.getItem('token');
}

@NgModule({
   declarations: [
      AppComponent,
      LoginComponent,
      NavComponent,
      HomeComponent,
      RegisterComponent,
      ListsComponent,
      MessagesComponent,
      MemberListComponent,
      MemberCardComponent
   ],
   imports: [
      BrowserModule,
      HttpClientModule,
      FormsModule,
      BsDropdownModule.forRoot(),
      BrowserAnimationsModule,
      RouterModule.forRoot(appRoutes),
      JwtModule.forRoot({
         config: {
            tokenGetter,
            whitelistedDomains: ['localhost:5000'],
            blacklistedRoutes: ['localhost:5000/api/auth']
         }
      })
   ],
   providers: [
      AuthService,
      ErrorInterceptorProvider
   ],
   bootstrap: [
      AppComponent
   ]
})
export class AppModule { }

ลบ httpOptions ใน user.service.ts ออก

import { Injectable } from '@angular/core';
import { environment } from 'src/environments/environment';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { User } from '../_models/user';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  baseUrl = environment.apiUrl;

  constructor(private http: HttpClient) { }

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>(this.baseUrl + 'users');
  }

  getUser(id): Observable<User> {
    return this.http.get<User>(this.baseUrl + 'users/' + id);
  }

}

7. สร้าง Detail View Component เพื่อแสดงรายละเอียดของ Members

สร้าง member-detail component ภายใต้ members และทำการ import MemberDetailComponent ใน app.module.ts

member-detail.component.ts

import { Component, OnInit } from '@angular/core';
import { User } from 'src/app/_models/user';
import { UserService } from 'src/app/_services/user.service';
import { AlertifyService } from 'src/app/_services/alertify.service';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-member-detail',
  templateUrl: './member-detail.component.html',
  styleUrls: ['./member-detail.component.css']
})
export class MemberDetailComponent implements OnInit {
  user: User;

  constructor(private userService: UserService,
              private alertify: AlertifyService,
              private route: ActivatedRoute) { }

  ngOnInit() {
    this.loadUser();
  }

  // members/{id}
  loadUser() {
    this.userService.getUser(+this.route.snapshot.params.id).subscribe((user: User) => {
      this.user = user;
    }, error => {
      this.alertify.error(error);
    });
  }

}

member-detail.component.html

<p>
  {{user.knownAs}}
</p>

เพิ่ม Route ใน routes.ts

{ path: 'members/:id', component: MemberDetailComponent },
import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { ListsComponent } from './lists/lists.component';
import { MessagesComponent } from './messages/messages.component';
import { MemberListComponent } from './members/member-list/member-list.component';
import { AuthGuard } from './_guards/auth.guard';
import { MemberDetailComponent } from './members/member-detail/member-detail.component';

export const appRoutes: Routes = [
    { path: 'home', component: HomeComponent },
    {
        path: '',
        runGuardsAndResolvers: 'always',
        canActivate: [AuthGuard],
        children: [
            { path: 'members', component: MemberListComponent },
            { path: 'members/:id', component: MemberDetailComponent },
            { path: 'messages', component: MessagesComponent },
            { path: 'lists', component: ListsComponent }
        ]
    },
    { path: '**', redirectTo: 'home', pathMatch: 'full' },
];

เพิ่ม routerLink ใน member-card

<div class="card mb-4">
  <div class="card-img-wrapper">
    <img src="{{user.photoUrl}}" alt="{{user.knownAs}}" class="card-img-top">
    <ul class="list-inline member-icons anumate text-center">
      <li class="list-inline-item">
        <button class="btn btn-primary btn-sm" [routerLink]="['/members/', user.id]">
          <i class="fa fa-user"></i>
        </button>
      </li>
      <li class="list-inline-item"><button class="btn btn-primary btn-sm"><i class="fa fa-heart"></i></button></li>
      <li class="list-inline-item"><button class="btn btn-primary btn-sm"><i class="fa fa-envelope"></i></button></li>
    </ul>
  </div>
  <div class="card-body p1">
    <h6 class="card-title text-center mb-1">
      <i class="fa fa-user"></i> {{user.knownAs}}, {{user.age}}
    </h6>
    <p class="card-text text-muted text-center">{{user.city}}</p>
  </div>
</div>

ทดสอบรัน และลองคลิกรายละเอียดดู จะพบว่าทำงานได้แล้ว แต่ที่ colsole มี error

ทำการปรับ member-detail.component.html ใส่ ? ที่ user เพื่อป้องกันกรณีที่ user ไม่มีข้อมูลมา จะพบว่า error หายไปแล้ว

<p>
  {{user?.knownAs}}
</p>

8. ออกแบบ Template เพื่อแสดงรายละเอียดของ Members

member-detail.component.html

<div class="container mt-4">
  <div class="row">
    <div class="col-sm-4">
      <div class="card">
        <img src="{{user?.photoUrl}}" alt="{{user?.knownAs}}" class="card-img-top img-thumbnail">
        <div class="card-body">
          <div>
            <strong>Location:</strong>
            <p>{{user?.city}}, {{user?.country}}</p>
          </div>
          <div>
            <strong>Age:</strong>
            <p>{{user?.age}}</p>
          </div>
          <div>
            <strong>Last Active:</strong>
            <p>{{user?.lastActive}}</p>
          </div>
          <div>
            <strong>Member since:</strong>
            <p>{{user?.created}}</p>
          </div>
        </div>
        <div class="card-footer">
          <div class="btn-group d-flex">
            <button class="btn btn-primary w-100">Like</button>
            <button class="btn btn-success w-100">Message</button>
          </div>
        </div>
      </div>
    </div>
    <div class="col-sm-8">

    </div>
  </div>
</div>

member-detail.component.css

.img-thumbnail {
    margin: 25px;
    width: 85%;
    height: 85%;
}

.card-body {
    padding: 0 25px;
}

.card-footer {
    padding: 10px 15px;
    background-color: #fff;
    border-top: none;
}

ลองรันและลองดูหน้ารายละเอียดของ member

9. ใช้งาน Tap ในการแสดงรายละเอียดของ Members

import TabsModule

TabsModule.forRoot(),
import { TabsModule } from 'ngx-bootstrap/tabs';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { TabsModule } from 'ngx-bootstrap/tabs';
import { RouterModule } from '@angular/router';
import { JwtModule } from '@auth0/angular-jwt';

import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { AuthService } from './_services/auth.service';
import { NavComponent } from './nav/nav.component';
import { HomeComponent } from './home/home.component';
import { RegisterComponent } from './register/register.component';
import { ErrorInterceptorProvider } from './_services/error.interceptor';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ListsComponent } from './lists/lists.component';
import { MessagesComponent } from './messages/messages.component';
import { appRoutes } from './routes';
import { MemberListComponent } from './members/member-list/member-list.component';
import { MemberCardComponent } from './members/member-card/member-card.component';
import { MemberDetailComponent } from './members/member-detail/member-detail.component';

export function tokenGetter() {
   return localStorage.getItem('token');
}

@NgModule({
   declarations: [
      AppComponent,
      LoginComponent,
      NavComponent,
      HomeComponent,
      RegisterComponent,
      ListsComponent,
      MessagesComponent,
      MemberListComponent,
      MemberCardComponent,
      MemberDetailComponent
   ],
   imports: [
      BrowserModule,
      HttpClientModule,
      FormsModule,
      BsDropdownModule.forRoot(),
      TabsModule.forRoot(),
      BrowserAnimationsModule,
      RouterModule.forRoot(appRoutes),
      JwtModule.forRoot({
         config: {
            tokenGetter,
            whitelistedDomains: ['localhost:5000'],
            blacklistedRoutes: ['localhost:5000/api/auth']
         }
      })
   ],
   exports: [
      TabsModule
   ],
   providers: [
      AuthService,
      ErrorInterceptorProvider
   ],
   bootstrap: [
      AppComponent
   ]
})
export class AppModule { }

เพิ่มโค้ด html tabs ใน member-detail.component.html

<div class="tab-panel">
  <tabset class="member-tabset">
    <tab heading="About {{user?.knownAs}}">
      <h4>Description</h4>
      <p>{{user?.introduction}}</p>
      <h4>Looking for</h4>
      <p>{{user?.lookingFor}}</p>
    </tab>
    <tab heading="Interests">
      <h4>Interests</h4>
      <p>{{user?.interests}}</p>
    </tab>
    <tab heading="Photos">
      <p>Photos will go here</p>
    </tab>
    <tab heading="Messages">
      <p>Messages will go here</p>
    </tab>
  </tabset>
</div>
<div class="container mt-4">
  <div class="row">
    <div class="col-sm-4">
      <div class="card">
        <img src="{{user?.photoUrl}}" alt="{{user?.knownAs}}" class="card-img-top img-thumbnail">
        <div class="card-body">
          <div>
            <strong>Location:</strong>
            <p>{{user?.city}}, {{user?.country}}</p>
          </div>
          <div>
            <strong>Age:</strong>
            <p>{{user?.age}}</p>
          </div>
          <div>
            <strong>Last Active:</strong>
            <p>{{user?.lastActive}}</p>
          </div>
          <div>
            <strong>Member since:</strong>
            <p>{{user?.created}}</p>
          </div>
        </div>
        <div class="card-footer">
          <div class="btn-group d-flex">
            <button class="btn btn-primary w-100">Like</button>
            <button class="btn btn-success w-100">Message</button>
          </div>
        </div>
      </div>
    </div>
    <div class="col-sm-8">
      <div class="tab-panel">
        <tabset class="member-tabset">
          <tab heading="About {{user?.knownAs}}">
            <h4>Description</h4>
            <p>{{user?.introduction}}</p>
            <h4>Looking for</h4>
            <p>{{user?.lookingFor}}</p>
          </tab>
          <tab heading="Interests">
            <h4>Interests</h4>
            <p>{{user?.interests}}</p>
          </tab>
          <tab heading="Photos">
            <p>Photos will go here</p>
          </tab>
          <tab heading="Messages">
            <p>Messages will go here</p>
          </tab>
        </tabset>
      </div>
    </div>
  </div>
</div>

เพิ่ม css ใน styles.css

.tab-panel {
  border: 1px solid #ddd;
  padding: 10px;
  border-radius: 4px;
}

.nav-tabs > li.open,
.member-tabset > .nav-tabs > li:hover {
  border-bottom: 4px solid #fbcdcf;
}

.member-tabset > .nav-tabs > li.open > a,
.member-tabset > .nav-tabs > li:hover > a {
  border: 0;
  background: none !important;
  color: #333333;
}

.member-tabset > .nav-tabs > li.open > a > i,
.member-tabset > .nav-tabs > li:hover > a > i {
  color: #a6a6a6;
}

.member-tabset > .nav-tabs > li.open .dropdown-menu,
.member-tabset > .nav-tabs > li:hover .dropdown-menu {
  margin-top: 0px;
}

.member-tabset > .nav-tabs > li.active {
  border-bottom: 4px solid #e95420;
  position: relative;
}

.member-tabset > .nav-tabs > li.active > a {
  border: 0 !important;
  color: #333333;
}

.member-tabset > .nav-tabs > li.active > a > i {
  color: #404040;
}

.member-tabset > .tab-content {
  margin-top: -3px;
  background-color: #fff;
  border: 0;
  border-top: 1px solid #eee;
  padding: 15px 0;
}

ลองรันดูอีกครั้ง

10. ใช้งาน Route Resolvers เพื่อรับส่งข้อมูล

สร้าง Resolver ที่ _resolvers/member-detail.resolver.ts

import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, Router } from '@angular/router';
import { User } from '../_models/user';
import { UserService } from '../_services/user.service';
import { AlertifyService } from '../_services/alertify.service';
import { Observable, of } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable()
export class MemberDetailResolver implements Resolve<User> {
    constructor(private userService: UserService,private router: Router, private alertify: AlertifyService) {}

    resolve(route: ActivatedRouteSnapshot): Observable<User> {
        return this.userService.getUser(route.params.id).pipe(
            catchError(error => {
                this.alertify.error('Problem retrieving data');
                this.router.navigate(['/members']);
                return of(null);
            })
        );
    }
}

ทำการเพิ่ม providers ใน app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { TabsModule } from 'ngx-bootstrap/tabs';
import { RouterModule } from '@angular/router';
import { JwtModule } from '@auth0/angular-jwt';

import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { AuthService } from './_services/auth.service';
import { NavComponent } from './nav/nav.component';
import { HomeComponent } from './home/home.component';
import { RegisterComponent } from './register/register.component';
import { ErrorInterceptorProvider } from './_services/error.interceptor';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ListsComponent } from './lists/lists.component';
import { MessagesComponent } from './messages/messages.component';
import { appRoutes } from './routes';
import { MemberListComponent } from './members/member-list/member-list.component';
import { MemberCardComponent } from './members/member-card/member-card.component';
import { MemberDetailComponent } from './members/member-detail/member-detail.component';
import { MemberDetailResolver } from './_resolvers/member-detail.resolver';

export function tokenGetter() {
   return localStorage.getItem('token');
}

@NgModule({
   declarations: [
      AppComponent,
      LoginComponent,
      NavComponent,
      HomeComponent,
      RegisterComponent,
      ListsComponent,
      MessagesComponent,
      MemberListComponent,
      MemberCardComponent,
      MemberDetailComponent
   ],
   imports: [
      BrowserModule,
      HttpClientModule,
      FormsModule,
      BsDropdownModule.forRoot(),
      TabsModule.forRoot(),
      BrowserAnimationsModule,
      RouterModule.forRoot(appRoutes),
      JwtModule.forRoot({
         config: {
            tokenGetter,
            whitelistedDomains: ['localhost:5000'],
            blacklistedRoutes: ['localhost:5000/api/auth']
         }
      })
   ],
   exports: [
      TabsModule
   ],
   providers: [
      AuthService,
      ErrorInterceptorProvider,
      MemberDetailResolver
   ],
   bootstrap: [
      AppComponent
   ]
})
export class AppModule { }

กำหนดให้ Route ใช้งาน Resolver ที่สร้างขึ้นใน routes.ts

import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { ListsComponent } from './lists/lists.component';
import { MessagesComponent } from './messages/messages.component';
import { MemberListComponent } from './members/member-list/member-list.component';
import { AuthGuard } from './_guards/auth.guard';
import { MemberDetailComponent } from './members/member-detail/member-detail.component';
import { MemberDetailResolver } from './_resolvers/member-detail.resolver';

export const appRoutes: Routes = [
    { path: 'home', component: HomeComponent },
    {
        path: '',
        runGuardsAndResolvers: 'always',
        canActivate: [AuthGuard],
        children: [
            { path: 'members', component: MemberListComponent },
            { path: 'members/:id', component: MemberDetailComponent,
                resolve: {user: MemberDetailResolver}},
            { path: 'messages', component: MessagesComponent },
            { path: 'lists', component: ListsComponent }
        ]
    },
    { path: '**', redirectTo: 'home', pathMatch: 'full' },
];

เรียกใช Resolver ใน onInit ของ member-detail.component.ts

this.route.data.subscribe(data => {
      this.user = data.user;
});
import { Component, OnInit } from '@angular/core';
import { User } from 'src/app/_models/user';
import { UserService } from 'src/app/_services/user.service';
import { AlertifyService } from 'src/app/_services/alertify.service';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-member-detail',
  templateUrl: './member-detail.component.html',
  styleUrls: ['./member-detail.component.css']
})
export class MemberDetailComponent implements OnInit {
  user: User;

  constructor(private userService: UserService,
              private alertify: AlertifyService,
              private route: ActivatedRoute) { }

  ngOnInit() {
    this.route.data.subscribe(data => {
      this.user = data.user;
    });
  }

  // members/{id}
  loadUser() {
    this.userService.getUser(+this.route.snapshot.params.id).subscribe((user: User) => {
      this.user = user;
    }, error => {
      this.alertify.error(error);
    });
  }

}

ทีนี้ก็สามารถลบ ? ใน member-detail.component.html ได้แล้ว เพราะข้อมูลจะมาแน่ ๆ แล้ว

<div class="container mt-4">
  <div class="row">
    <div class="col-sm-4">
      <div class="card">
        <img src="{{user.photoUrl}}" alt="{{user.knownAs}}" class="card-img-top img-thumbnail">
        <div class="card-body">
          <div>
            <strong>Location:</strong>
            <p>{{user.city}}, {{user?.country}}</p>
          </div>
          <div>
            <strong>Age:</strong>
            <p>{{user.age}}</p>
          </div>
          <div>
            <strong>Last Active:</strong>
            <p>{{user.lastActive}}</p>
          </div>
          <div>
            <strong>Member since:</strong>
            <p>{{user.created}}</p>
          </div>
        </div>
        <div class="card-footer">
          <div class="btn-group d-flex">
            <button class="btn btn-primary w-100">Like</button>
            <button class="btn btn-success w-100">Message</button>
          </div>
        </div>
      </div>
    </div>
    <div class="col-sm-8">
      <div class="tab-panel">
        <tabset class="member-tabset">
          <tab heading="About {{user.knownAs}}">
            <h4>Description</h4>
            <p>{{user.introduction}}</p>
            <h4>Looking for</h4>
            <p>{{user.lookingFor}}</p>
          </tab>
          <tab heading="Interests">
            <h4>Interests</h4>
            <p>{{user.interests}}</p>
          </tab>
          <tab heading="Photos">
            <p>Photos will go here</p>
          </tab>
          <tab heading="Messages">
            <p>Messages will go here</p>
          </tab>
        </tabset>
      </div>
    </div>
  </div>
</div>

ทดสอบรัน และยังคงสามารถทำงานได้เช่นเดิม

สร้าง Resolver อีกตัวหนึ่ง เพื่อใช้งานกับ member-list.component ชื่อ member-list.resolver.ts โดยคัดลอกจากไฟล์เดิมได้เลย

member-list.resolver.ts

import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, Router } from '@angular/router';
import { User } from '../_models/user';
import { UserService } from '../_services/user.service';
import { AlertifyService } from '../_services/alertify.service';
import { Observable, of } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable()
export class MemberListResolver implements Resolve<User[]> {
    constructor(private userService: UserService,private router: Router, private alertify: AlertifyService) {}

    resolve(route: ActivatedRouteSnapshot): Observable<User[]> {
        return this.userService.getUsers().pipe(
            catchError(error => {
                this.alertify.error('Problem retrieving data');
                this.router.navigate(['/home']);
                return of(null);
            })
        );
    }
}

routes.ts

import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { ListsComponent } from './lists/lists.component';
import { MessagesComponent } from './messages/messages.component';
import { MemberListComponent } from './members/member-list/member-list.component';
import { AuthGuard } from './_guards/auth.guard';
import { MemberDetailComponent } from './members/member-detail/member-detail.component';
import { MemberDetailResolver } from './_resolvers/member-detail.resolver';
import { MemberListResolver } from './_resolvers/member-list.resolver';

export const appRoutes: Routes = [
    { path: 'home', component: HomeComponent },
    {
        path: '',
        runGuardsAndResolvers: 'always',
        canActivate: [AuthGuard],
        children: [
            { path: 'members', component: MemberListComponent,
                resolve: {users: MemberListResolver}},
            { path: 'members/:id', component: MemberDetailComponent,
                resolve: {user: MemberDetailResolver}},
            { path: 'messages', component: MessagesComponent },
            { path: 'lists', component: ListsComponent }
        ]
    },
    { path: '**', redirectTo: 'home', pathMatch: 'full' },
];

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { TabsModule } from 'ngx-bootstrap/tabs';
import { RouterModule } from '@angular/router';
import { JwtModule } from '@auth0/angular-jwt';

import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { AuthService } from './_services/auth.service';
import { NavComponent } from './nav/nav.component';
import { HomeComponent } from './home/home.component';
import { RegisterComponent } from './register/register.component';
import { ErrorInterceptorProvider } from './_services/error.interceptor';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ListsComponent } from './lists/lists.component';
import { MessagesComponent } from './messages/messages.component';
import { appRoutes } from './routes';
import { MemberListComponent } from './members/member-list/member-list.component';
import { MemberCardComponent } from './members/member-card/member-card.component';
import { MemberDetailComponent } from './members/member-detail/member-detail.component';
import { MemberDetailResolver } from './_resolvers/member-detail.resolver';
import { MemberListResolver } from './_resolvers/member-list.resolver';

export function tokenGetter() {
   return localStorage.getItem('token');
}

@NgModule({
   declarations: [
      AppComponent,
      LoginComponent,
      NavComponent,
      HomeComponent,
      RegisterComponent,
      ListsComponent,
      MessagesComponent,
      MemberListComponent,
      MemberCardComponent,
      MemberDetailComponent
   ],
   imports: [
      BrowserModule,
      HttpClientModule,
      FormsModule,
      BsDropdownModule.forRoot(),
      TabsModule.forRoot(),
      BrowserAnimationsModule,
      RouterModule.forRoot(appRoutes),
      JwtModule.forRoot({
         config: {
            tokenGetter,
            whitelistedDomains: ['localhost:5000'],
            blacklistedRoutes: ['localhost:5000/api/auth']
         }
      })
   ],
   exports: [
      TabsModule
   ],
   providers: [
      AuthService,
      ErrorInterceptorProvider,
      MemberDetailResolver,
      MemberListResolver
   ],
   bootstrap: [
      AppComponent
   ]
})
export class AppModule { }

member-list.component.ts

import { Component, OnInit } from '@angular/core';
import { User } from '../../_models/user';
import { UserService } from '../../_services/user.service';
import { AlertifyService } from '../../_services/alertify.service';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-member-list',
  templateUrl: './member-list.component.html',
  styleUrls: ['./member-list.component.css']
})
export class MemberListComponent implements OnInit {
  users: User[];

  constructor(private userService: UserService, private alertify: AlertifyService, private route: ActivatedRoute) { }

  ngOnInit() {
    this.route.data.subscribe(data => {
      this.users = data.users;
    });
  }

  loadUsers() {
    this.userService.getUsers().subscribe((users: User[]) => {
      this.users = users;
    }, error => {
      this.alertify.error(error);
    });
  }

}

ทดสอบรัน และต้องสามารถทำงานได้เหมือนเดิม แต่ข้อมูลเราจะต้องมาเสมอทุกจังหวะแล้ว

11. เพิ่ม Photo Gallery

ติดตั้ง ngx-gallery โดยใช้คำสั่ง

npm install @kolkov/ngx-gallery

import NgxGalleryModule ใน app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { TabsModule } from 'ngx-bootstrap/tabs';
import { RouterModule } from '@angular/router';
import { JwtModule } from '@auth0/angular-jwt';
import { NgxGalleryModule } from '@kolkov/ngx-gallery';

import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { AuthService } from './_services/auth.service';
import { NavComponent } from './nav/nav.component';
import { HomeComponent } from './home/home.component';
import { RegisterComponent } from './register/register.component';
import { ErrorInterceptorProvider } from './_services/error.interceptor';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ListsComponent } from './lists/lists.component';
import { MessagesComponent } from './messages/messages.component';
import { appRoutes } from './routes';
import { MemberListComponent } from './members/member-list/member-list.component';
import { MemberCardComponent } from './members/member-card/member-card.component';
import { MemberDetailComponent } from './members/member-detail/member-detail.component';
import { MemberDetailResolver } from './_resolvers/member-detail.resolver';
import { MemberListResolver } from './_resolvers/member-list.resolver';

export function tokenGetter() {
   return localStorage.getItem('token');
}

@NgModule({
   declarations: [
      AppComponent,
      LoginComponent,
      NavComponent,
      HomeComponent,
      RegisterComponent,
      ListsComponent,
      MessagesComponent,
      MemberListComponent,
      MemberCardComponent,
      MemberDetailComponent
   ],
   imports: [
      BrowserModule,
      HttpClientModule,
      FormsModule,
      BsDropdownModule.forRoot(),
      TabsModule.forRoot(),
      BrowserAnimationsModule,
      RouterModule.forRoot(appRoutes),
      NgxGalleryModule,
      JwtModule.forRoot({
         config: {
            tokenGetter,
            whitelistedDomains: ['localhost:5000'],
            blacklistedRoutes: ['localhost:5000/api/auth']
         }
      })
   ],
   exports: [
      TabsModule
   ],
   providers: [
      AuthService,
      ErrorInterceptorProvider,
      MemberDetailResolver,
      MemberListResolver
   ],
   bootstrap: [
      AppComponent
   ]
})
export class AppModule { }

กำหนดการแสดงผล และรูปภาพใน member-detail.component.ts

import { Component, OnInit } from '@angular/core';
import { User } from 'src/app/_models/user';
import { UserService } from 'src/app/_services/user.service';
import { AlertifyService } from 'src/app/_services/alertify.service';
import { ActivatedRoute } from '@angular/router';
import { NgxGalleryOptions, NgxGalleryImage, NgxGalleryAnimation } from '@kolkov/ngx-gallery';

@Component({
  selector: 'app-member-detail',
  templateUrl: './member-detail.component.html',
  styleUrls: ['./member-detail.component.css']
})
export class MemberDetailComponent implements OnInit {
  user: User;
  galleryOptions: NgxGalleryOptions[];
  galleryImages: NgxGalleryImage[];

  constructor(private userService: UserService,
              private alertify: AlertifyService,
              private route: ActivatedRoute) { }

  ngOnInit() {
    this.route.data.subscribe(data => {
      this.user = data.user;
    });

    this.galleryOptions = [
      {
        width: '500px',
        height: '500px',
        imagePercent: 100,
        thumbnailsColumns: 4,
        imageAnimation: NgxGalleryAnimation.Slide,
        preview: false
      }
    ];

    this.galleryImages = this.getImages();
  }

  getImages() {
    const imageUrls = [];
    for (const photo of this.user.photos) {
      imageUrls.push({
        small: photo.url,
        medium: photo.url,
        large: photo.url,
        description: photo.description
      });
    }
    return imageUrls;
  }

}

นำ ngx-gallery ไปแสดงรูปภาพใน member-detail.component.html

<ngx-gallery [options]="galleryOptions" [images]="galleryImages" style="display: inline-block; margin-bottom: 20px;"></ngx-gallery>
<div class="container mt-4">
  <div class="row">
    <div class="col-sm-4">
      <div class="card">
        <img src="{{user.photoUrl}}" alt="{{user.knownAs}}" class="card-img-top img-thumbnail">
        <div class="card-body">
          <div>
            <strong>Location:</strong>
            <p>{{user.city}}, {{user?.country}}</p>
          </div>
          <div>
            <strong>Age:</strong>
            <p>{{user.age}}</p>
          </div>
          <div>
            <strong>Last Active:</strong>
            <p>{{user.lastActive}}</p>
          </div>
          <div>
            <strong>Member since:</strong>
            <p>{{user.created}}</p>
          </div>
        </div>
        <div class="card-footer">
          <div class="btn-group d-flex">
            <button class="btn btn-primary w-100">Like</button>
            <button class="btn btn-success w-100">Message</button>
          </div>
        </div>
      </div>
    </div>
    <div class="col-sm-8">
      <div class="tab-panel">
        <tabset class="member-tabset">
          <tab heading="About {{user.knownAs}}">
            <h4>Description</h4>
            <p>{{user.introduction}}</p>
            <h4>Looking for</h4>
            <p>{{user.lookingFor}}</p>
          </tab>
          <tab heading="Interests">
            <h4>Interests</h4>
            <p>{{user.interests}}</p>
          </tab>
          <tab heading="Photos">
            <ngx-gallery [options]="galleryOptions" [images]="galleryImages" style="display: inline-block; margin-bottom: 20px;"></ngx-gallery>
          </tab>
          <tab heading="Messages">
            <p>Messages will go here</p>
          </tab>
        </tabset>
      </div>
    </div>
  </div>
</div>

โปรดติดตามตอนต่อไป…

Line
Facebook
Twitter
Google
[ตอนที่ 16] Social App Workshop ด้วย ASP.NET Core 3 กับ Angular 9 การทำ Sorting
[ตอนที่ 15] Social App Workshop ด้วย ASP.NET Core 3 กับ Angular 9 การทำ Filtering
[ตอนที่ 14] Social App Workshop ด้วย ASP.NET Core 3 กับ Angular 9 การใช้งาน Paging
No Preview
[ตอนที่ 13] Social App Workshop ด้วย ASP.NET Core 3 กับ Angular 9 การกำหนดรูปแบบการแสดงผลวัน-เวลา
เตรียม Atom สำหรับ React Native #3
เตรียม Visual Studio Code สำหรับ React Native #2
การติดตั้ง React Native บน macOs #1
การกำหนดค่า TF_MIN_GPU_MULTIPROCESSOR_COUNT เพื่อให้ TensorFlow ใช้งาน GPU ทุกตัว
ติดตั้ง Ubuntu 17.04 ใช้งานร่วมกับ Windows 10
การติดตั้ง TensorFlow & Caffe บน Ubuntu 16.04