初入angular4——實際項目搭建總結

前言

接到一個pc端後臺項目,還會加入兩個安卓同事一塊兒學習和作這個項目,須要帶一下他們。 既ng1以後,我就沒怎麼有過其它後臺框架的實際項目經驗了,期間用的移動端框架也並不是vue、angular系列、react,包括es六、webpack等也都並不熟悉。 公司一個其它後臺項目使用了vue,偶爾我會維護一下可是整體來講對體系不熟悉。 一直比較喜歡angular,應該是有過ng1框架搭建的經驗的緣由(前面也有寫過博客),學習過ng2和ng4的官方demo,總的來講照着抄寫一遍意義不大,必需要用於實際項目才能真正吸取。 目前ng1確定不要用了,我要從新熟悉和搭建一個架子,從自我喜愛和前期一些基礎來說,ng4是最好的選擇,恰好typescript的語法對安卓同事也比較友好。 向領導請示了以後,也獲得了容許。css

入坑之初,問題通常比較多,使用的是官方angular-cli建立項目。 中途完善cli的功能,理解整個框架是比較費精力的事情。 總的來講此次就花了兩三天時間,梳理好了框架,配置好了一些基本功能和依賴,好比環境部署,路由嵌套,主頁面佈局(側邊欄,導航欄,內容,底部),公共服務(loading,http請求封裝,全局服務title,用戶信息存取)。 然後就直接和同事一塊兒開發了。 最開始還有點沒底,不知道搭架子要花多久時間,如今來看這個進度和時間仍是很滿意的,固然過程當中參考了一些網上的同行分享的資源和代碼。 很是感謝兩位同事的積極支持,他們學習的也很快。html

問題細節點

scss

angular-cli.json 設置 styleExt爲scss以後,在組件裏寫Styles,並不會編譯把scss編譯成css,必需要寫在獨立的scss,而後經過styleUrls引入。前端

 

routes path

routes path前面不能加 /  不然會報錯vue

const routes: Routes = [
    { path: '', redirectTo: '/main', pathMatch: 'full' },
    { 
      path: 'main',  
      component: MainComponent
    },
]

 

 <router-outlet>

<router-outlet> 的意思是當經過路由訪問component的時候,component的selector會找到 <router-outlet></router-outlet> ,@Component定義的selector會直接生成一個標籤,載入到<router-outlet></router-outlet>下方,若是不設置selector標籤名就是默認的ng-component。 react

 

RouterModule

  • forRoot creates a module that contains all the directives, the given routes, and the router service itself.
  • forChild creates a module that contains all the directives and the given routes, but does not include the router service.

總的來講forRoot是定義一級路由,forChild是定義二級路由或者說是子路由。好比咱們的引導模塊(命名通常是app.module.ts),也就是根模塊,裏面註冊路由假設是 /main,就須要使用forRoot。根模塊經過forRoot註冊的路由/main,須要嵌套路由成爲 /main/home ,那home的module就須要用forChild去註冊路由。webpack

 

routes的loadChildren屬性

ng4的提供了路由懶加載,以下,咱們有個一級路由/main/main有個二級路由/home對應home組件,咱們能夠經過,下面的方法來定義子模塊的路由和組件,可是這種寫法就須要把路由寫在一塊兒,而咱們但願home組件的路由是一個單獨的home.routes.ts文件存在於home文件夾中,經過在相似父模塊中引入子模塊的方式註冊home路由git

{
  path: 'main',
 compontent: MainComponent, children: [ { path:
'home', component: HomeComponent, } ] }

因此就要使用loadChildren,經過它來註冊子模塊和子路由,代碼以下。 loadChildren會找到路徑文件app/home/home.module,#隔開,後面是exports的模塊名。 下面的HomeModule, 定義了home的路由和組件關係,由於是二級路由,因此這裏用的是RouterModule.forChild,最後經過框架的處理,就達到了上面代碼裏的 children 屬性的效果。 ps: 本來是訪問 /main/home 就會載入mainhome組件,可是發現直接訪問路徑 /home 能直接載入home組件,彷佛也註冊了一個根域名。  原來是使用了loadChildren以後,HomeModule模塊就已經被註冊了到main模塊下了,而我在AppModule裏又經過imports註冊了一次HomeModule,這樣就重複注入了,並且仍是一次和父模塊同級的註冊,這一點要當心。es6

