在本文中,我们将研究如何将Angular项目更新到最新版本。
本文是SitePoint Angular 2+教程的第6部分,该教程介绍如何使用Angular CLI创建CRUD应用程序。
在第1部分中,我们学习了如何启动和运行Todo应用程序并将其部署到GitHub页面。 这样做很好,但是不幸的是,整个应用程序都挤在一个组件中。
在第2部分中,我们研究了模块化程度更高的组件体系结构,并学习了如何将单个组件分解为较小的组件的结构树,这些树更易于理解,重用和维护。
在第3部分中,我们更新了应用程序以使用RxJS和Angular的HTTP服务与REST API后端进行通信。
在第4部分中 ,我们介绍了Angular Router,并了解了当浏览器URL更改时路由器如何更新我们的应用程序,以及如何使用路由器从后端API解析数据。
在第5部分中 ,我们向应用程序添加了身份验证,并了解了如何保护应用程序中的部分免受未经授权的访问。
不用担心 您无需遵循本教程的第1、2、3、4或5部分就可以使6有意义。 您可以简单地获取我们的仓库的副本,查看第5部分中的代码,并将其用作起点。 下面将对此进行详细说明。
要开始我们更新Angular的目标,请确保您已安装最新版本的Angular CLI。 如果没有安装,则可以使用以下命令进行安装:
npm install -g @angular/cli@latest
如果您需要删除以前版本的Angular CLI,则可以:
npm uninstall -g @angular/cli angular-cli
npm cache clean
npm install -g @angular/cli@latest
之后,您将需要第5部分中的代码的副本。可以在GitHub上找到它 。 本系列中的每篇文章在存储库中都有一个相应的标记,因此您可以在应用程序的不同状态之间来回切换。
我们在第5部分结尾并在本文中开始的代码被标记为part-5 。 本文结尾处的代码标记为part-6 。
您可以将标签视为特定提交ID的别名。 您可以使用git checkout
在它们之间切换。 您可以在此处阅读更多内容 。
因此,要启动并运行(安装了最新版本的Angular CLI),我们可以这样做:
git clone [email protected]:sitepoint-editors/angular-todo-app.git
cd angular-todo-app
git checkout part-5
npm install
ng serve
然后访问http:// localhost:4200 / 。 如果一切顺利,您应该会看到正在运行的Todo应用程序。
在本文中,当我们更新Angular时,我们将学习以下内容:
到本文结尾,您将了解:
让我们开始吧!
为了支持蓬勃发展的生态系统,Angular必须既稳定又不断发展。
一方面,Angular旨在为开发人员提供关键任务应用程序的最大稳定性。 另一方面,它不断需要适应和发展以支持Web技术的最新变化。
因此,Angular团队决定使用带有语义版本控制 的基于时间的发布周期 。
基于时间的发布周期意味着我们可以每两周或几个月获得Angular的新版本(Angular 5,Angular 6,Angular 7等)。
语义版本控制意味着Angular的版本号使我们能够预测,如果我们升级到它,是否会破坏我们的应用程序。
本质上,语义版本如下: Major.Minor.Patch
。
因此,版本v1.3.8
的主要组件值为1,次要组件值为3,补丁组件的值为1。
发布新版本时,新版本将隐式指示对代码进行更改的类型。
增加语义版本时,将应用以下规则:
每个增量以数字形式发生,增量为1。
修复错误并保持代码向后兼容后,补丁组件就会增加:
v0.0.3 // Before bugfix
v0.0.4 // After bugfix
添加功能并且代码保持向后兼容时,将增加次要组件,并将修补程序组件重置为零:
v0.2.4 // Before addition of new functionality
v0.3.0 // After addition of new functionality
当实现导致代码向后不兼容的更改 (也称为中断更改)时 ,将增加主要部分,次要和补丁部分重置为零:
v7.3.5 // Before implementing backwards incompatible changes
v8.0.0 // After implementing backwards incompatible changes
如果您不熟悉语义版本控制,请确保查看此简单的语义版本控制指南 。
Angular团队将语义版本控制与基于时间的发布周期相结合,旨在:
发布时间表并不是一成不变的,因为可能会有假期或特殊事件,但这很好地表明了我们对即将发布的版本的期望。
您可以关注Angular官方博客和官方更改日志 ,以了解最新动态。
语义版本的一个巨大好处是,我们可以使用补丁程序或次要版本安全地更新Angular应用程序,而不必担心会破坏我们的应用程序。
但是,如果有新的主要版本怎么办?
我们已经了解到,重大发布可能伴随重大更改。 那么,如何知道我们现有的应用程序是否会中断,如果我们更新它呢?
一种方法是阅读正式的更改日志并浏览更改列表。
一种简单得多的方法是使用《 Angular更新指南》来更新Angular。 您选择当前的Angular版本以及要升级到的版本,应用程序会告诉您需要采取的确切步骤:
对于我们的Angular Todo应用程序,我们希望从Angular 4.0升级到Angular 5.0。
让我们选择应用程序复杂性级别“ 高级”,以便我们看到需要采取的所有可能措施:
我们获得了更新应用程序所需采取的所有步骤的完整概述。
多么甜蜜!
更新之前列表包含12个项目。 没有任何一项适用于我们的Angular Todo应用程序,因此我们可以安全地进行下一步。
在“更新期间”列表中,仅最后一项适用于我们的应用程序。 我们需要更新依赖关系,因此让我们在项目的根目录中运行建议的命令:
$ npm install @angular/{animations,common,compiler,compiler-cli,core,forms,http,platform-browser,platform-browser-dynamic,platform-server,router}@'^5.0.0' [email protected] rxjs@'^5.5.2'
$ npm install [email protected] --save-exact
因为我们在“ 启动和运行”部分中将Angular CLI更新为最新版本,所以我们也更新了本地版本:
$ npm install @angular/cli@latest --save-dev
为了验证我们的应用程序是否正常运行,我们运行:
$ ng serve
如果ng serve
无法启动,请尝试删除您的node_modules
目录和package-lock.json
文件,然后运行npm install
重新创建一个干净的node_modules
目录和package-lock.json
文件。
更新后列表包含四个项目,其中第一个和最后一个适用于我们的应用程序:
HttpModule
切换到HttpClientModule
rxjs/operators
导入RxJS运算rxjs/operators
并使用RxJS管道运算符 让我们一一解决。
Angular Update Guide告诉我们应该从HttpModule
切换到HttpClientModule
。
如果我们查看Angular 5.0.0发行说明 ,就会了解到Angular 4.3及更高版本附带了一个新的HttpClient
,它可以自动处理JSON响应并支持HTTP拦截器。
它指出,要更新我们的代码,我们必须用HttpClientModule
替换HttpModule
,注入HttpClient
服务并删除所有map(res => res.json())
调用,因为新的HttpClient
自动解析JSON响应。
让我们打开src/app/app.module.ts
并替换HttpModule
:
// ...
import { HttpModule } from '@angular/http';
@NgModule({
declarations: [
// ...
],
imports: [
// ...
HttpModule,
],
providers: [
// ...
],
bootstrap: [AppComponent]
})
export class AppModule {
}
使用HttpClientModule
:
// ...
import { HttpClientModule } from '@angular/common/http';
@NgModule({
declarations: [
// ...
],
imports: [
// ...
HttpClientModule,
],
providers: [
// ...
],
bootstrap: [AppComponent]
})
export class AppModule {
}
接下来,我们必须使用HttpClient
服务而不是Http
服务,并删除代码中的所有map(res => res.json())
调用,因为新的HttpClient
自动为我们解析响应。
在第3部分中 ,我们将所有与HTTP相关的代码集中在一个名为ApiService
的服务中,现在,我们收获了该方法的好处。
结果,我们只需要更新一个文件,所以让我们打开src/app/api.service.ts
并替换:
import {
Http,
Headers,
RequestOptions,
Response
} from '@angular/http';
// ...
@Injectable()
export class ApiService {
constructor(
private http: Http,
private session: SessionService
) {
}
public signIn(username: string, password: string) {
return this.http
.post(API_URL + '/sign-in', {
username,
password
})
.map(response => response.json())
.catch(this.handleError);
}
public getAllTodos(): Observable {
const options = this.getRequestOptions();
return this.http
.get(API_URL + '/todos', options)
.map(response => {
const todos = response.json();
return todos.map((todo) => new Todo(todo));
})
.catch(this.handleError);
}
public createTodo(todo: Todo): Observable {
const options = this.getRequestOptions();
return this.http
.post(API_URL + '/todos', todo, options)
.map(response => {
return new Todo(response.json());
})
.catch(this.handleError);
}
public getTodoById(todoId: number): Observable {
const options = this.getRequestOptions();
return this.http
.get(API_URL + '/todos/' + todoId, options)
.map(response => {
return new Todo(response.json());
})
.catch(this.handleError);
}
public updateTodo(todo: Todo): Observable {
const options = this.getRequestOptions();
return this.http
.put(API_URL + '/todos/' + todo.id, todo, options)
.map(response => {
return new Todo(response.json());
})
.catch(this.handleError);
}
public deleteTodoById(todoId: number): Observable {
const options = this.getRequestOptions();
return this.http
.delete(API_URL + '/todos/' + todoId, options)
.map(response => null)
.catch(this.handleError);
}
private handleError(error: Response | any) {
console.error('ApiService::handleError', error);
return Observable.throw(error);
}
private getRequestOptions() {
const headers = new Headers({
'Authorization': 'Bearer ' + this.session.accessToken
});
return new RequestOptions({ headers });
}
}
与
import {
HttpClient,
HttpErrorResponse,
HttpHeaders
} from '@angular/common/http';
// ...
@Injectable()
export class ApiService {
constructor(
private http: HttpClient,
private session: SessionService
) {
}
public signIn(username: string, password: string) {
return this.http
.post(API_URL + '/sign-in', {
username,
password
})
.catch(this.handleError);
}
public getAllTodos(): Observable {
const options = this.getRequestOptions();
return this.http
.get(API_URL + '/todos', options)
.map(response => {
const todos = response;
return todos.map((todo) => new Todo(todo));
})
.catch(this.handleError);
}
public createTodo(todo: Todo): Observable {
const options = this.getRequestOptions();
return this.http
.post(API_URL + '/todos', todo, options)
.map(response => {
return new Todo(response);
})
.catch(this.handleError);
}
public getTodoById(todoId: number): Observable {
const options = this.getRequestOptions();
return this.http
.get(API_URL + '/todos/' + todoId, options)
.map(response => {
return new Todo(response);
})
.catch(this.handleError);
}
public updateTodo(todo: Todo): Observable {
const options = this.getRequestOptions();
return this.http
.put(API_URL + '/todos/' + todo.id, todo, options)
.map(response => {
return new Todo(response);
})
.catch(this.handleError);
}
public deleteTodoById(todoId: number): Observable {
const options = this.getRequestOptions();
return this.http
.delete(API_URL + '/todos/' + todoId, options)
.map(response => null)
.catch(this.handleError);
}
// ...
}
我们将HttpModule
的旧类替换为HttpClientModule
的新类。
更具体地说,我们替换为:
import { Http, Headers, RequestOptions, Response } from '@angular/http';
import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
HttpErrorResponse
Response
Headers
与HttpHeaders
return new RequestOptions({ headers });
与return { headers };
如果我们运行:
$ ng serve
并将浏览器导航到http://localhost:4200
,我们看到我们的应用程序仍然可以按预期运行,但是现在在幕后使用HttpClientModule
。
是时候解决项目2:从rxjs/operators
导入RxJS运算rxjs/operators
并使用RxJS管道运算符了。
Angular 5已更新为使用RxJS 5.5.2或更高版本。
从5.5版开始,RxJS附带了可管道运算符 。 官方文件说:
管道运算符是返回带有签名的函数的任何函数:
…
(source: Observable ) => Observable 您可以从
rxjs/operators
下的多个位置拉入所需的任何运算rxjs/operators
(复数!)。 还建议直接使用所需的Observable创建方法,如下所示,范围为:import { range } from >'rxjs/observable/range'; import { map, filter, scan } from >'rxjs/operators'; const source$ = range(0, 10); source$.pipe( filter(x => x % 2 === 0), map(x => x + x), scan((acc, x) => acc + x, 0) ) .subscribe(x => console.log(x))
尽管这听起来很复杂,但从根本上讲,这意味着我们以前使用链式方法的地方:
source$
.operatorOne()
.operatorTwo()
.subscribe()
现在,我们应该从rxjs/operators
导入运算rxjs/operators
并使用.pipe()
方法来应用它们:
source$
.pipe(
operatorOne(),
operatorTwo()
)
.subscribe()
管道运算符的主要好处是:
.pipe()
方法将对代码的影响降至最低。
我们的应用程序中有两项需要重构: ApiService
和TodosComponent
。
首先,让我们打开src/app/api.service.ts
来更新我们的ApiService
:
// import operators from rxjs/operators
import { map } from 'rxjs/operators';
// ...
@Injectable()
export class ApiService {
constructor(
private http: HttpClient,
private session: SessionService
) {
}
// ...
// update .map() to .pipe(map())
public getAllTodos(): Observable {
const options = this.getRequestOptions();
return this.http
.get(API_URL + '/todos', options)
.pipe(
map(response => {
const todos = response;
return todos.map((todo) => new Todo(todo));
})
)
.catch(this.handleError);
}
// update .map() to .pipe(map())
public createTodo(todo: Todo): Observable {
const options = this.getRequestOptions();
return this.http
.post(API_URL + '/todos', todo, options)
.pipe(
map(response => {
return new Todo(response);
})
)
.catch(this.handleError);
}
// update .map() to .pipe(map())
public getTodoById(todoId: number): Observable {
const options = this.getRequestOptions();
return this.http
.get(API_URL + '/todos/' + todoId, options)
.pipe(
map(response => {
return new Todo(response);
})
)
.catch(this.handleError);
}
// update .map() to .pipe(map())
public updateTodo(todo: Todo): Observable {
const options = this.getRequestOptions();
return this.http
.put(API_URL + '/todos/' + todo.id, todo, options)
.pipe(
map(response => {
return new Todo(response);
})
)
.catch(this.handleError);
}
}
我们从rxjs/operators
导入map
管道运算rxjs/operators
并将所有出现的事件从.map(fn)
更新为.pipe(map(fn))
。
接下来,让我们打开src/app/todos/todos.component.ts
将相同的更改应用于TodosComponent
:
// import operators from rxjs/operators
import { map } from 'rxjs/operators';
// ...
@Component({
selector: 'app-todos',
templateUrl: './todos.component.html',
styleUrls: ['./todos.component.css']
})
export class TodosComponent implements OnInit {
// ...
// update .map() to .pipe(map())
public ngOnInit() {
this.route.data
.pipe(
map((data) => data['todos'])
)
.subscribe(
(todos) => {
this.todos = todos;
}
);
}
// ...
}
同样,我们从rxjs/operators
导入map
管道运算rxjs/operators
并将.map(fn)
更新为.pipe(map(fn))
。
而已! 正如Angular Update Guide所指示的那样,我们应用程序中的链式运算符已被可管道运算符取代。
如果将浏览器导航到http://localhost:4200
, http://localhost:4200
看到我们的应用程序仍然可以正常运行。
为了验证我们是否确实在运行Angular 5,我们可以打开元素检查器:
Angular将ng-version
属性添加到app-root
并带有其运行版本的值。 我们看到ng-version="5.2.9"
,表明我们正在运行Angular 5.2.9。
任务完成! 我们的应用程序已成功升级到Angular 5.2.9。
我们涵盖了很多内容,所以让我们回顾一下我们学到的东西。
在第一篇文章中 ,我们学习了如何:
Todo
类来代表单个Todo
TodoDataService
服务以创建,更新和删除待办事项 AppComponent
组件显示用户界面 在第二篇文章中 ,我们将AppComponent
重构为将其大部分工作委托给:
TodoListComponent
以显示TodoListComponent
列表 TodoListItemComponent
以显示单个待办事项 TodoListHeaderComponent
来创建一个新的待办事项 TodoListFooterComponent
来显示还剩下多少个TodoListFooterComponent
。 在第三篇文章中 ,我们学习了如何:
ApiService
与REST API通信 TodoDataService
以使用新的ApiService
AppComponent
以处理异步API调用 ApiMockService
以避免在运行单元测试时进行真正的HTTP调用。 在第四篇文章中 ,我们了解到:
在第五篇文章中,我们了解到:
AuthService
来实现身份验证逻辑 SessionService
来存储会话数据 在有关如何更新Angular的这篇文章中,我们了解到:
HttpClientModule
替换HttpModule
ng-version
属性如何让我们验证我们正在运行的Angular版本。 在即将发布的版本中,Angular CLI将引入ng update
命令来帮助更新Angular应用程序。 一旦有更多详细信息可用,我们将为您提供有关此新命令如何使我们的生活更加轻松的后续文章。
在此之前,您可以将本文用作如何将Angular应用程序更新到最新版本的指南。
这篇文章中的所有代码都可以在GitHub上找到 。
有一个很棒的!
From: https://www.sitepoint.com/update-angular-projects/