Merhaba,
Bu yazımda size Angular Guard yapısından ve kullanımından bahsedeceğim. Guard yapısı ile uygulamamızda yapmış olduğumuz sayfalar arası geçişlerimize kontrol mekanizması eklemiş oluyoruz. Hangi kullanıcı bu sayfaya erişebilecek, hangi aşamada sayfadan ayrılabilecek vb. Örneğin sisteme giriş yapan bir kullanıcı tüm sayfalara erişebilirken, giriş yapmayan kullanıcı sadece belirli sayfalarını görebiliyor.
Bu konumuz için Home, Profile sayfaları olan ve birde Login, Logout yapısı olan bir uygulama hazırlayalım ve profile sayfasına sadece login olan kullanıcı erişebilsin ve ayrıca profile sayfasındayken değişiklikleri kayıt etmeden sayfadan ayrılamasın.
Hazırlayacağımız uyglamamız bittiğinde aşağıdaki gibi klasör yapısına sahip olacaktır.
Uygulama klasör yapısı
src
├── app
│ ├── app-routing.module.ts
│ ├── app.component.html
│ ├── app.component.scss
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── components
│ │ ├── login
│ │ │ ├── login.component.html
│ │ │ ├── login.component.scss
│ │ │ └── login.component.ts
│ │ └── profile
│ │ ├── profile.component.html
│ │ ├── profile.component.scss
│ │ └── profile.component.ts
│ ├── guards
│ │ ├── can-activate.guard.ts
│ │ └── can-deactivate-profile.guard.ts
│ └── services
│ ├── auth.service.ts
│ └── data.service.ts
│
Proje oluşturma
İlk olarak projemizi aşağıdaki komut ile oluşturalım ve oluşturduğumuz proje klasörüne geçelim.
ng new angular-guard-usage
cd angular-guard-usage
Servisleri oluşturma
Şimdi ise yapacağımız işlemler için gerekli olan servislerimizi oluşturalım. İlk olarak services klasörünü aşağıdaki komut ile oluşturalım. Daha sonra bu klasör altında gerekli olan servislerimizi oluşturalım.
cd src/app
mkdir services
cd services
Auth servisini oluşturma
Yapacağımız kontroller için gerekli olan auth servisimizi aşağıdaki komut ile oluşturalım.
ng g s auth
Data servisini oluşturma
Kullanacağımız datalarımız için gerekli olan servisimizi aşağıdaki komutla oluşturalım.
ng g s data
Guard oluşturma
Benzer şekilde yine ilk önce guards klasörümüzü oluşturalım ve sonrasında ihtiyacımız olan guard servislerini bu klasör altında oluşturalım.
cd src/app
mkdir guards
cd guards
Kontrol için kullanacağımız ve belirttiğimiz alana erişim ya da oradan ayrılma işlemeni kontrol edeceğimiz active ve deactive guard servislerini oluşturalım.
can-activate guard oluşturma
Aşağıdaki komut ile can-activate guard servisimizi oluşturalım.
ng g guard can-activate
can-deactivate-profile guard oluşturma
Benzer şekilde aşağıdaki komut yardımı ile ihtiyacımız olan guard servisimizi oluşturalım.
ng g guard can-deactivate-profile
Component oluşturma
Hem servislerimiz hemde guard servislerimiz hazır olduğuna göre şimdi kullanacak olduğumuz componentlerimizi oluşturalım. Yine yaptığımız gibi ilk olarak components klasörümüzü oluşturalım ve bu klasör altında gerekli olan componentleri tanımlayalım.
cd src/app
mkdir components
cd components
login componenti oluşturma
Aşağıdaki komut ile login componentimizi oluşturalım.
ng g c login
profile componenti oluşturma
İhtiyacımız olan diğer component olan profil componentinide aşağıdaki komut ile oluşturalım.
ng g c profile
Artık projemizde ihtiyacımız olan tüm servisleri ve componentleri oluşturduk şimdi uygulama klasör yapısındaki sırayla tek tek component ve servislerimizin içeriğini güncelleyelim.
App
app-routing.module.ts içeriği
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LoginComponent } from './components/login/login.component';
import { ProfileComponent } from './components/profile/profile.component';
import { CanActivateGuard } from './guards/can-activate.guard';
import { CanDeactivateProfileGuard } from './guards/can-deactivate-profile.guard';
const routes: Routes = [
{ component: LoginComponent, path: 'login' },
{
component: ProfileComponent,
path: 'profile',
canActivate: [CanActivateGuard],
canDeactivate: [CanDeactivateProfileGuard],
},
{ path: '**', pathMatch: 'full', redirectTo: '' },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
app.component.html içeriği
<div class="toolbar" role="banner">
<img
width="40"
alt="Angular Logo"
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg=="
/>
<span>{{ title }}</span>
</div>
<div class="content" role="main">
<div class="card highlight-card card-small">
<svg
id="rocket"
xmlns="http://www.w3.org/2000/svg"
width="101.678"
height="101.678"
viewBox="0 0 101.678 101.678"
>
<g id="Group_83" data-name="Group 83" transform="translate(-141 -696)">
<circle
id="Ellipse_8"
data-name="Ellipse 8"
cx="50.839"
cy="50.839"
r="50.839"
transform="translate(141 696)"
fill="#dd0031"
/>
<g
id="Group_47"
data-name="Group 47"
transform="translate(165.185 720.185)"
>
<path
id="Path_33"
data-name="Path 33"
d="M3.4,42.615a3.084,3.084,0,0,0,3.553,3.553,21.419,21.419,0,0,0,12.215-6.107L9.511,30.4A21.419,21.419,0,0,0,3.4,42.615Z"
transform="translate(0.371 3.363)"
fill="#fff"
/>
<path
id="Path_34"
data-name="Path 34"
d="M53.3,3.221A3.09,3.09,0,0,0,50.081,0,48.227,48.227,0,0,0,18.322,13.437c-6-1.666-14.991-1.221-18.322,7.218A33.892,33.892,0,0,1,9.439,25.1l-.333.666a3.013,3.013,0,0,0,.555,3.553L23.985,43.641a2.9,2.9,0,0,0,3.553.555l.666-.333A33.892,33.892,0,0,1,32.647,53.3c8.55-3.664,8.884-12.326,7.218-18.322A48.227,48.227,0,0,0,53.3,3.221ZM34.424,9.772a6.439,6.439,0,1,1,9.106,9.106,6.368,6.368,0,0,1-9.106,0A6.467,6.467,0,0,1,34.424,9.772Z"
transform="translate(0 0.005)"
fill="#fff"
/>
</g>
</g>
</svg>
<span class="active-page">{{ activePage }}</span>
</div>
<div class="card-container">
<button class="card card-small" routerLink="">
<span>Home</span>
</button>
<button class="card card-small" routerLink="profile">
<span>Profile</span>
</button>
<button
*ngIf="!authService.isLoggedIn"
class="card card-small login"
routerLink="login"
>
<span>Login</span>
</button>
<button
*ngIf="authService.isLoggedIn"
class="card card-small logout"
(click)="logout()"
>
<span>Logout</span>
</button>
</div>
</div>
<router-outlet></router-outlet>
app.component.scss içeriği
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 8px 0;
}
p {
margin: 0;
}
.toolbar {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 60px;
display: flex;
align-items: center;
background-color: #1976d2;
color: white;
font-weight: 600;
}
.toolbar img {
margin: 0 16px;
}
.content {
display: flex;
margin: 82px auto 32px;
padding: 0 16px;
max-width: 960px;
flex-direction: column;
align-items: center;
}
.card-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
margin-top: 16px;
}
.card {
all: unset;
border-radius: 4px;
border: 1px solid #eee;
background-color: #fafafa;
height: 40px;
width: 200px;
margin: 0 8px 16px;
padding: 16px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
transition: all 0.2s ease-in-out;
line-height: 24px;
}
.card-container .card:not(:last-child) {
margin-right: 0;
}
.card.card-small {
height: 16px;
width: 168px;
}
.card-container .card:not(.highlight-card) {
cursor: pointer;
}
.card-container .card:not(.highlight-card):hover {
transform: translateY(-3px);
box-shadow: 0 4px 17px rgba(0, 0, 0, 0.35);
}
.card.highlight-card {
background-color: #1976d2;
color: white;
font-weight: 600;
border: none;
width: auto;
min-width: 30%;
position: relative;
}
.card.card.highlight-card span {
margin-left: 60px;
}
svg#rocket {
width: 80px;
position: absolute;
left: -10px;
top: -24px;
}
svg#rocket-smoke {
height: calc(100vh - 95px);
position: absolute;
top: 10px;
right: 180px;
z-index: -10;
}
.active-page {
text-transform: capitalize;
}
.login {
color: green;
font-weight: bold;
}
.logout {
color: red;
font-weight: bold;
}
app.component.ts içeriği
import { Component, OnInit, VERSION } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { AuthService } from './services/auth.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {
public title = `Angular ${VERSION.major} Guard usage`;
public activePage = 'Home';
constructor(public authService: AuthService, private router: Router) {}
public ngOnInit(): void {
this.router.events.subscribe((event) => {
if (event instanceof NavigationEnd) {
const path = event.url.split('/')[1];
this.activePage = path === '' ? 'home' : path;
}
});
}
public logout(): void {
this.authService.logout();
this.router.navigate(['.']);
}
}
app.module.ts içeriği
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { LoginComponent } from './components/login/login.component';
import { ProfileComponent } from './components/profile/profile.component';
@NgModule({
declarations: [AppComponent, LoginComponent, ProfileComponent],
imports: [BrowserModule, AppRoutingModule],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
Login
login.component.html içeriği
<p>{{ loginContent }}</p>
login.component.scss içeriği
p{
display: flex;
justify-content: center;
}
login.component.ts içeriği
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { delay } from 'rxjs';
import { AuthService } from 'src/app/services/auth.service';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss'],
})
export class LoginComponent implements OnInit {
public loginContent: string = '';
constructor(private authService: AuthService, private router: Router) {}
public ngOnInit(): void {
this.loginContent = 'Logging in...';
setTimeout(() => {
this.authService.login();
this.loginContent = '';
this.router.navigate(['.']);
}, 700);
}
}
Profile
profile.component.html içeriği
<p>profile works!</p>
<div>
<button (click)="saveChanges()">Save changes</button>
<button (click)="rollbackChanges()">Rollback changes</button>
</div>
profile.component.scss içeriği
p,
div {
display: flex;
justify-content: center;
}
profile.component.ts içeriği
import { Component } from '@angular/core';
import { DataService } from 'src/app/services/data.service';
@Component({
selector: 'app-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.scss'],
})
export class ProfileComponent {
public isSaved: boolean = false;
constructor(private dataService: DataService) {}
public saveChanges(): void {
this.dataService.saveChanges();
this.isSaved = true;
}
public rollbackChanges(): void {
this.dataService.rollbackChanges();
this.isSaved = false;
}
}
CanActivate
can-activate.guard.ts içeriği
import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
import { AuthService } from '../services/auth.service';
@Injectable({ providedIn: 'root' })
export class CanActivateGuard implements CanActivate {
constructor(private authService: AuthService) {}
public canActivate(): boolean {
if (!this.authService.isLoggedIn) {
alert('You should login first...');
return false;
}
return true;
}
}
CanDeactivate
can-deactivate-profile.guard.ts içeriği
import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';
import { ProfileComponent } from '../components/profile/profile.component';
import { DataService } from '../services/data.service';
@Injectable({
providedIn: 'root',
})
export class CanDeactivateProfileGuard
implements CanDeactivate<ProfileComponent>
{
constructor(private dataService: DataService) {}
public canDeactivate(component: ProfileComponent): boolean {
if (!this.dataService.isSaved || !component.isSaved) {
alert('You should save your changes...');
return false;
}
return true;
}
}
Auth
auth.service.ts içeriği
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class AuthService {
private _isLoggedIn: boolean = false;
public get isLoggedIn(): boolean {
return this._isLoggedIn;
}
public login(): void {
this._isLoggedIn = true;
}
public logout(): void {
this._isLoggedIn = false;
}
}
Data
data.service.ts içeriği
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class DataService {
private _isSavedChanges: boolean = false;
public get isSaved(): boolean {
return this._isSavedChanges;
}
public saveChanges(): void {
this._isSavedChanges = true;
}
public rollbackChanges(): void {
this._isSavedChanges = false;
}
}
Şimdi projemizde kullandığımız tüm component servis ve guard içeriklerini güncellediğimize göre artık guard kullanımında yaptığımız tanımları ve kontrollerini ele alalım. Bizim örneğinimizde bir sayfaya erişebilmek ve sayfadan ayrılma işlemlerini kontrol etmek için iki guard hazırladık ve bu guardları ilgili route tanımındaki canActivate ve canDeactivate bilgileri içerisine ekledik. Daha sonra CanActivateGuard tanımı içinde sayfaya erişmek için gerekli olan kontrollerimizi yazdık ve son olarak da CanDeactivateProfileGuard içinde ise sayfadan ayrılma işlemininin kontrolünü yazdık.
Kaynak kod ve Demo
Sizde kendinize göre bir guard yazmak ya da bu örnekte yapılan düzenlemeleri özelleştirmek için aşağıdaki GitHub adresine, çalışan uygulama için Demo sayfasına bakabilirsiniz.