昨天花了1天的時間把本身的博客從之前的Express換成了Angular2+Express,遂記錄於此。博客Demo在這裏,你也能夠點擊這裏查看完整代碼。css
第一次使用Angular2,仍是遇到了很多問題,好比html
若是你也遇到了這些問題,或者你想了解一下Angular2開發的大致流程,能夠接着往下看。前端
先來談談傳統的Web開發流程。在傳統開發裏,前端的工做多是用HTML、CSS將頁面「繪製」出來,而後用JS去處理頁面裏的邏輯。但因爲頁面中須要展現一些動態的來自數據庫中的數據,因此「繪製」的內容不能在當時確實下來,因而用一些「變量」填充在HTML裏,等有數據時,才用數據去替換對應的變量,獲得最終的完整的頁面。以上用「變量」填充HTML的過程,有可能也是由前端完成,但更多的時候實際上是後端來完成的;用數據去替換變量的過程,即所謂的頁面渲染通常也是在後端完成的,即所謂的後端渲染。還忘了說的一點是路由。傳統意義下,頁面的路由是由後端控制的,即咱們每點擊一個連接,跳轉到哪一個頁面或者說接收到什麼頁面徹底是由後端控制的。webpack
以上是傳統Web先後端搭配幹活的方式,存在着一些問題。好比上面所說的用變量填充HTML的操做若交給後端去作,那麼他必須先讀懂前端的HTML邏輯,而後才能下手;就算把填充變量的活交給前端去作,但因爲這些變量都來自後端,前端測試起來將很是困難;好比,因爲填充HTML的操做是交給後端去作的,那麼前端在作頁面時多是用一些寫死的數據作的測試,最終將真實數據套用過來時,頁面顯示可能會有出入;再好比若是前端已經將頁面交給後端去添加變量,若他再修改了頁面,他必須告訴後端哪裏作了修改,不然後端須要在修改後的頁面裏從新再添加一遍變量,這樣以前的工做都白費了。git
因而,有人提出增大前端的職責範圍,把頁面渲染交給前端去作,但仍是在服務端完成,後端只負責提供數據API接口,徹底無論頁面的渲染,包括路由。而此意義下的前端,即須要編寫頁面的結構樣式,還須要負責將數據套在裏面渲染出最終的頁面,須要數據時,經過HTTP或者其餘手段調用後端提供的接口便可。這樣分工下來,先後端的工做幾乎沒有重疊之處,他們惟一的交接點在於提供數據的API接口,而這個API接口能夠保證是穩定的。這確實可以解決以前的開發效率問題,但增長了一層接口的調用,並對前端的要求會更高。而對前端人員而言,最熟悉的編程語言莫過於JS,因而多出的,調用後端接口,渲染頁面的這一層很天然的就會採用Node.js來作。因而有了下面這圖(盜用自淘寶UED博客,如今好像搜索不到了:-?):github
再說說Angular的工做模式。Angular跟上圖的工做方式很像,但只是說在先後端分工上是類似的。Angular把頁面渲染的工做放在了瀏覽器端,(固然Angular也支持後端渲染,參見Angular Universal),所以沒有Node這一層,以下圖:web
這種方式其實更像是C/S架構的軟件:除了數據須要向後臺獲取,其他的工做,像是頁面路由,頁面渲染等,都是在」客戶端「完成的,只不過這裏的」客戶端「運行在瀏覽器裏。這便是所謂的SPA(單頁面應用)。數據庫
前面說了一些題外話,下面正式介紹用Angular開發咱們的博客前端,須要把Node.js和npm安裝好,npm倉庫最好使用國內的鏡像。能夠安裝一個叫作nrm的庫來很是方便的更改咱們的npm源。express
首先是工程骨架的搭建,直接採用Angular的構建工具@angular/cli
,先安裝:npm
npm install -g @angular/cli
安裝完成後就可使用ng命令去生成咱們的項目了:
ng new NiceBlog
生成的同時它會自動安裝依賴包,完畢後,咱們就能夠進入NiceBlog目錄,運行初始構建的項目了:
cd NiceBlog npm start
注意:這裏有坑!若是你使用的angular-cli版本是1.0.0-rc.1,生成的項目極可能跑不起來,至少我這裏是這樣。你須要將Angular的版本化由2.4.0換成2.4.9,而後從新安裝依賴。
以後你即可以開發了。開發時,只要你修改代碼,瀏覽器會自動刷新。
博客打算作成這個樣子:
業務邏輯很是簡單,就再也不作解釋了。按照Angular的開發思想,咱們須要將一個應用切分爲多(一)個模塊,每一個模塊切分爲多(一)個組件,組件依賴於服務,管道等。簡單解釋一下這些概念,模塊是一系列組件,服務,管道等元素的集合,它一般按照業務功能進行劃分;組件能夠當作是一個頁面裏的小部件,好比一個導航條,一個菜單欄,一個Top10列表等;服務和後端開發裏面的Service層類似,它爲組件提供服務,好比一個ArticleService暴露出getArticles方法,爲組件提供獲取文章的服務,這樣組件在須要文章數據時,依賴該服務便可,而沒必要考慮如何獲得的這些數據;管道一般用來處理數據的輸出格式。
因爲這個應用夠簡單,咱們不須要多餘的模塊,一個App模塊做爲啓動模塊,一個路由模塊便可。而後App模塊再按頁面結構分爲app、header、footer、summary、archive、detail、about組件。這些模塊後能夠用ng
命名自動生成,以生成header組件爲例:
ng g component header
咱們的工做中心圍繞組件展開,其他的一切都是爲組件服務的。一個基本組件由三個方面(文件)組成:
[組件名].component.html
;[組件名].component.css
;[組件名].component.ts
下面介紹各個組件的編寫。
app組件是咱們App模塊的bootstrap組件(啓動引導組件),這個ng在建立項目時就已經幫咱們生成了。咱們須要作的是在app組件裏面佈置好頁面結構便可,這須要在該組件對應的HTML頁面app.component.html
裏寫:
<blog-header></blog-header> <main> <router-outlet> </router-outlet> </main> <blog-footer></blog-footer>
相信很容易看懂它的意思:頂部和底部是header和footer組件,它們是固定的,會出如今每一個頁面;夾在中間的main便籤裏面router-outlet
表示的是路由組件,到時候在路由模塊裏指定的是哪個組件,它就會被那個組件代替。而後,你能夠爲main便籤設置點樣式,好比讓它居中,這個在app組件對應的css文件app.component.css
設置便可。這樣app組件就搞定了。
header組件即頁面的導航欄,沒有啥邏輯,所以也只須要編寫其html和css便可:
header.component.html
<nav> <div class="wrapper"> <img class="logo" src="../../../assets/img/logo.jpg"/> <div class="items"> <a class="item" routerLink="/home" routerLinkActive="active">首·頁</a> <a class="item" routerLink="/archives" routerLinkActive="active">歸·檔</a> <a class="item" routerLink="/about" routerLinkActive="active">關·於</a> <a class="item" href="https://github.com/derker94" target="_blank">Github</a> </div> </div> </nav>
代碼也很簡單,但要注意裏面的a
標籤的連接地址是寫在routerLink
屬性裏的,而不是在傳統的href
裏。這個屬性和routerLinkActive
是Angular定義的,照作就是。這樣咱們點擊連接時,不會發出http請求,頁面的路由是Angular完成的。
下面定義route模塊。可使用ng g module app-routing
命令幫咱們自動生成。在生成的模塊定義文件app-routing.module.ts
裏,須要交代路由連接與相應模板的關係,以前咱們在app組件一節中就說過,這樣<router-outlet></router-outlet>
,就會被相應的組件替換。具體代碼以下:
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import {SummaryComponent} from './components/summary/summary.component' import {ArchiveComponent} from './components/archive/archive.component' import {AboutComponent} from './components/about/about.component' import {DetailComponent} from './components/detail/detail.component' const routes: Routes = [ {path: 'home', component: SummaryComponent}, {path: 'archives', component: ArchiveComponent}, {path: 'about', component: AboutComponent}, {path: '', redirectTo: '/home', pathMatch: 'full'}, {path: 'articles/:id', component: DetailComponent}, ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule], providers: [] }) export class AppRoutingModule { }
路由編寫好後,你就能夠點擊頁面上的連接了,看看路由是否是生效了呢。
這兩個組件沒什麼好介紹的,都是些寫死的數據。
根據以上路由規則,這個組件是咱們在訪問/home
時用到的組件。它是一個文章摘要的列表,就像下圖同樣:
看到列表,天然想到對應的數據結構,數組;而列表的每一項正對應文章(Article)數據結構。因而先定義Article數據結構:
article.ts
export class Article { _id: string; title: string; word: number; // 字數 view: number; // 閱讀數 comment: number; // 評論 comments: string[]; // 評論 labels: string[]; //標籤 summary: string; // 摘要 html: string; // html 格式內容 date: Date; }
而後,在Summary組件中,固然有一個文章數組的成員變量:
summary.component.ts
export class SummaryComponent implements OnInit { articles: Article[]; constructor() { } }
因而在html中咱們就能夠」顯示「該文章數組了:
summary.component.html
<div class="wrapper" infinite-scroll (scrolled)="onScroll()"> <section *ngFor="let article of articles"> <h2> <a class="primary-link" [routerLink]="['/articles', article._id]">{{article.title}}</a> <time class="float-right">{{article.date | smartDate}}</time> </h2> <p class="hint">字數 {{article.word}} 閱讀 {{article.view}} 評論 {{article.comment || 0}} </p> <p>{{article.summary}}...</p> <p> <span class="label" *ngFor="let label of article.labels">{{label}}</span> </p> </section> </div>
其中用到了用來循環操做的ngFor
指令,具體語法請參考Angular2官方文檔吧。
再回到summary.component.ts中,咱們考慮如何得到這個文章數組呢,以前就說過經過服務來拿,咱們注入一個ArticleService
(目前還沒建立,先寫着吧):
export class SummaryComponent implements OnInit { articles: Article[]; constructor(private articleService: ArticleService) {// <====== } }
而後再生命週期方法裏調用該服務:
ngOnInit() { this.articleService.getSummaries(0, this.limit).subscribe(res => { this.articles = res.data; this.total = res.total; }); }
archive組件也是相似的,這裏就再也不介紹了。
下面編寫Article服務類,好像也沒啥好說的,就不貼代碼了。Angular2在Http裏面用到了RxJs,很值得學習。須要說明的一點是,在咱們的代碼裏,是直接經過後端接口來獲取數據的,要想先後端同步工做,必須先把http接口定義好。還須要說明的一點是,若前端在完成Service後想進行測試,然後端接口開發還沒完成,或者前端在開發階段時服務器是跑在本地的,這樣調用接口存在跨域問題。解決上面問題的方法是使用Angular提供的in-memory-web-api模塊。
以上是用Angular編寫前端的大體過程,相信你已經清楚了。還有一個我遇到的問題是:如何在一個組件中使用第三方的腳本呢?好比我要用Mathjax去處理我頁面裏的Tex公式,之前的作法是直接在html裏面用script便籤引入Mathjax庫便可,但如今好像沒地方可讓咱們這麼去作,在xxx.component.html
中去寫嗎?我試過,不行。最後Google到Stack Overflow裏的一個答案,寫一個服務來幫助咱們加載,具體能夠看我Github上的代碼。
最後,代碼寫完後,咱們可使用npm run build
去build咱們的代碼,最後咱們的代碼會被打包成不多的幾個文件。你會發現,這樣打包出來的代碼,有些文件會很大,有1M左右。能夠開啓aot進行優化,具體是把package.json
中的build對應的命令加上以下參數:
ng build --aot -prod
後端採用Express開發,數據庫使用的是MongoDB,採用這二者主要是開發的快。固然你也能夠經常使用各類其餘的語言技術,好比用JavaWeb來開發,或者用GO,Python,Ruby來開發等等。接口採用Restful風格,以json做爲輸出格式,相信這個很容易就能搞定,這裏很少說。
想提一下的是,本來我準備把開發好的後端代碼也用webpack打包一下,這樣不只能裝x,最重要的是這麼多文件被打包成一個文件,體積上小了很多,並且發佈的時候特方便。但無奈裝x歸裝x,在剛開始還能打包,但隨着安裝的庫的增多,便開始報錯,解決又須要花大力氣,遂放棄。
最後說一下,先後端開發好後怎麼結合在一塊兒呢?這個具體實現要看你的後端選擇的技術了。可是要保證:
index.html
是首頁面,在訪問根url/
時,須要後端把這個index.html
響應給瀏覽器。/
上,或者仍是將index.html
響應給瀏覽器,注意是請求轉發,而不是重定向。第3點是解決一些頁面從首頁點進來是ok的,可是刷新就報404的問題的關鍵。爲何這樣可以解決呢?這是由於咱們使用Angular後,點擊連接時並不是像傳統的那樣發出一個http請求(還記得在header組件中,咱們並無爲a標籤指定href屬性嗎),而是由Angular處理了點擊操做(前端路由),更新了頁面(DOM),並更新了瀏覽器地址欄中的地址。咱們刷新瀏覽器,至關於發出一個http請求去請求該頁面,然後端壓根就沒有編寫處理該請求的邏輯,天然會報404。解決的方法就是既然咱們把路由交給了Angular去作,那麼對於後端沒法處理的請求一樣轉發到前端去,讓前端去完成。
以上過程記錄的並不詳細,緣由是若是你已經學過Angular了,那麼你會以爲太囉嗦了;若是你還沒學過Angular,建議你仍是到官網去學習,那你已經講解的很是詳細了。以上只是記錄總體結構和遇到的問題,但願可以爲你帶來幫助。
最後談一談使用Angular的感受,一個字,太棒了!最大的感覺是,它讓不會組織代碼的人都能把代碼管理的層次分明。至於缺點嘛,儘管使用了aot,但build出來的文件仍是感受太大(500K左右),對於一個跑在1M小水管的博客應用來講,有點接受不了。但若是你開發一個稍微大型點的應用,相信這個缺陷應該不是問題了。