iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 29
0
Software Development

Emacs 來寫程式系列 第 29

[Emacs-29] 用 Emacs 來寫 Angular

Emacs 對於 typescript 的設定

其實在前面寫 javascript 設定時,已經將後端 tsserver 設定完畢了,這裡只要加入

(add-hook 'typescript-mode-hook #'setup-tide-mode)

讓預設的 typescript-mode 加入 tide-mode 次模式的支援,就可以跟 javascript 一樣使用相關的功能,諸如

開始一個專案

我們直接用一個例子來看 Emacs 對於 Angular 專案相關的功能,先來使用

$ng new hello

開啟一個新的專案,這個簡單的專案不需要 Routing, 只需要 css,因為在 vagrant client 下執行,啟動時加入 $ng serve --host 0.0.0.0 Imgur

專案簡單起見使用 bootstrap (純 CSS 部分),$npm install bootstrap -S,在 styles.css 加入

@import "~bootstrap/dist/css/bootstrap.css"

加入後,原先的網頁字體也跟著改變了 Imgur

建立一個元件

使用 $ng generate component posts 來建立一個元件,修改 app.component.html為

<div>
    <app-posts></app-posts>
</div>

Imgur

建立基本 html layout

<div class="container">
  <h2 class="my-3 text-center">List of all Posts</h2>
  <div class="row">
    <div class="col-md-6">
      <div class="card mb-4 shadow-sm">
        <div class="card-body">
          <div class="card-title">title</div>
          <div class="card-text mb-4">body</div>
          <a href="#" class="btn btn-outline-primary">More...</a>
        </div>
      </div>
    </div>
  </div>
</div>

完成後的樣子 Imgur

建立 模擬資料 (mock-data) 來測試

Angular 建議資料的來源為 Service,將資料與元件(表現資料) 分離,大部分資料來自於 RESTful API 後端,我們可以先用模擬資料先將元件表現的部分確定,以免由於後端的連結問題來糾纏

  • 加入 Post Model
    models/post.ts
export class Post {
    id:number;
    title: string;
    body: string
}
  • 加入 services/mock-posts.ts
import {Post} from "../models/post";

export const POSTS: Post[] = [
  {id: 1, title: "first post", body: "first post content"},
  {id: 2, title: "secon post", body: "second post content"},
  {id: 3, title: "third post", body: "third post content"},
  {id: 4, title: "forth post", body: "forth post content"},
];
  • 修改 posts.component.ts 匯入 mock-posts,匯入時,自動補全會提示可以 import 的部分 Imgur
import { Component, OnInit } from '@angular/core';
import { POSTS } from '../services/mock-posts';

@Component({
  selector: 'app-posts',
  templateUrl: './posts.component.html',
  styleUrls: ['./posts.component.css']
})
export class PostsComponent implements OnInit {
    posts = POSTS;
  constructor() { }

    ngOnInit() {
        console.log(this.posts);
  }
}
  • 修改 posts.components.html
<div class="container">
  <h2 class="my-3 text-center">List of all Posts</h2>
  <div class="row">
    <div class="col-md-6" *ngFor="let post of posts">
      <div class="card mb-4 shadow-sm">
        <div class="card-body">
          <div class="card-title">{{post.title}}</div>
          <div class="card-text mb-4">{{post.body}}</div>
          <a href="#" class="btn btn-outline-primary">More...</a>
        </div>
      </div>
    </div>
  </div>
</div>

使用者介面的部分大致完成 Imgur

建立一個服務

真正的 Angular 服務應該是從後端連結資料,非同步來提供資料以供元件表現,我們來建立一個服務

$ng generate service posts

先利用先前的 mock-posts 建立一個 Observable

import {Injectable} from "@angular/core";
import {Post} from "../models/post";
import {POSTS} from "./mock-posts";

import {Observable, of} from "rxjs";

@Injectable({
  providedIn: "root",
})
export class PostsService {
  constructor() {}
  getPost(): Observable<Post[]> {
    return of(POSTS);
  }
}
  • 當然 posts.component.ts 也需要修改使用 .subscribe 來接這個 observable
import {Component, OnInit} from "@angular/core";
import {PostsService} from "../services/posts.service";

@Component({
  selector: "app-posts",
  templateUrl: "./posts.component.html",
  styleUrls: ["./posts.component.css"],
})
export class PostsComponent implements OnInit {
  posts = [];
  constructor(private postService: PostsService) {}

  ngOnInit() {
    this.getPosts();
  }
  getPosts() {
    this.postService.getPost().subscribe(posts => (this.posts = posts));
  }
}

需要注入(inject) PostService,然後 subscribe 服務,修改完,網頁不變,表示服務已經成功 Imgur

使用 HttpClient 來連結 JSONPlaceholder

  • 加入 HttpClientModule
    如果忘了 import 直接加入, flycheck 會顯示錯誤顏色,按 Ctrl-c ! l 顯示更多訊息 Imgur
    Ctrl-c ! v 可以看在 typescript-mode 下,flycheck 可以使用 tide 或者 tslint,如果要使用 tslint,需要做設定 Imgur
    app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';

import { AppComponent } from './app.component';
import { PostsComponent } from './posts/posts.component';
import { HttpClient } from 'selenium-webdriver/http';

@NgModule({
  declarations: [
    AppComponent,
    PostsComponent
  ],
  imports: [
      BrowserModule,
      HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
  • 修改 posts.service.ts
import {Injectable} from "@angular/core";
import {HttpClient} from "@angular/common/http";

import {Post} from "../models/post";
//import {POSTS} from "./mock-posts";

import {Observable, of} from "rxjs";

@Injectable({
  providedIn: "root",
})
export class PostsService {
  constructor(private http: HttpClient) {}
  getPost(): Observable<Post[]> {
    //return of(POSTS);
    return this.http.get<Post[]>(
      "https://jsonplaceholder.typicode.com/posts?userId=1",
    );
  }
}

注入 HttpClietn 服務,這個也是一個 Observable,完成後 Imgur

修改一下 posts.component.html

加入一個管線 (pipe),讓 body 只顯示最多 50 個字

<div class="card-text mb-3">{{post.body | slice:0:50 }}...</div>

最後的完成畫面 Imgur

相關影片: Yes
相關簡報: 簡報
相關程式: Github
Emacs 設定檔: Github 請下載到 ~/.emacs.d 啟動 Emacs 即可自動安裝相關套件
相關資訊: 我的部落格


上一篇
[Emacs-28] 用 Emacs 來寫 Vue.js 實戰篇
下一篇
[Emacs-30] 用 Emacs 寫 React
系列文
Emacs 來寫程式30

尚未有邦友留言

立即登入留言