聲明 本系列文章內容梳理自以下來源: "Angular 官方中文版教程" 官方的教程,其實已經很詳細且易懂,這裡再次梳理的目的在於複習和鞏固相關知識點,剛開始接觸學習 Angular 的還是建議以官網為主。 因為這系列文章,更多的會帶有我個人的一些理解和解讀,由於目前我也才剛開始接觸 Angular ...
聲明
本系列文章內容梳理自以下來源:
官方的教程,其實已經很詳細且易懂,這裡再次梳理的目的在於複習和鞏固相關知識點,剛開始接觸學習 Angular 的還是建議以官網為主。
因為這系列文章,更多的會帶有我個人的一些理解和解讀,由於目前我也才剛開始接觸 Angular 不久,在該階段的一些理解並不一定是正確的,擔心會有所誤導,所以還是以官網為主。
正文- 架構概覽
接觸 Angular 大概一個月吧,期間寫了個項目,趁現在稍微有點時間,來回顧梳理一下。
其實,如果前端網站並不是特別複雜,那麼使用 Angular 無非也就是常跟幾個重要的知識點打交道,在官網的核心知識的第一節中就將這些知識點羅列出來了,也就是:架構概覽。
畫了這個圖來大概表示下 Angular 的架構概覽,基本涉及到一些常見的重要的知識點了,比如:
- 模塊
- 路由
- 組件
- 模板
- 服務
- 指令
- 管道
不同的類型,文件名通常會都按照一定的規範來命名,以便直接看出該文件的角色。
當然,文件命名只是給開發人員來方便維護、辨別,對於 Angular 來說,這些都是一份份的 ts 文件代碼,所以,都需要在相對應的文件中加上一些裝飾器比如:@Directive,@Pipe,@Component,@NgModel 等這些,才能夠讓 Angular 識別出該文件的角色、用途。
基本上,用 Angular 做一個簡單的前端項目,就是跟上面這些打交道,理清它們各自的用途及用法,還有之間的聯繫,基本上,就可以上手進行一些開發了。
當然,像在 Service 服務中,還會有非同步編程、HttpClient 網路編程的相關知識點;
在 Component 組件中,也還會有表單、動畫相關的編程知識點,這些都是需要進一步去深入學習研究,但從總體架構上來看,就是要先瞭解以上這些知識點了。
模塊
一個 Angular 項目,至少會有一個模塊,即最少都會有一份用 @NgModel 聲明的 ts 文件,表明該文件作為模塊角色,來管理其他角色。
其他角色包括:組件、指令、管道、服務等等,這些角色必須在模塊文件中聲明瞭,才能夠被該模塊內的其他角色所使用,而且同一個組件、指令、管道不允許同時在多個模塊中進行聲明,只能通過模塊 exports 給其他模塊使用。
Angular 里的模塊,並不等同於 Android 項目中的模塊概念。
在 Android 項目代碼中,可能我們會根據功能來進行模塊的劃分,但這個模塊僅僅是抽象上的概念,也就是建個包,把代碼都集中管理。
而 Angular 里的模塊,不僅可以在項目結構上集中管理同一個模塊的代碼文件,還可以為模塊內的代碼提供一個運行的上下文。
意思就是說,不同模塊在運行期間互不影響,就好像各自運行在各自的沙箱容器中一樣。舉個簡單的例子,在不同模塊中聲明相同的變數名,或相同的 css 的類選擇器,它們之間並不會起衝突。
當然,模塊之間可以有交互,模塊可以依賴於另一模塊,模塊內的可以共用資源等等,所以,NgModel 中有許多需要配置的聲明項,比如:
- declarations:聲明屬於本模塊內的組件、指令、管道
- providers:聲明屬於本模塊內的服務
- imports:聲明本模塊所引用的其他模塊,通常是 imports 其他模塊在 exports 中聲明的項
- exports:聲明本模塊對外公開的組件、指令、管道等,在這裡公開的項才可以被其他模塊所使用
- bootstrap:只有根模塊才需要配置,用來設置應用主視圖,Angular 應用啟動後,這裡就是入口,類似於 Android 中的入口 Activity
- 還有其他一些可選配置,比如應用主題,或者動態的組件聲明等等
在 Angular 中,大多數的模式就是,一個根模塊管理著很多功能模塊,然後,每個模塊管理自己模塊內部所使用到的組件、指令、管道、服務、或者需要依賴於其他模塊,如果該模塊內部的這些角色,有些可以供其他模塊使用,那麼就需要對外暴露。
路由
一個項目這麼多模塊,Angular 並不會一開始就把所有模塊都載入,而是惰性載入,按需載入。
那麼,什麼時候會去載入呢?
就是等某個模塊內部的組件被使用的時候會載入,而組件是什麼時候會被使用的呢?
有兩個時機,一是組件被直接調用;二是觸發了路由去載入;
路由通常的配置方式是用一個 @NgModel 聲明的模塊,但只用其中兩項配置:imports 和 exports,imports 用來導入當前模塊所有組件與 url 的映射表,而 exports 用來將這些映射表信息暴露,以供相對應的模塊去引入使用。
當然,你不想抽離路由配置,直接將其配置在對應模塊的 imports 內也可以,抽離的話,相對獨立,可維護。
區別於傳統的前端網頁的跳轉方式,Angular 項目是一個單頁應用,所謂的單頁應用就是說只有一個頁面,所有頁面的跳轉,其實是將當前頁面的顯示內容進行替換,頁面仍舊只有一個,並不會打開新的頁面。
而頁面的跳轉,通常有以下幾種場景:
- 用戶輸入 url 進行跳轉
- 用戶點擊交互按鈕進行跳轉
- 用戶操作前進或後退進行跳轉
這些場景,路由的工作機制都能夠很好的支持。
如果網頁很簡單,只有一個首頁,並不存在頁面跳轉場景,那麼可以不用配置路由,只需要在 index.html 中配置根視圖,以及在根模塊的 bootstrap 中配置根視圖組件即可。
但如果項目劃分成了多個功能模塊,那麼應該交由每個模塊管理自己的路由表,而後選擇一個上層模塊,來統一關聯各個模塊路由,有兩種方式:一是在上層模塊的 imports 內按照一定順序來導入各個功能模塊;但這種方式想要按照路由層級來查看路由表就比較麻煩,需要到各個模塊內部去查看或者藉助一些工具。
另一種方式是,在上層模塊的路由表中使用 loadChildren 載入各個功能模塊,然後各個功能模塊預設路由都顯示成空視圖,各自內部再通過配置 children 的路由表方式來管理各個模塊內部自己的路由表。
組件與模板
在 Angular 中,最常接觸的應該就是組件了。
我是這麼理解的,組件可以是你在界面上看到的任何東西,可以是一個頁面,可以是頁面上的一個按鈕。
而對於瀏覽器解析並呈現前端頁面時,Html、CSS、JavaScript 這三分文件通常都是需要的,而 Angular 是使用了 TypeScript,所以一個組件,其實就包括了:Html,CSS,TypeScript。
在 Angular 中,可以說,是以組件為單位來組成頁面的,組件是核心,因為 Angular 提供的功能基本都是用來為組件服務的。
以上,是我的理解。
但要註意,官網教程中,很多地方的組件描述,更多時候是傾向於表示 TypeScript 的那份文件,因為對於組件來說,TypeScript 可以說是它的核心,CSS 只是樣式文件,Html 更類似於模板存在。
所以這裡將組件和模板放在一起講,因為就像開頭那張圖一樣,組件是一份 TypeScript 文件,在該文件中,定義了這個組件的模板(template)來源和 CSS 樣式來源。
模板提供了該組件的呈現結構,而 TypeScript 里定義了組件的數據來源及交互行為,它們兩一起組織成一個視圖呈現給用戶。
既然,這份 TypeScript 的組件文件和模板文件需要共同合作,那麼它們之間就少不了交互,所以就涉及到很多所謂的模板語法,也就是所謂的組件和模板之間的交互方式。
比如,當要往模板中嵌入 TypeScript 中的變數數據時,可以使用 {{value}}
這種語法形式,同樣的,還有模板中標簽的屬性綁定,事件回調註冊的交互方式的語法。
總之,Angular 支持雙向數據綁定,是一種以數據驅動的思想來讓頁面進行交互刷新的方式,區別於傳統的前端模式。在以往,如果需要動態的更新 DOM 上的信息時,需要先獲取到相對應的元素實例對象,然後調用相應的 DOM API 來操縱 DOM;
而使用 Angular 的話,可以直接在模板的相應元素中,將某個屬性與 TypeScript 文件中某個變數直接進行綁定,後續這個變數值變化時,Angular 會自動去更新相應 DOM 的屬性,也就是說,原本那些操縱 DOM 的代碼,Angular 幫我們做了,我們不用再自己去處理了。
另外,註意,以上出現的 TypeScript 的描述,你可以理解成官網中的組件,我之所以不想用組件的方式來進行描述,是因為,我覺得,組件是一個整體,它本身就包括了 TypeScript 文件和模板文件,所以官網中說的組件和模板的交互,我覺得,換成組件中的 TypeScript 文件與模板文件的交互更為適合。
當然,這隻是我目前階段的理解。
服務
服務是一個廣義上的概念,通常用來處理那些跟 UI 交互無關的事情,比如網路請求的工作等。
所以它也是為組件服務,而且 Angular 有一套依賴註入機制,也就是說,組件只需要告訴 Angular,它需要哪些服務,至於這些服務的實例是什麼時候創建,交給誰去管理等這些組件內部都不用自己去處理了。
Angular 會自動創建相關的服務實例,然後在組件適當的時候,將這個實例註入給組件去使用。
這種模式跟以前在 Android 端開發時有所區別,在 Android 端中,當需要業務層某個實例對象時,通常都需要自己內部去初始化,或者這個實例是個單例的話,也需要自己去實現單例。
但在 Angular 中,你可以藉助它依賴註入的機制,來讓 Angular 幫你去做這些依賴的對象的實例管理的事,如果需要一個全局的單例服務,那麼可以將該服務聲明成 root 即全局可用;如果需要一個模塊內的單例,那麼可以在該模塊的 providers 中聲明該服務;如果需要一個組件自己的實例對象,那麼可以在組件的元數據塊的 providers 中配置該服務。
總之,就是,跟 UI 交互無關的工作,可以抽到服務中去處理,而該服務實例的管理,交給 Angular 就可以了,組件只需要告訴 Angular 它需要哪種形式的服務即可。
那麼,組件是怎麼告訴 Angular 的呢?
同樣在 Android 項目或者後端項目中,也有一些依賴註入框架,那些通常都是藉助註解的方式來實現。
但在 Angular 中,不用這麼麻煩,直接在組件的構造函數的參數中,聲明某個服務類型的參數即可。
指令
指令也是為組件服務的,但是,是在組件的模板文件中來使用。
因為組件的模板,其實就是一份 HTML 文件,基於 HTML 的標簽之上,加上一些 Angular 的模板語法,而 Angular 在將這份 HTML 文件代碼交給瀏覽器解析之前,會先自行解析一遍,去將模板中不屬於 HTML 的那些語法解析出相應的行為。
而指令分為結構型指令和屬性型指令,它們的區別,其實就在於,一個是改變 DOM 的結構,一個是改變 DOM 元素的樣式。
所以說,指令的目的,其實就是簡化一些操縱 DOM 的工作,比如你需要讓某些按鈕都具有統一的行為和樣式,當被點擊時先做什麼,再做什麼。
實現這個,你當然可以在 TypeScript 中去書寫這些邏輯,但要應用到每個按鈕上,就比較繁瑣。
這個時候,就可以將這些工作都封裝到指令內部,然後在每個按鈕標簽上加上該指令,Angular 在解析模板時,發現了這個指令,就會為每個按鈕都加上這麼一段程式邏輯。
我個人覺得,指令的功能,讓我們處理一些相同的行為,可以更好的去封裝,減少冗餘和繁瑣。
當然,上面舉的場景,也可以自己封裝個按鈕組件,然後在其他模板中,不使用原生按鈕,而使用封裝後的按鈕組件,也可以達到目的。
所以,組件其實也是指令的一種,但組件的實現方式會比較重,有時候,只需要封裝一些簡單的行為邏輯,就可以直接藉助指令的方式封裝。
指令的原理也很簡單,在模板中某個元素標簽上,添加上某個指令後,解析到這個指令時,會進入這個指令的相關工作,而指令內部,會獲取到一個當前指令掛載的元素標簽對象,既然都拿到這個對象了,那麼,在指令內部想對這個元素做什麼,都可以了。
指令還有另一個通途,通常用來擴展原有的功能,因為可能項目中,在模板里使用的組件或者 HTML 元素的標簽因為種種原生無權或不方便進行修改,而又想在其基礎上擴展一些功能,此時就可以利用指令來實現。
管道
管道同樣是為組件服務,也同樣是在組件的模板文件中來使用。
它的用途,在於,將數據按照一定的規則進行轉換,比如 Object 對象,轉換成 json 格式數據,再比如,long 型的時間,轉換成具體的時間日期等等。
Angular 中已經內置了一些管道,也可以自定義管道。
示例
大概瞭解了 Angular 的架構概覽,接下去就來看看一個簡單的 Angular 項目結構,以及各個文件、模塊的用途,稍微講一下。
這是用 WebStrom 創建一個 Angular 項目後,自動生成的簡單架構。
在利用 Angular Cli 工具生成腳手架時,預設就已經生成了很多配置項,而且此時,項目已經是可以運行的,因為也自動生成了一個根模塊和根視圖,預設頁面是 Angular 的歡迎界面。
挑幾個來講講。
angular.json
這是 Angular-CLI 的配置文件,而 Angular-CLI 是自動化的工程構建工具,也就是利用這個工具,可以幫助我們完成很多工作,比如創建項目、創建文件、構建、打包等等。
原本的 HTML、CSS、JavaScript 的前端開發模式,並沒有工程的概念,只要用瀏覽器打開 HTML 文件就能夠運行。而 Angular 引入了 TypeScript,Scss 等瀏覽器並不無法識別的語言,自然,要讓瀏覽器運行 Angular 項目之前,需要進行一次編譯,一次轉換。
這些工作就可以藉助 Angular-CLI 來進行。另外,創建一個模塊,創建一個組件,也都可以通過 Angular-CLI 來。
那麼,在創建這些文件或者說,打包編譯這些項目文件時,該按照怎樣的規則,就是參照 angular.json 這份配置文件。
大概看一下內容:
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json", // 預設的配置項,比如預設配置了 ng g component 生成組件時應該生成哪些文件等等
"version": 1,
"newProjectRoot": "projects",
"projects": {
"daView": { // 項目的配置
"root": "",
"sourceRoot": "src", // 源代碼路基
"projectType": "application", // 項目的類型,是應用還是三方庫(library)
"prefix": "app", // 利用命令生成 component 和 directive 的首碼
"schematics": {}, // 替換掉第一行的 schema.json 中的一些預設配置項,不如創建組件時,不要生成spec文件
"architect": { // 執行一些構造工作時的配置
"build": { // 執行 ng build 時的一些配置項
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/daView", // 編譯後的文件輸出的位置
"index": "src/index.html", // 構建所需的模板 Index.html
"main": "src/main.ts", // 構建所需的文件
"polyfills": "src/polyfills.ts", // 構建所需的文件
"tsConfig": "src/tsconfig.app.json", // 對 typescript 編譯的配置文件
"assets": [ // 構建所需的資源
"src/favicon.ico",
"src/assets"
],
"styles": [ // 構建所需的樣式文件,可以是 scss
"src/styles.css"
],
"scripts": [] // 構建所需的三方庫,比如 jQuery
},
"configurations": {/*...*/}
},
"serve": {/*...*/}, // 執行 ng serve 時的一些配置項
"extract-i18n": {/*...*/},
"test": {/*...*/},
"lint": {/*...*/}
}
}
},
"daView-e2e": {/*...*/},
"defaultProject": "daView"
}
所以,利用 Angular-CLI 生成的初始項目中,有許多基本的文件,這些文件,基本也都在 angular.json 中被配置使用了,每個配置文件基本都有各自的用途。
比如,tslint 用來配置 lint 檢查,tsconfig 用來配置 TypeScript 的編譯配置,其他那些 html,css,ts,js 文件基本都是 Angular 項目運行所需的基礎文件。
package.json
對於一個工程項目來說,依賴的三方庫管理工具也很重要,在 Android 項目中,通常是藉助 Gradle 或 maven 來管理三方庫。
而在 Angular 項目中,是使用 npm 來進行三方庫的管理,對應的配置文件就是 package.json。
在這份配置文件中,配置了項目所需要的三方庫,npm 會自動去將這些三方庫下載到 node_modules
目錄中。然後,再去將一些需要一起打包的三方庫在 angular.json 中進行配置。
app/src 源碼
以上就是利用 Angular-CLI 創建項目生成的初始架構中各個文件的大概用途,下麵講講 Angular 項目的大概運行流程。
在 src 中的 index.html
文件就是單頁應用的頁面文件,裡面的 body 標簽內,自動加入了一行根視圖的組件:
<app-root></app-root>
就是根組件 AppComponent (自動生成的)的組件標簽,當 Angular 在 HTML 文件中發現有組件標簽時,就會去載入該組件所屬的模塊,並去解析組件的模板文件,將其嵌入到 HTML 文件的組件標簽中。
看一下自動生成的根模塊的部分內容:
//app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
//app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'daView';
}
app.module.ts
文件用 @NgModule 表示該文件角色是模塊,併在內部配置了它的組件 AppComponent,這樣 AppComponent 組件就只屬於該模塊了,並能夠在該模塊內的其他組件中被使用。
另外,由於該模塊是根模塊,所以還需要配置 bootstrap,設置應用的根視圖,這個配置需要和 index.html
里的 body 標簽內的根視圖組件是同一個組件,否則運行時就會報錯了。
當項目中模塊多了的時候,各模塊之間基本是通過路由或者組件來進行相互關聯。
比如,我們新創建個 Home 模塊,然後在根模塊中創建個 app-routing 路由配置文件:
//app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [
{
path: 'home', loadChildren: './home/home.module#HomeModule'
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
然後在 app.module.ts 的 imports 中將該路由配置導入,這樣當路由到 home 時,會去載入 home 模塊,然後看看 home 模塊的路由配置:
//home-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import {HomeComponent} from './home.component';
import {HomeCenterComponent} from './component/home-center.component';
const routes: Routes = [
{
path: '',
children: [
{
path: '', component: HomeCenterComponent
}
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class HomeRoutingModule { }
home 模塊的預設視圖為空,但交由其子視圖來控制,所以,當導航到 home 時,home 模塊會去載入它內部的 HomeCenterComponent 組件。
以上,是當項目中有多模塊時,我的處理方式。
當按照這種方式來實現時,對於瞭解一個 Angular,就有一定的規律可循了:
- 先找根視圖組件,然後確認根視圖組件中的 router-outlet 標簽的區域,因為這個區域展示的就是由根模塊路由導航到的新的組件內容;
- 去根模塊的配置中找到根模塊的路由配置表,來查看第一個層級的路由分別對應哪些模塊;
- 去這些相應的模塊中,查看它們各自內部的路由配置表,來確定各自模塊的預設視圖組件是哪個,下一個層級的各個路由所對應的視圖組件;
- 這樣,一個頁面的組件層次結構就能夠很快的理清。
大家好,我是 dasu,歡迎關註我的公眾號(dasuAndroidTv),公眾號中有我的聯繫方式,歡迎有事沒事來嘮嗑一下,如果你覺得本篇內容有幫助到你,可以轉載但記得要關註,要標明原文哦,謝謝支持~