[ng2 - practice] - Github search profile (P1)

Mình cũng đã giới thiệu về ng2 qua 2 bài ng-formng-cli. Nhưng học phải đi đôi với hành phải không nào? Hôm nay mình sẽ cùng các bạn build 1 app nhỏ để áp dụng những cái mình học được nhé.

Mình chỉ tự tìm tòi & đọc doc trên trang chủ, chưa có nhiều kinh nghiệm thực tế. Nên chắc chắn sẽ có những thiếu sót, mình rất hoan nghênh/mong muốn nhận được những góp ý của các bạn

1. Init project

Tất nhiên rồi, đầu tiên phải tạo project chứ nhỉ. Chạy ng new github-search nhé

CSS Preprocessor mặc định sẽ là css. Nếu bạn thích xài scss/sass thì có thể set lại bằng command : ng set defaults.styleExt scss

Ng2 được build dựa trên các component nên ta cũng phải tạo ra các component trong pj của mình thôi App của mình là search profile trên github nên ta sẽ phải tạo ra 1 form để search ng g component form-seach sẽ giúp mình tạo ra 1 component thật đơn giản

Lưu ý, phần html/css mình sẽ chỉ post những đoạn code chính, bạn có thể vào link github của mình ở dưới bài viết để xem bản full không che nhé

Tạo giao diện cái nhỉ, vào file app/form-search/form-search.component.html vừa được tạo nhé

<input class="form-control" name="username" placeholder="Enter A Github Username..." autocomplete="off" type="text">

Tạo model để chứa thông tin user chứ nhỉ

// file app/mode/github-user.ts
export class gitHubUser {
    constructor (
        public user: any, public repos: any,
        public username: any, public items: any,
    ) {    }
}

Tạo service để search chứ nhỉ. Mình chỉ post code để đó & không nói gì nhé (😃) lát sẽ xài)

// file app/service/user-search.service.ts
@Injectable()
export class UserSearchService {}

Các bạn nhớ import service này vào app.module.ts nhé

2. Search user

1. Xây dựng service

Bắt tay vào công việc chính nào

// file app/form-search/form-search.component.html
<input [(ngModel)]="keySearch" id="search-box" (keyup)="search(keySearch)"
                            class="form-control" name="username" placeholder="Enter A Github Username..."
                            autocomplete="off" type="text">

Ta add thêm sự kiện (keyup) cho input

// file app/form-search/form-search.component.ts
export class FormSearchComponent implements OnInit {
    protected txtSearch = new Subject<string>();
    listUser$: Observable<gitHubUser[]>;
    keySearch = '';
    
    search(keySearch: string): void {
        this.txtSearch.next(keySearch);
    }
    
    ngOnInit() {
        this.listUser$ = this.txtSearch
            .debounceTime(300)        // wait for 300ms pause in events
            .distinctUntilChanged()   // ignore if next search term is same as previous
            .switchMap(term => term   // switch to new observable each time
                // return the http search observable
                ? this.userSearchService.searchUser(term)
                // or the observable of empty heroes if no search term
                : Observable.of<gitHubUser[]>([])
            )
            .catch(error => {
                // TODO: real error handling
                console.log(error);
                return Observable.of<gitHubUser[]>([]);
            });
    }

Ta khai báo 1 Subject với kiểu string, cái này sẽ sinh ra một observable event stream và gán cho biến txtSearch, biên này sẽ sinh ra một Observable of strings Mỗi lần gọi hàm search sẽ đẩy một string mới vào stream này qua next() listUser$ sẽ chứa những user từ api trả về debounceTime(300) sẽ delay khoảng 300ms để tránh gọi api quá nhiều lần sẽ bị tốn tài nguyên distinctUntilChanged() sẽ chỉ gọi api nếu giá trị được thay đổi switchMap chuyển đổi từ sang kiểu observable trước khi gọi đến api

Giờ ta sẽ đụng đến service nhé

private urlSearch = 'https://api.github.com/search/users?q=';
    constructor(private http: Http) {    }

    searchUser(keySearch: string): Promise<gitHubUser[]> {
        return this.http
            .get(this.urlSearch + keySearch)
            .toPromise()
            .then(response => response.json().items)
            .catch(this.handleError);
    }

    private handleError(error: any): Promise<any> {
        console.log('An error occurred', error);
        return Promise.reject(error.message || error);
    }

http.get sẽ trả về một RxJS Observable, nên ta phải dùng toPromise() để chuyển từ Observable về Promise

2. Hiển thị user

Sau 1 hồi quần quật với cái service thì đã đến lúc ta phải show kết quả mà api trả về chứ nhỉ

// file app/form-search/form-search.component.html
<div class="suggestion-container">
    <div *ngFor="let user of listUser$ | async" class="suggestion" (click)="findUser(user.login)">
        <span>{{user.login}}</span>
    </div>
</div>

Các bạn lưu ý là phải chạy qua async nhé. PipeAsync sẽ đăng kí vào Observable và chuyển đổi sang array để cho ngFor có thể dùng. Mình bị lack chỗ này nên mất cả buổi sáng mới fix được 😦(

Các bạn để í là minh có bind event click vào mỗi div của kết quả trả về nhé. Nên là trong file component mình cũng phải khai báo hàm đó thôi

// file app/form-search/form-search.component.ts
protected userGit: gitHubUser;
findUser(userName) {
        this.keySearch = userName;
        this.userGit = new gitHubUser('', '', userName, '');
    }

Ở trên là mình search LIKE nên sẽ trả về nhiều user. Mục đích của mình là search cụ thể 1 user mà, nên mình sẽ có 1 biến để lưu user mà client đã chọn

3. Hide result search

Bạn để í là khi mình click chọn 1 user rồi, nhưng list user từ api trả về không bị ẩn (trông xấu mù). Xử nó thôi Mình sẽ có 1 cờ để show/hide bọn đấy

// file app/form-search/form-search.component.ts
hideResultSearch = true;
constructor(private userSearchService: UserSearchService) {
        document.addEventListener('click', this.offClickHandler.bind(this));
    }

offClickHandler() {
    this.hideResultSearch = true;
}

search(keySearch: string): void {
    this.hideResultSearch = false;
    this.txtSearch.next(keySearch);
}

// file app/form-search/form-search.component.html
<div class="suggestion-container" [hidden]="(listUser$ | async)?.length <= 0 || hideResultSearch">

Khi bạn click bất kì chỗ nào trên màn hình thì biến hideResultSearch được gán = true => sẽ ẩn list user kia đi

Hết phần 1. Phần sau mình sẽ hướng dẫn tìm/hiên thị thông tin của 1 user mà bạn click link full không che - github

All Rights Reserved