{
  path: 'main',
compontent: MainComponet,//注意這裏加載的是Main loadChildren:
'app/home/home.module#HomeModule', } //HomeModule const routes: Routes = [ { path: '', component: HomeCompontent} ] @NgModule({ declarations: [   HomeCompontent ], imports: [   RouterModule.forChild(routes) ], providers: [] }) export class HomeModule { }

 

總結一下上面的router相關內容 :

假設一個場景,根模塊註冊兩個路徑,一個是/login,一個是/main/login這個路由訪問就是單純的一個登錄頁面,/main下面的路由都將是對應核心頁面和業務,由於在main組件裏包含了公用的側邊欄、導航欄、內容容器和底部欄,因此 /main路由加載的main組件的內容容器裏須要嵌套子模塊。 舉個實例,當我訪問/main/home的時候main組件會加載home組件到content容器中,當我訪問/main/managermanager組件又會替換content中的home。這樣咱們的公共頁面部分就是不變的main組件,根據子路由的變化,去加載不一樣的組件到content裏。github

如下是main組件的html大體代碼和實際頁面截圖:web

 

 

這裏也有一個知識點是<router-outlet>標籤的嵌套,上面的代碼中<div class="main-content">下面有一個<router-outlet>標籤,home等二級組件會被載入到父組件main的<router-outlet>標籤下。而main組件的父組件是引導組件AppCompontent,因此main組件是經過一級路由被載入到AppCompontent的html模板的<router-outlet>下方,如下是AppCompontent組件部分代碼:

那麼打開調試器,咱們就能從dom節點上看清楚,router-outlet的嵌套關係:

目錄結構 

再看下src的目錄結構,component文件夾是存放一些公共的組件,login和main組件是註冊的一級路由,home和另外一個馬賽克組件是註冊爲main的二級路由,實際後面會註冊不少組件到main下,可是他們的文件夾劃分都是同級。

 

 使用hash路由

RouterModule.forRoot(routes, { useHash: true });使用hash路由,後端不用修改配置,這樣比較方便,省去不少麻煩

title

引入了platform-browser的Title,使用它的setTitle方法改標題

 

APP_BASE_HREF

在非hash路由狀況下,有時候環境的緣由佈置靜態資源路徑的時候可能不是根域名,同時還要刪除index.html的<base>標籤,不然會有問題

import { APP_BASE_HREF } from '@angular/common';

在app.module裏註冊providers: [{provide: APP_BASE_HREF, useValue: environment.publicBase}],

http

使用http相關API,須要注入HttpModule,不然會報錯: No provider for Http

import { HttpModule } from '@angular/http';

declare

引入了三方JS,三方JS定義的全局變量,在引入到代碼裏,編譯會報錯:沒有定義。須要在前面加個申明 declare let thirdVar:any;

規範

文件命名service,component,route,module,主要類型的文件種類很少,每次新建文件命名太長,引入的時候也麻煩,因此除了根目錄命名保持xx.component.ts這種格式,其餘文件統一爲xx.c.ts,xx.s.ts。

xx.s.ts == xx.service.ts | export class xxS
xx.r.ts == xx.routes.ts | export class xxR
xx.m.ts == xx.module.ts | export class xxM
xx.c.ts == xx.component.ts | export class xxC

 

bootstrap4

考慮引入boostrap4來做爲css庫佈局。

關於rem,咱們通常用rem做爲單位的時候,是更但願利用它的特性改變font-size達到自適應效果,會先定義一個font-size的範圍和對應的屏幕寬度範圍,根據設計稿的寬度獲得一個基數,再用設計稿中元素的實際像素去除以基數獲得rem,最後根據屏幕寬度動態設置font-size的相應值達到自適應效果。bootstrap4以瀏覽器字體默認大小16px,直接定義了元素的rem值,它的源碼裏沒有任何計算,我想他們是參照16px來設置的元素大小,而後求出的rem值,當頁面根font-size的值是16px的時候,全部的bootstrap4的元素大小就是標準大小,若是咱們想讓頁面的元素總體放大或者縮小,咱們只須要去改變font-size的大小,font-size設置爲多少,須要咱們本身計算和定義規則。由於是三方庫,因此這塊的實現方法和咱們本身實際項目使用rem的時候,會有些反差。

