在 15 分钟内在您的 Angular 应用中实现 Google 登录(OAuth)🚀
KittyGramAuth
序言:我和我亲爱的朋友 Martina(她也是 Angular 和 Web 技术的 GDE)都非常喜欢猫。😻 有一天,我们正在讨论合作的事情,突然冒出一个非常疯狂(但很有趣)的想法。那就是创建 KittyGram :一个超极简的 Instagram 克隆版,只允许上传猫咪🐱的照片 。💡 很可爱,不是吗?
这是我们将创建KittyGram 的 系列文章中的一篇文章, 我们将使用世界上最好的前端 Web 框架(咳咳 Angular)来实现 KittyGram。
就本文的范围而言,我们将主要实现 Google 登录和路由。
本文要求您对 Angular、Angular Material 和 Firebase 有基本的了解。
实际上,我有一个 YouTube 播放列表 ,您可以参考有关 Firebase 的内容,以防万一。
话虽如此,本文面向的是经验水平参差不齐的读者。 为了以防万一你只对某个特定部分感兴趣,我还是在下面添加了一个 TL;DR; 👇🏻。
TL;DR;
不喜欢读书?😵
啊!你跟我一样。🤗 我也觉得光看文字很难理解。别担心!我为你准备了一个演示这个应用的视频,并附有说明。
VIDEO
这段视频来自 @PairAngular ,这是一个非常棒的 YouTube 频道,我和 @Martina 会在这个频道上制作更多类似上面的结对编程视频。喜欢吗?订阅 PairAngular YouTube 频道, 以便随时了解未来更多类似的课程。🙂
项目概述🔭
由于这是一个极简版 Instagram 克隆版,我们不会真正关注 Instagram 的具体方面/功能。我们只需要让用户能够:
通过 Google 登录并注销。
发布一张猫的图片 - 创建。
查看其他用户发布的猫图像并对其做出反应(任意次数) - FEED。
由于在本文中,我们仅关注实现 Google 登录,因此我们只会使用占位符图像来实现 CREATE 和 FEED 功能。
一旦这部分实现,它应该看起来并做出如下反应:
VIDEO
从上面的视频中您可以清楚地看到,该应用程序有不同的方面。
从 UI 角度来看,我们需要一个 CreateComponent
、一个 FeedComponent
、一个 HeaderComponent
和一个 ProfileCardComponent
。对于大多数这些组件,我们也将在这里使用 Angular Material。
从路由的角度来看,我们有一个 /create
路由,另一个 /feed
路由。我们也不想让未经授权的用户访问该 /create
路由。所以我们需要保护该 /create
路由。
你可能会问:“我们该如何实现 Google Sign-In 的想法?”处理 OAuth 流程、令牌生命周期以及与 Google API 的集成可能相当复杂。
但您不必担心独自管理所有这些事情。Google 已将所有这些复杂性隐藏起来,并将其公开为一项服务,我们只需付出很少的努力即可利用。这项服务名为 Firebase Authentication ,我们将在本应用中使用它。
VIDEO
好了!现在我们对整个应用程序及其各个部分有了大致的了解,让我们开始从头开始构建它吧。
设置 Firebase 项目🔥
要使用 Firebase 身份验证,我们需要设置一个 Firebase 项目。您需要一个 Firebase 帐户,只需使用 您的 Google 帐户登录 Firebase 控制台即可创建。
按照这个 1 分钟左右的短视频来设置 Firebase 项目:
VIDEO
复制并粘贴我们找到的这个配置。我们稍后会用到它。
太棒了!现在我们有了项目设置,让我们快速启用身份验证。请按照以下视频操作:
VIDEO
太棒了!现在我们已经完全设置好 Firebase 项目,并在其中启用了 Google 登录。现在让我们继续设置我们的 Angular 应用。
设置 Angular 应用
让我们从创建一个新的 Angular 应用开始。请确保您使用的是最新版本的 Angular CLI。
使用以下命令安装最新版本的 Angular CLI:
npm i -g @angular/cli@latest
安装完成后,您可以通过运行以下命令来验证您的版本 ng --version
:
太棒了!现在让我们运行以下命令创建一个新的 Angular 项目: ng new KittyGram
让我们导航到刚刚创建的项目文件夹:( cd KittyGram
在 Windows 上)
好了!现在,我们可以从 项目概述 部分的视频中清楚地看到,我们将使用 Angular Material 。因此,让我们设置我们的 Angular 应用以使用 Angular Material。
设置 AngularMaterial :
设置 @angular/material
使用:
ng add @angular/material
它可能会询问你一些问题,例如你想使用的主题,是否设置字体和动画。只需按照下面的截图回答即可。
完成后,我会稍微重构一下代码,以适应我通常喜欢的风格。顺便说一句,这只是我的个人意见,你其实不必这么做。
将主题路径从迁移 angular.json
到 styles.scss
原因: 考虑到我们已经有一个文件,我不希望 angular.json
被 CSS 文件污染 styles.scss
。所以我们可以直接导入这些 CSS 文件。
在 中搜索 deeppurple-amber.css angular.json
并删除其中的 URL。删除以下 URL:
"./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css",
重构之前,您应该能够找到两次此 URL 的出现。重构之后, styles
您的数组 angular.json
应该如下所示:
{
"..." : "..." ,
"projects" : {
"KittyGram" : {
"..." : "..." ,
"architect" : {
"build" : {
"..." : "..." ,
"options" : {
...
"styles" : [
"src/styles.scss"
],
...
},
...
},
...
"test" : {
...
"options" : {
...
"styles" : [
"src/styles.scss"
],
...
}
},
...
}
}
},
...
}
Enter fullscreen mode
Exit fullscreen mode
现在将此 URL 移动到 styles.scss
。更改后,您的 URL styles.scss
应该如下所示:
/* You can add global styles to this file, and also import other style files */
html ,
body {
height : 100% ;
}
body {
margin : 0 ;
font-family : Roboto , "Helvetica Neue" , sans-serif ;
}
@import "~@angular/material/prebuilt-themes/deeppurple-amber.css" ;
Enter fullscreen mode
Exit fullscreen mode
创建一个 AppMaterialModule
:
我们已经在之前的一篇 文章 中讨论过这个问题了。所以我就不多说了。我只需要从 BrowserAnimationsModule
这里导出即可。
因此我们的 AppMaterialModule
看起来是这样的:
app-material.module.ts
import { BrowserAnimationsModule } from ' @angular/platform-browser/animations ' ;
import { MatButtonModule } from ' @angular/material/button ' ;
import { MatDividerModule } from ' @angular/material/divider ' ;
import { MatIconModule } from ' @angular/material/icon ' ;
import { MatMenuModule } from ' @angular/material/menu ' ;
import { MatToolbarModule } from ' @angular/material/toolbar ' ;
import { MatTooltipModule } from ' @angular/material/tooltip ' ;
import { MatSnackBarModule } from ' @angular/material/snack-bar ' ;
import { NgModule } from ' @angular/core ' ;
@ NgModule ({
exports : [
BrowserAnimationsModule ,
MatButtonModule ,
MatDividerModule ,
MatIconModule ,
MatMenuModule ,
MatToolbarModule ,
MatTooltipModule ,
MatSnackBarModule ,
],
})
export class AppMaterialModule {}
Enter fullscreen mode
Exit fullscreen mode
下一步是设置 AngularFire
。
设置 AngularFire :
我们需要在 Angular 应用中使用 Firebase 来启用登录功能。Angular 提供了一个名为 的官方 Firebase SDK, @angular/fire
我们可以使用它来实现这一点。接下来我们来操作一下。 @angular/fire
使用以下命令进行设置:
ng add @angular/fire
它可能会询问您一些问题,例如是否需要授予您收集用于分析、cli 使用情况和错误报告信息等的使用数据的权限。除此之外,它还可能会要求您粘贴授权码,然后它会打开一个弹出屏幕,让您登录到您用于注册 Firebase 的 Gmail 帐户。
然后,根据您是否能够成功粘贴授权码,它可能会要求您运行 firebase login --reauth
以为您执行此操作。
在成功的情况下,它可能会要求你从 Firebase 控制台上创建的 Firebase 项目列表中选择你的 Firebase 应用。像这样:
我们还需要 firebase
JavaScript SDK。因此,让我们也使用以下命令安装它:
npm i firebase
完成后,我们会添加一些东西,让它符合我通常喜欢的风格。我们首先创建一个 AppFirebaseModule
创建一个 AppFirebaseModule
:
我将运行 ng g m app-firebase
并生成此模块。它将被创建在一个单独的文件夹中。因此,我将把它移出该文件夹并删除空文件夹。
现在,当我们设置 Firebase 项目时,我们也将配置复制到了某个地方。我们现在需要它。让我们将该配置存储在名为 firebase
和 文件的 environments.ts
对象中 environments.prod.ts
:
environment.ts
export const environment = {
production : false ,
firebase : {
apiKey : ' YOUR apiKey HERE ' ,
authDomain : ' YOUR authDomain HERE ' ,
databaseURL : ' YOUR databaseURL HERE ' ,
projectId : ' YOUR projectId HERE ' ,
storageBucket : ' YOUR storageBucket HERE ' ,
messagingSenderId : ' YOUR messagingSenderId HERE ' ,
appId : ' YOUR appId HERE ' ,
measurementId : ' YOUR measurementId HERE ' ,
},
};
Enter fullscreen mode
Exit fullscreen mode
注意: 此配置应保密,不应公开共享。
现在 AppFirebaseModule
,让我们设置 AngularFire
Google 登录所需的模块。
因此,我们需要 AngularFireModule
用它来初始化 Angular 应用中的 Firebase 项目。此外,我们还需要 AngularFireAuthModule
包含登录和注销所需的所有辅助函数的。
我们调用 initializeApp
our 上的方法 AngularFireModule
,并将存储在 const 中的配置传递给它 environment
。然后我们可以从这里暴露 AngularFireModule
。 AngularFireAuthModule
在代码中,它看起来像这样:
app-firebase.module.ts
import { AngularFireAuthModule } from ' @angular/fire/auth ' ;
import { AngularFireModule } from ' @angular/fire ' ;
import { NgModule } from ' @angular/core ' ;
import { environment } from ' ../environments/environment ' ;
@ NgModule ({
imports : [ AngularFireModule . initializeApp ( environment . firebase )],
exports : [ AngularFireModule , AngularFireAuthModule ],
})
export class AppFirebaseModule {}
Enter fullscreen mode
Exit fullscreen mode
AngularMaterial
现在我们已经完成了和的 设置 AngularFire
,我们现在可以在我们的中导入 AppFirebaseModule
和 AppMaterialModule
模块 AppModule
:
...
import { AppFirebaseModule } from ' ./app-firebase.module ' ;
import { AppMaterialModule } from ' ./app-material.module ' ;
...
@ NgModule ({
...
imports : [
...
AppFirebaseModule ,
AppMaterialModule ,
...
],
...
})
export class AppModule {}
Enter fullscreen mode
Exit fullscreen mode
实现 Angular 应用
实现 Angular 应用非常简单。就本文而言,我们需要四个组件:
这 HeaderComponent
将是我们的主导航栏。它将包含登录、创建帖子和显示用户个人资料卡片的按钮。这些按钮将根据用户是否登录有条件地显示。
个人资料卡组件,其中包含有关用户的一些详细信息和一个注销按钮。
针对 FeedCompoent
路线的和 针对路线的 /feed
类似 。 CreateComponent
/feed
让我们使用以下命令创建它们:
ng g c components/create --module=app && ng g c components/feed --module=app && ng g c components/profile-card --module=app && ng g c components/header --module=app
此命令将在名为的文件夹中为我们创建这 4 个组件 components
。
我们只需要 CreateComponent
和 的占位符图片 FeedComponent
。我从 Katerina Limpitsouni 的 Undraw 下载了这些图片。我们可以将它们存储在 文件夹中,并在以下组件模板中链接它们: assets
create.component.html
:
<img alt= "Create Post" class= "placeholder-image" src= "/assets/create.png" />
Enter fullscreen mode
Exit fullscreen mode
feed.component.html
:
<img alt= "Feed" class= "placeholder-image" src= "/assets/feed.png" >
Enter fullscreen mode
Exit fullscreen mode
在实现剩下的两个组件之前,我们需要一种方式来实现用户的登录和注销。 AngularFire
让这一切变得非常简单。它有一个 AngularFireAuthModule
模块,可以暴露 AngularFireAuth
我们可以作为依赖注入的服务。然后,我们可以调用它的方法来实现登录和注销。
除此之外,此服务还公开了一个 authState
Observable,其中包含与当前登录用户相关的状态数据。因此,我们可以实现一个服务,它公开一些方法来帮助登录和注销,以及 user$
Observable。就像这样简单:
auth.service.ts
:
import { AngularFireAuth } from ' @angular/fire/auth ' ;
import { auth } from ' firebase/app ' ;
import { BehaviorSubject , Observable , from } from ' rxjs ' ;
import { Injectable } from ' @angular/core ' ;
import { switchMap } from ' rxjs/operators ' ;
@ Injectable ({
providedIn : ' root ' ,
})
export class AuthService {
private user : BehaviorSubject <
Observable < firebase . User >
> = new BehaviorSubject < Observable < firebase . User >> ( null );
user$ = this . user
. asObservable ()
. pipe ( switchMap (( user : Observable < firebase . User > ) => user ));
constructor ( private afAuth : AngularFireAuth ) {
this . user . next ( this . afAuth . authState );
}
loginViaGoogle (): Observable < auth . UserCredential > {
return from ( this . afAuth . signInWithPopup ( new auth . GoogleAuthProvider ()));
}
logout (): Observable < void > {
return from ( this . afAuth . signOut ());
}
}
Enter fullscreen mode
Exit fullscreen mode
这里的实现非常简单。如果有什么不明白的地方,欢迎在下面留言。
太棒了。现在我们有了一个可以作为依赖项注入到我们的 中的服务 HeaderComponent
。 它将 HeaderComponent
利用这些方法和 user$
Observable 在导航栏上显示相应的选项。我们还将利用从 和方法 subscribe
返回的 Observable, 将一些警报显示为 Snackbar。 loginViaGoogle
logout
既然我们要这么做,我们将使用 take
操作员来完成,这样我们就不必 unsubscribe
手动操作了。
header.component.ts
:
import { catchError , take } from ' rxjs/operators ' ;
import { Component } from ' @angular/core ' ;
import { EMPTY , Observable , of } from ' rxjs ' ;
import { MatSnackBar } from ' @angular/material/snack-bar ' ;
import { Router } from ' @angular/router ' ;
import { AuthService } from ' ../../services/auth/auth.service ' ;
import { FEED } from ' ./../../consts/routes.const ' ;
@ Component ({
selector : ' app-header ' ,
templateUrl : ' ./header.component.html ' ,
styleUrls : [ ' ./header.component.scss ' ],
})
export class HeaderComponent {
user$ : Observable < firebase . User > = this . auth . user$ ;
constructor (
private readonly auth : AuthService ,
private readonly snackBar : MatSnackBar ,
private readonly router : Router ,
) {}
login () {
this . auth
. loginViaGoogle ()
. pipe (
take ( 1 ),
catchError (( error ) => {
this . snackBar . open ( ` ${ error . message } 😢` , ' Close ' , {
duration : 4000 ,
});
return EMPTY ;
}),
)
. subscribe (
( response ) =>
response &&
this . snackBar . open (
`Oh! You're here. I demand that you feed me, Hooman. 😾` ,
' Close ' ,
{
duration : 4000 ,
},
),
);
}
logout () {
this . auth
. logout ()
. pipe ( take ( 1 ))
. subscribe (( response ) => {
this . router . navigate ([ `/ ${ FEED } ` ]);
this . snackBar . open ( ' Come back soon with treats! 😿 ' , ' Close ' , {
duration : 4000 ,
});
});
}
}
Enter fullscreen mode
Exit fullscreen mode
/feed
实现也相当简单。用户退出后, 我们还会明确地将用户导航到该路由。
注意: 在实际项目中,我还会将小吃店消息移动到 const 文件中。
对于模板,如果用户已登录,我们将显示 ProfileCardComponent
和“创建”图标。否则,我们将向用户显示“登录”图标。
header.component.html
:
<mat-toolbar color= "primary" >
<mat-toolbar-row>
<button
mat-button
routerLink= "/feed"
matTooltip= "🐱Gram Home" >
🐱Gram
</button>
<span class= "spacer" ></span>
<ng-container *ngIf= "user$ | async as user; else loginIcon" >
<button
mat-icon-button
routerLink= "/create"
matTooltip= "Post a cute 🐱"
>
<mat-icon
aria-hidden= "false"
aria-label= "Post a cute 🐱"
>
cloud_upload
</mat-icon>
</button>
<app-profile-card
[user]= "user"
(logoutClick)= "logout()" >
</app-profile-card>
</ng-container>
<ng-template #loginIcon >
<button
mat-icon-button
(click)= "login()"
matTooltip= "Login"
>
<mat-icon
aria-hidden= "false"
aria-label= "Login"
>
fingerprint
</mat-icon>
</button>
</ng-template>
</mat-toolbar-row>
</mat-toolbar>
Enter fullscreen mode
Exit fullscreen mode
正如您所见,我们使用了 ProfileCardComponent
作为子组件。这是一个 展示组件 ,它接受 user
作为 @Input
属性,并在用户点击注销按钮时发出事件 logoutClick
@Output
。
所以我们的 ProfileCardComponent
样式看起来是这样的:
profile-card.component.ts
:
import { Component , EventEmitter , Input , Output } from ' @angular/core ' ;
@ Component ({
selector : ' app-profile-card ' ,
templateUrl : ' ./profile-card.component.html ' ,
styleUrls : [ ' ./profile-card.component.scss ' ],
})
export class ProfileCardComponent {
@ Input () user : firebase . User ;
@ Output () logoutClick : EventEmitter < null > = new EventEmitter < null > ();
logout () {
this . logoutClick . emit ();
}
}
Enter fullscreen mode
Exit fullscreen mode
模板看起来是这样的:
profile-card.component.html
:
<button
mat-mini-fab
color= "primary"
class= "avatar-button"
[matMenuTriggerFor]= "beforeMenu"
>
<img
[alt]= "user.displayName"
[src]= "user.photoURL"
class= "avatar" />
</button>
<mat-menu #beforeMenu =" matMenu " xPosition= "before" >
<div class= "profile-card" >
<img
[alt]= "user.displayName"
[src]= "user.photoURL"
class= "big-avatar" />
<h4> {{ user.displayName }}</h4>
<p> {{ user.email }}</p>
<mat-divider></mat-divider>
<button mat-stroked-button (click)= "logout()" >
Sign Out
</button>
<mat-divider></mat-divider>
<p class= "profile-footer" >
Made with 😻 by <a href= "https://twitter.com/SiddAjmera" > @SiddAjmera</a>
</p>
</div>
</mat-menu>
Enter fullscreen mode
Exit fullscreen mode
现在我们已经准备好了所有模块、组件和服务。让我们通过路由将它们整合在一起。
通过路由将所有内容整合在一起
为此,我们需要通过配置来设置路由 AppRoutingModule
。我们已经知道有两条路线:
该 /feed
路线将引导用户到 FeedComponent
。
该 /create
路线将引导用户到 CreateComponent
。
但是该 /create
路由不应该被未经授权的用户访问。如果没有 AngularFire
,我们会实现一个 CanActivate
Guard 来实现这一点。但多亏了 @angular/fire
,我们有一个 AngularFireAuthGuard
可以使用 redirectUnauthorizedTo
其辅助函数进行配置的守卫。配置它使我们能够告诉 Angular 将未经授权的用户导航到哪里。
所有这些在代码中看起来是这样的:
app-routing.module.ts
:
import {
AngularFireAuthGuard ,
redirectUnauthorizedTo ,
} from ' @angular/fire/auth-guard ' ;
import { NgModule } from ' @angular/core ' ;
import { Routes , RouterModule } from ' @angular/router ' ;
import { BASE , CREATE , FEED } from ' ./consts/routes.const ' ;
import { CreateComponent } from ' ./components/create/create.component ' ;
import { FeedComponent } from ' ./components/feed/feed.component ' ;
const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo ([ FEED ]);
const routes : Routes = [
{
path : BASE ,
redirectTo : `/ ${ FEED } ` ,
pathMatch : ' full ' ,
},
{
path : FEED ,
component : FeedComponent ,
},
{
path : CREATE ,
component : CreateComponent ,
canActivate : [ AngularFireAuthGuard ],
data : { authGuardPipe : redirectUnauthorizedToLogin },
},
];
@ NgModule ({
imports : [ RouterModule . forRoot ( routes )],
exports : [ RouterModule ],
})
export class AppRoutingModule {}
Enter fullscreen mode
Exit fullscreen mode
就这样!我们的 Angular 应用现在已经准备好支持 Google 登录和路由了。🎉✨
您可以在这里找到该项目的源代码:
这个存储库演示了我们在 KittyGram 中的身份验证和授权
KittyGramAuth
该项目是使用Angular CLI 版本 9.0.5生成的 。
开发服务器
运行 ng serve
开发服务器。导航到 http://localhost:4200/
。如果您更改任何源文件,应用程序将自动重新加载。
代码脚手架
运行 ng generate component component-name
生成新组件。您也可以使用 ng generate directive|pipe|service|class|guard|interface|enum|module
。
建造
运行 ng build
以构建项目。构建产物将存储在 dist/
目录中。请使用该 --prod
标志进行生产构建。
运行单元测试
运行以通过 Karma ng test
执行单元测试 。
运行端到端测试
运行以通过 Protractor ng e2e
执行端到端测试 。
进一步帮助
要获取有关 Angular CLI 的更多帮助,请使用 ng help
或查看 Angular CLI README 。
下一步👣
KittyGram 功能丰富,而这只是其中的一小部分。所有这些伟大的功能都源于一个不起眼的起点,就像我们刚刚构建的这个一样。在下一篇文章中,我们将实现 CreateComponent
一个 Reactive Form 表单。我们还将在其中实现 Firebase 存储 ,以便将图片上传到 Firebase 存储桶。等不及了?就在这儿:
结束语🎉
这篇文章就到此结束了。感谢你的阅读。希望你喜欢。
非常感谢 Martina Kraus 的校对以及与我合作完成这个项目。我也非常感谢 Akhil 和 Rajat 的 校对,以及他们提供的建设性反馈,使文章更加完善。
希望本文能让你学到一些关于 Angular 和 Firebase 的新知识。如果觉得有用,请点击🧡/🦄图标,并将其添加到你的阅读列表 (🔖)。也欢迎分享这篇文章给你刚接触 Angular 并想实现类似目标的朋友。
另外,别忘了在这里关注 Martina:
那么下次再见。👋🏻
图标来源: AngularIO Press Kit |登录由 Noun Project 的 lastspark 提供
文章来源:https://dev.to/angular/implement-google-sign-in-oauth-in-your-angular-app-in-under-15-minutes-1ebo