若是項目中引入了它,咱們給自定義元素直接設置px值的話,就會出現問題,若是咱們須要改變font-size的大小,就必須統一使用rem,不然font-size改變的時候,自定義的px元素並不會改變。那麼自定義元素須要設置爲rem值。

NG-ZORRO

想了想,須要快速開發,仍是須要一個UI插件庫,本身去造輪子開發成本過高,經擼哥介紹,知道了螞蟻金服的ng庫ng-zorro,支持ng4,https://ng.ant.design。 看了下很全,還提供了柵格佈局和按鈕樣式,轉眼一想,若是用bootstrap4,相互之間可能有衝突,好比boostrap的reset相關的,並且用boot的按鈕樣式和螞蟻的樣式可能看起來不搭調。因此我在引入ng-zorro以後,先註釋了對bootstrap4的引用,一些公用樣式,後面能夠考慮本身寫。

部署打包

src目錄下有個environments文件夾,這裏的文件是配置環境的,.angular-cli.json文件有配置兩個默認環境,一個是開發一個是發佈環境,在咱們開發的時候,默認選擇的是dev環境

在src下的main.ts裏有這麼一段代碼,這裏的意思是切換到生產模式時禁用開發環境下特有的檢查(雙重變動檢測週期)來讓應用運行得更快。

咱們在開發項目的時候,也必然須要配置開發,測試和生產環境,不一樣的環境的接口或者其餘設置確定是不同的。 我須要配置一個apiBase變量,表明不一樣環境的接口域名,在開發的時候ng4會運行ng serve在本地運行一個服務,域名是localhost,那麼後端部署的接口確定不在咱們這個開發域名下,因此這個 apiBase 就是咱們後端接口的域名 apiBase='http://www.xxxx.com' (須要後端支持跨域)。 當咱們把打包好的代碼部署到QA或者生產環境後,訪問前端頁面的url和後端接口都在同一域名,因此 apiBase='/' 。 那麼dev和prod的environment代碼分別以下:

//dev
export const environment = {
  production: false,
  apiBase: 'http://www.xx.com/'
};
//prod
export const environment = {
  production: true,
  apiBase: '/'
};

 

angular-cli 建立的 environment.ts 裏有一段註釋,以下圖。 意思是若是咱們用 ng build 命令打包的時候,加上 --env=prod(若是是自定義environment文件,必須是ng build --environment=xxx命令),將會把 environment.ts 替代爲 environment.prod.ts ,那麼 main.ts 裏的代碼 import { environment } from './environments/environment'; 實際變成了 import { environment } from './environments/environment.prod'; 能夠經過在 main.ts 打印日誌查詢當前環境變量是不是咱們須要的

 

所以,咱們就只須要把相應的環境變量配置好,以下API接口的代碼和 main.ts 文件同樣,我引入了 environment,在開發或者打包的時候,angular 配置的打包工具會自動載入相應的環境變量

 

結語

由於業務需求緣由,過久沒真正學習搭建新框架了,內心也一直不踏實,感受再不學點都跟不上時代了, 因此此次項目的機會也算是了卻本身一個心願。 對比ng4和ng1,開發模式有了很大的變化,給個人感受就是ng4的模塊劃分更乾淨,寫法更舒服。 也多是由於有一些angualr系列的基礎, 能力也應該比前年學習ng1的時候更強,此次入門很快。 es6和typescript語法有時候分不清誰是誰,落後的知識趁着此次儘快補起來。 由於新鮮感的緣由,寫代碼變得更有樂趣了,在頁面細節和動畫上,稍微多搞了一點東西進去(後臺項目沒有設計師,自由發揮)。轉眼3個多月沒寫博客了,這之間學的新東西很少,可是回頭補了一下基礎的一些知識,也算是有不少收穫和新的理解。 設計模式的博客寫了一部分,後面會抽時間一步步完成,是一篇但願你們都能看懂的博客,盡請期待!

 

PS:這篇博客不按期更新,更新的寫到下面吧。

 

  • [ngClass]="item.fromAccount == webimS.userId ? 'me':'other'" ,ngClass能夠這樣寫,官網沒有這種示例
  • 按照縮寫規則,指令directive我應該寫成xx.d.ts的,可是d.ts這個格式的文件彷佛會被框架其餘程序處理,就會報錯,因此指令的命名我就沒用d.ts這樣的縮寫了
  • 指令在父模塊declarations以後,發如今子模塊裏使用指令,根本沒有反應。折騰了好久,發現declarations申明的只在當前模塊才能使用,而個人懶加載的子模塊無效,因此爲指令定義一個獨立的module,在須要使用指令的地方import這個module,以下圖

 

  • 在指令directive中要拿到當前使用指令的dom,須要使用ViewContainerRef

import { Directive, EventEmitter, ViewContainerRef, AfterViewInit, OnDestroy} from '@angular/core';
constructor(public viewContainerRef: ViewContainerRef) { }
  • 若是咱們須要拿到當前控制器下某個dom節點,須要使用@ViewChild
//compontent
@ViewChild('xxx')
 xxx: ElementRef;

getXXX(){
    console.log(this.xxx)
}

//html
<div #xxx></div>

 

表單驗證指令封裝

   ng提供表單驗證 FormGroup 能夠定義每一個表單的驗證條件,定義好以後,須要在表單下面寫不少的ngIf dom來判斷和展現當前表單的錯誤填寫提醒,這樣很很差的一點是提醒的文字是須要佔位置的,在處理頁面的時候須要兼容這些提醒文案,給他們作兼容佈局(若是表單所有是獨立的一排一排的還好,若是一行裏有不少表單,每一個表單的寬度可能也不同,這時提醒文案就很差放了),並且每次寫那麼多條件和dom真的很麻煩。

寫了一個指令組件,提醒文案做爲彈出層展現出來,把當前表單的formControl對象傳入指令,把全部的條件統一文案,好比說required 文案爲‘’必填‘’。那須要作4件事情,1:動態爲指令里加入組件(參考了官網 核心知識->模板與綁定->動態組件),2:讓組件絕對定位到表單右上角,須要用一個div包裹一下表單,並獲取表單的寬度,把寬度傳給組件,組件給提示框設置絕對定位。3:傳入formControl對象,指令組件須要判斷顯示隱藏,4:統一文案,條件知足後給顯示框展現對應文案,由於formControl的errors是一個對象,因此須要配置一個管道pipe來把errors轉換爲對應文案。

 

一個報錯:

ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'false'.

我寫的每個指令都會有這個報錯,通常報錯在數據變化後觸發,網上查了一下,說是沒有使用 enableProdMode();方法就會觸發這個報錯,在main.ts中判斷了環境,若是是開發環境的話就不使用enableProdMode();方法,因此目前開發環境會報錯,可是也不影響邏輯,因此這個報錯暫時忽略

 

form reset:

一個頁面可能會有一個彈窗來填寫表單,填寫的時候有兩種狀態,編輯和新增,可是都是用的同一個彈窗對象,表單作了驗證、錯誤條件達到而且dirty屬性爲true的時候,就會展現錯誤提醒。在新增和編輯切換的時候若是直接修改表單的值,dirty就會一直是true,就可能一直有錯誤提示。因此須要在必定狀況下使用 formGroup 的 reset來重置表單,dirty就會是false了。每一個表單本身也有reset方法。當使用formGroup重置表單的時候,textarea有可能並不會被重置,若是沒有被重置,須要單獨處理下textarea,給textarea的formControl對象單獨reset一下。

 

正式環境打包的檢查:

使用ng build --prod 命令時,打包的檢查比較嚴格。開發時候使用的private定義能夠在模板裏使用對象,在開發環境就會報錯。一些模板裏綁定的對象數據,是須要後端返回了數據纔會傳值給對象,在打包的時候就會檢測到當前對象屬性不存在就沒法經過,這時就不能用{{xxx.atrr}}這種形式輸出數據,得用{{xxx['attr']}}這種方式,才能跳過檢查。

 

某些狀況使用this編譯會報錯:

 直接上圖,圖一會報錯 編譯沒法經過,代碼邏輯是正確的。圖二能編譯經過

 

 

 

ng5出來後,我會把當前項目升級,到時候項目也比較完善了,我會抽出核心部分,分享到github上,敬請期待。

相關文章
相關標籤/搜索