ساخت سیستم ثبت نام و ورود کاربر در انگولار ۷
در این بخش چگونگی ساخت یک سیستم ثبت نام و ورود کاربر ساده با استفاده از Angular 7، TypeScript و webpack 4 را بررسی خواهیم کرد.
ساختار پروژه
ساختا پروژه مربوط به این آموزش بر اساس راهنمای سایت رسمی انگولار (Angular Style Guide) تنظیم شده است. به ازای هر بخشی که به پروژه اضافه می شود، یه فولدر ایجاد می کنیم (مانند home، login و register). سایر بخش های مشترک (مانند services، models، guards، Components و helpers) در داخل فولدرهایی با پیشوند “_” قرار می گیرند. در زیر می توانید ساختار مربوط به فایل های پروژه آموزشی را مشاهده کنید:
src app _components alert.component.html alert.component.ts index.ts _guards auth.guard.ts index.ts _helpers error.interceptor.ts fake-backend.ts jwt.interceptor.ts index.ts _models user.ts index.ts _services alert.service.ts authentication.service.ts user.service.ts index.ts home home.component.html home.component.ts index.ts login login.component.html login.component.ts index.ts register register.component.html register.component.ts index.ts app.component.html app.component.ts app.module.ts app.routing.ts index.html main.ts polyfills.ts typings.d.ts package.json tsconfig.json webpack.config.js
خروجی نهایی پروژه
خروجی فرم لاگین
خروجی قرم ثبت نام
قالب کامپوننت Alert
مسیر: /src/app/_components/alert.component.html
کامپوننت Alert شامل کدهای HTML است که برای نمایش پیام های alert در بالای صفحه استفاده می شوند.
<div *ngIf="message" [ngClass]="{ 'alert': message, 'alert-success': message.type === 'success', 'alert-danger': message.type === 'error' }">{{message.text}}</div>
کامپوننت Alert
مسیر: /src/app/_components/alert.component.ts
کامپوننت alert هر زمان که سرویس alert هشداری دریافت کند، پیامی را به قالب ارسال می کند. این کار با subscribe کردن به متد getMessage() مربوط به سرویس alert که یک Observable باز میگرداند، انجام می شود.
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Subscription } from 'rxjs'; import { AlertService } from '@/_services'; @Component({ selector: 'alert', templateUrl: 'alert.component.html' }) export class AlertComponent implements OnInit, OnDestroy { private subscription: Subscription; message: any; constructor(private alertService: AlertService) { } ngOnInit() { this.subscription = this.alertService.getMessage().subscribe(message => { this.message = message; }); } ngOnDestroy() { this.subscription.unsubscribe(); } }
Auth Guard در انگولار ۷
مسیر: /src/app/_guards/auth.guard.ts
auth guard در انگولار به منظور جلوگیری از دسترسی غیرمجاز کاربران به مسیرها استفاده می شود. در این مثال auth guard برای محافظت کردن از صفحه home استفاده می شود.
import { Injectable } from '@angular/core'; import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; import { AuthenticationService } from '@/_services'; @Injectable({ providedIn: 'root' }) export class AuthGuard implements CanActivate { constructor( private router: Router, private authenticationService: AuthenticationService ) {} canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { const currentUser = this.authenticationService.currentUserValue; if (currentUser) { // authorised so return true return true; } // not logged in so redirect to login page with the return url this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }}); return false; } }
رهگیری خطای Http
مسیر: /src/app/_helpers/error.interceptor.ts
Error Interceptor پاسخ های http دریافتی از API را بررسی می کند تا ببیند خطایی رخ داده است که خیر. اگر پاسخ ۴۰۱ (به معنی احراز هویت نشده) وجود داشته باشد، کاربر به طور خودکار از برنامه خارج (logout) می شود. سایر خطاها به صورت یک alert به کاربر نمایش داده می شوند. با گسترش کلاس HttpInterceptor می توانید یک رهگیر سفارشی (Custom Interceptor) ایجاد کنید تا خطاهای دریافتی از سرور را مدیریت کنید.
interceptor های http به request pipeline موجود در بخش providers فایل app.module.ts اضافه می شوند.
import { Injectable } from '@angular/core'; import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http'; import { Observable, throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { AuthenticationService } from '@/_services'; @Injectable() export class ErrorInterceptor implements HttpInterceptor { constructor(private authenticationService: AuthenticationService) {} intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return next.handle(request).pipe(catchError(err => { if (err.status === 401) { // auto logout if 401 response returned from api this.authenticationService.logout(); location.reload(true); } const error = err.error.message || err.statusText; return throwError(error); })) } }
Fake Backend Provider در انگولار ۷
مسیر: /src/app/_helpers/fake-backend.ts
Fake Backend Provider امکان اجرای مثال بدون یک backend یا backendless را فراهم می کند. در این حالت از local storage برای ذخیره سازی اطلاعات کاربرانی که ثبت نام می کنند، استفاده می شود. به عبارت دیگر با استفاده از این سرویس می توانید API و دیتابیس اصلی را شبیه سازی کنید.
با گسترش کلاس HttpInterceptor می توانید یک رهگیر سفارشی (Custom Interceptor) برای تغییر درخواست های http قبل از ارسال به سرور ایجاد کنید. در این مثال FakeBackendInterceptor به جای ارسال برخی از درخواست های خاص به سرور واقعی، یک پاسخ جعلی برای آن ها ایجاد می کند.
import { Injectable } from '@angular/core'; import { HttpRequest, HttpResponse, HttpHandler, HttpEvent, HttpInterceptor, HTTP_INTERCEPTORS } from '@angular/common/http'; import { Observable, of, throwError } from 'rxjs'; import { delay, mergeMap, materialize, dematerialize } from 'rxjs/operators'; // array in local storage for registered users let users = JSON.parse(localStorage.getItem('users')) || []; @Injectable() export class FakeBackendInterceptor implements HttpInterceptor { intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { const { url, method, headers, body } = request; // wrap in delayed observable to simulate server api call return of(null) .pipe(mergeMap(handleRoute)) .pipe(materialize()) // call materialize and dematerialize to ensure delay even if an error is thrown (https://github.com/Reactive-Extensions/RxJS/issues/648) .pipe(delay(500)) .pipe(dematerialize()); function handleRoute() { switch (true) { case url.endsWith('/users/register') && method === 'POST': return register(); case url.endsWith('/users/authenticate') && method === 'POST': return authenticate(); case url.endsWith('/users') && method === 'GET': return getUsers(); case url.match(/\/users\/\d+$/) && method === 'GET': return getUserById(); case url.match(/\/users\/\d+$/) && method === 'DELETE': return deleteUser(); default: // pass through any requests not handled above return next.handle(request); } } // route functions function register() { const user = body if (users.find(x => x.username === user.username)) { return error('Username "' + user.username + '" is already taken') } user.id = users.length ? Math.max(...users.map(x => x.id)) + 1 : 1; users.push(user); localStorage.setItem('users', JSON.stringify(users)); return ok(); } function authenticate() { const { username, password } = body; const user = users.find(x => x.username === username && x.password === password); if (!user) return error('Username or password is incorrect'); return ok({ id: user.id, username: user.username, firstName: user.firstName, lastName: user.lastName, token: 'fake-jwt-token' }) } function getUsers() { if (!isLoggedIn()) return unauthorized(); return ok(users); } function getUserById() { if (!isLoggedIn()) return unauthorized(); const user = users.find(x => x.id == idFromUrl()); return ok(user); } function deleteUser() { if (!isLoggedIn()) return unauthorized(); users = users.filter(x => x.id !== idFromUrl()); localStorage.setItem('users', JSON.stringify(users)); return ok(); } // helper functions function ok(body?) { return of(new HttpResponse({ status: 200, body })) } function unauthorized() { return throwError({ status: 401, error: { message: 'Unauthorised' } }); } function error(message) { return throwError({ error: { message } }); } function isLoggedIn() { return headers.get('Authorization') === 'Bearer fake-jwt-token'; } function idFromUrl() { const urlParts = url.split('/'); return parseInt(urlParts[urlParts.length - 1]); } } } export const fakeBackendProvider = { // use fake backend in place of Http service for backend-less development provide: HTTP_INTERCEPTORS, useClass: FakeBackendInterceptor, multi: true };
JWT Interceptor در انگولار ۷
مسیر: /src/app/_helpers/jwt.interceptor.ts
JWT Interceptor درخواست های http برنامه را رهگیری می کند تا اگر کاربر لاگین کرده باشد، توکن احراز هویت JWT را به هدر درخواست اضافه کند. با گسترش کلاس HttpInterceptor می توانید یک رهگیر سفارشی (Custom Interceptor) برای تغییر درخواست های http قبل از ارسال به سرور ایجاد کنید.
interceptor های http به request pipeline موجود در بخش providers فایل app.module.ts اضافه می شوند.
import { Injectable } from '@angular/core'; import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http'; import { Observable } from 'rxjs'; import { AuthenticationService } from '@/_services'; @Injectable() export class JwtInterceptor implements HttpInterceptor { constructor(private authenticationService: AuthenticationService) {} intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { // add authorization header with jwt token if available let currentUser = this.authenticationService.currentUserValue; if (currentUser && currentUser.token) { request = request.clone({ setHeaders: { Authorization: `Bearer ${currentUser.token}` } }); } return next.handle(request); } }
ساخت مدل کاربر (User Model)
مسیر: /src/app/_models/user.ts
user model یک کلاس ساده است که پراپرتی های مربوط به کاربر در آن تعریف شده اند. پراپرتی token برای نگهداری توکن JWT که بعد از احراز هویت موفق از سرور گرفته شده است، استفاده می شود.
export class User { id: number; username: string; password: string; firstName: string; lastName: string; token: string; }
سرویس Alert
مسیر: /src/app/_services/alert.service.ts
سرویس alert کامپوننت های موجود در برنامه را قادر می سازد تا بتوانند با استفاده از کامپوننت alert پیام های هشدار را در بالا صفحه نمایش دهند. این سرویس متدهایی برای نمایش پیام های موفقیت/خطا و یک متد دیگر به نام getMessage دارد که یک Observable باز میگرداند. این Observable توسط کامپوننت alert برای فهمیدن اینکه چه زمان باید هشدار را نشان دهد، استفاده می شود.
import { Injectable } from '@angular/core'; import { Router, NavigationStart } from '@angular/router'; import { Observable, Subject } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class AlertService { private subject = new Subject<any>(); private keepAfterNavigationChange = false; constructor(private router: Router) { // clear alert message on route change router.events.subscribe(event => { if (event instanceof NavigationStart) { if (this.keepAfterNavigationChange) { // only keep for a single location change this.keepAfterNavigationChange = false; } else { // clear alert this.subject.next(); } } }); } success(message: string, keepAfterNavigationChange = false) { this.keepAfterNavigationChange = keepAfterNavigationChange; this.subject.next({ type: 'success', text: message }); } error(message: string, keepAfterNavigationChange = false) { this.keepAfterNavigationChange = keepAfterNavigationChange; this.subject.next({ type: 'error', text: message }); } getMessage(): Observable<any> { return this.subject.asObservable(); } }
سرویس Authentication
مسیر: /src/app/_services/authentication.service.ts
سرویس authentication برای ورود (login) و خروج از برنامه (logout) استفاده می شود. این سرویس برای ورود کاربران، اطلاعات آنها را به API ارسال می کند و در پاسخ دریافتی به دنبال توکن JWT می گردد. اگر توکن را پیدا کند، به معنی احراز هوبت موفق است و اطلاعات کاربر شامل توکن JWT در local storage ذخیره می شود.
از آنجا که اطلاعات ورود کاربر در local storage ذخیره می شود، هر بار که کاربر صفحه را refresh کند یا مرورگر را ببند و دوباره باز کند، نیازی به ورود دوباره نخواهد داشت. اگر می خواهید که این گونه نباشد، می توانید اطلاعات را در جایی که پایداری کمتری دارد مثل session storage ذخیره کنید که در صورت بسته شده مرورگر اطلاعات ذخیره شده از بین می روند.
سرویس authentication دو پراپرتی را برای دسترسی به اطلاعات کاربری که وارد سیستم شده است، ارائه می دهد. پراپرتی currentUser که یک observable است و می تواند زمانی که بخواهیم یک کامپوننت را در هنگام ورود و خروج کاربر بروز کنیم، استفاده شود. برای مثال در app.component.ts می توانیم بر اساس ورود/خروج کاربر منوی سایت را نمایش داده یا مخفی کنیم. پراپرتی currentUserValue زمانی مورد استفاده قرار می گیرد که بخواهید اطلاعات کاربری که لاگین شده است را بگیرید اما نیازی به بروز رسانی واکنشی در هنگام تغییر آن ندارید. به عنوان مثال در auth.guard.ts که دسترسی به مسیرها را بررسی می کند، با استفاده از این پراپرتی می توان بررسی کرد که آیا کاربر لاگین شده است یا خیر.
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { BehaviorSubject, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { User } from '@/_models'; @Injectable({ providedIn: 'root' }) export class AuthenticationService { private currentUserSubject: BehaviorSubject<User>; public currentUser: Observable<User>; constructor(private http: HttpClient) { this.currentUserSubject = new BehaviorSubject<User>(JSON.parse(localStorage.getItem('currentUser'))); this.currentUser = this.currentUserSubject.asObservable(); } public get currentUserValue(): User { return this.currentUserSubject.value; } login(username: string, password: string) { return this.http.post<any>(`${config.apiUrl}/users/authenticate`, { username, password }) .pipe(map(user => { // login successful if there's a jwt token in the response if (user && user.token) { // store user details and jwt token in local storage to keep user logged in between page refreshes localStorage.setItem('currentUser', JSON.stringify(user)); this.currentUserSubject.next(user); } return user; })); } logout() { // remove user from local storage to log user out localStorage.removeItem('currentUser'); this.currentUserSubject.next(null); } }
سرویس User
مسیر: /src/app/_services/user.service.ts
سرویس user شامل متدهای استاندارد CRUD (Create, read, update and delete) به منظور مدیریت کاربران است. این سرویس به عنوان یک رابط بین برنامه انگولار و API عمل می کند.
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { User } from '@/_models'; @Injectable({ providedIn: 'root' }) export class UserService { constructor(private http: HttpClient) { } getAll() { return this.http.get<User[]>(`${config.apiUrl}/users`); } getById(id: number) { return this.http.get(`${config.apiUrl}/users/${id}`); } register(user: User) { return this.http.post(`${config.apiUrl}/users/register`, user); } update(user: User) { return this.http.put(`${config.apiUrl}/users/${user.id}`, user); } delete(id: number) { return this.http.delete(`${config.apiUrl}/users/${id}`); } }
قالب کامپوننت Home
مسیر: /src/app/home/home.component.html
قالب کامپوننت home شامل کدهای html و angular است که یک پیام خوش آمدگویی را به همراه لیست کاربران و یک لینک delete برای هر کاربر نمایش می دهد.
<h1>Hi {{currentUser.firstName}}!</h1> <p>You're logged in with Angular 7!!</p> <h3>All registered users:</h3> <ul> <li *ngFor="let user of users"> {{user.username}} ({{user.firstName}} {{user.lastName}}) - <a (click)="deleteUser(user.id)" class="text-danger">Delete</a> </li> </ul>
کامپوننت Home
مسیر: /src/app/home/home.component.ts
کامپوننت home اطلاعات کاربر فعلی را به وسیله subscribe کردن به پراپرتی currentUser موجود در سرویس authentication می گیرد. این subscribe در یک متغیری به نام currentUserSubscription ذخیره می شود بنابراین می توانیم در هنگام نابود شدن کامپوننت آن را unsubscribe کنیم. کامپوننت home لیست کل کاربران را از طریق سرویس user می گیرد و در پراپرتی users قرار می دهد تا توسط قالب کامپوننت home استفاده شود.
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Subscription } from 'rxjs'; import { first } from 'rxjs/operators'; import { User } from '@/_models'; import { UserService, AuthenticationService } from '@/_services'; @Component({ templateUrl: 'home.component.html' }) export class HomeComponent implements OnInit, OnDestroy { currentUser: User; currentUserSubscription: Subscription; users: User[] = []; constructor( private authenticationService: AuthenticationService, private userService: UserService ) { this.currentUserSubscription = this.authenticationService.currentUser.subscribe(user => { this.currentUser = user; }); } ngOnInit() { this.loadAllUsers(); } ngOnDestroy() { // unsubscribe to ensure no memory leaks this.currentUserSubscription.unsubscribe(); } deleteUser(id: number) { this.userService.delete(id).pipe(first()).subscribe(() => { this.loadAllUsers() }); } private loadAllUsers() { this.userService.getAll().pipe(first()).subscribe(users => { this.users = users; }); } }
قالب کامپوننت Login
مسیر: /src/app/login/login.component.html
قالب کامپوننت login شامل یک فرم ورود با دو فیلد username و password است. در هنگام کلیک بر روی دکمه submit اگر مقدار یکی از فیلدها درست نباشد، پیامی را به کاربر نمایش می دهد. رویداد submit دکمه به متد onSubmit موجود در کامپوننت login بایند شده است.
<h2>Login</h2> <form [formGroup]="loginForm" (ngSubmit)="onSubmit()"> <div class="form-group"> <label for="username">Username</label> <input type="text" formControlName="username" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.username.errors }" /> <div *ngIf="submitted && f.username.errors" class="invalid-feedback"> <div *ngIf="f.username.errors.required">Username is required</div> </div> </div> <div class="form-group"> <label for="password">Password</label> <input type="password" formControlName="password" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.password.errors }" /> <div *ngIf="submitted && f.password.errors" class="invalid-feedback"> <div *ngIf="f.password.errors.required">Password is required</div> </div> </div> <div class="form-group"> <button [disabled]="loading" class="btn btn-primary">Login</button> <img *ngIf="loading" class="pl-3" src="data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==" /> <a routerLink="/register" class="btn btn-link">Register</a> </div> </form>
کامپوننت Login
مسیر: /src/app/login/login.component.ts
کامپوننت login برای کنترل ورود کاربران به برنامه از سرویس authentication استفاده می کند. اگر کاربر قبلا وارد شده باشد، به طور خودکار به صفحه home منتقل خواهد شد. شیء loginForm: FormGroup به منظور دسترسی به اطلاعات وارد شده در فرم استفاده می شود. FormGroup یک ماژول مربوط به Angular Reactive Forms است که در قالب بالا با دستورالعمل [formGroup]=”loginForm” به فرم بایند شده است.
import { Component, OnInit } from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { first } from 'rxjs/operators'; import { AlertService, AuthenticationService } from '@/_services'; @Component({templateUrl: 'login.component.html'}) export class LoginComponent implements OnInit { loginForm: FormGroup; loading = false; submitted = false; returnUrl: string; constructor( private formBuilder: FormBuilder, private route: ActivatedRoute, private router: Router, private authenticationService: AuthenticationService, private alertService: AlertService ) { // redirect to home if already logged in if (this.authenticationService.currentUserValue) { this.router.navigate(['/']); } } ngOnInit() { this.loginForm = this.formBuilder.group({ username: ['', Validators.required], password: ['', Validators.required] }); // get return url from route parameters or default to '/' this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/'; } // convenience getter for easy access to form fields get f() { return this.loginForm.controls; } onSubmit() { this.submitted = true; // stop here if form is invalid if (this.loginForm.invalid) { return; } this.loading = true; this.authenticationService.login(this.f.username.value, this.f.password.value) .pipe(first()) .subscribe( data => { this.router.navigate([this.returnUrl]); }, error => { this.alertService.error(error); this.loading = false; }); } }
قالب کامپوننت Register
مسیر: /src/app/register/register.component.html
قالب کامپوننت register شامل یک فرم ثبت نام ساده با چهار فیلد first name، last name، username و password است. اگر در هنگام کلیک بر روی دکمه submit مقادیر فیلدها نامعتبر باشند، پیامی به کاربر نمایش داده می شود. رویداد submit دکمه به متد onSubmit موجود در کامپوننت Register بایند شده است.
<h2>Register</h2> <form [formGroup]="registerForm" (ngSubmit)="onSubmit()"> <div class="form-group"> <label for="firstName">First Name</label> <input type="text" formControlName="firstName" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.firstName.errors }" /> <div *ngIf="submitted && f.firstName.errors" class="invalid-feedback"> <div *ngIf="f.firstName.errors.required">First Name is required</div> </div> </div> <div class="form-group"> <label for="lastName">Last Name</label> <input type="text" formControlName="lastName" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.lastName.errors }" /> <div *ngIf="submitted && f.lastName.errors" class="invalid-feedback"> <div *ngIf="f.lastName.errors.required">Last Name is required</div> </div> </div> <div class="form-group"> <label for="username">Username</label> <input type="text" formControlName="username" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.username.errors }" /> <div *ngIf="submitted && f.username.errors" class="invalid-feedback"> <div *ngIf="f.username.errors.required">Username is required</div> </div> </div> <div class="form-group"> <label for="password">Password</label> <input type="password" formControlName="password" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.password.errors }" /> <div *ngIf="submitted && f.password.errors" class="invalid-feedback"> <div *ngIf="f.password.errors.required">Password is required</div> <div *ngIf="f.password.errors.minlength">Password must be at least 6 characters</div> </div> </div> <div class="form-group"> <button [disabled]="loading" class="btn btn-primary">Register</button> <img *ngIf="loading" class="pl-3" src="data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==" /> <a routerLink="/login" class="btn btn-link">Cancel</a> </div> </form>
کامپوننت Register
مسیر: /src/app/register/register.component.ts
کامپوننت register با استفاده از سرویس user کاربر جدید را ثبت نام می کند. اگر کاربر قبلا وارد برنامه شده باشد، به طور خودکار به صفحه home منتقل خواهد شد.
import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { first } from 'rxjs/operators'; import { AlertService, UserService, AuthenticationService } from '@/_services'; @Component({templateUrl: 'register.component.html'}) export class RegisterComponent implements OnInit { registerForm: FormGroup; loading = false; submitted = false; constructor( private formBuilder: FormBuilder, private router: Router, private authenticationService: AuthenticationService, private userService: UserService, private alertService: AlertService ) { // redirect to home if already logged in if (this.authenticationService.currentUserValue) { this.router.navigate(['/']); } } ngOnInit() { this.registerForm = this.formBuilder.group({ firstName: ['', Validators.required], lastName: ['', Validators.required], username: ['', Validators.required], password: ['', [Validators.required, Validators.minLength(6)]] }); } // convenience getter for easy access to form fields get f() { return this.registerForm.controls; } onSubmit() { this.submitted = true; // stop here if form is invalid if (this.registerForm.invalid) { return; } this.loading = true; this.userService.register(this.registerForm.value) .pipe(first()) .subscribe( data => { this.alertService.success('Registration successful', true); this.router.navigate(['/login']); }, error => { this.alertService.error(error); this.loading = false; }); } }
قالب کامپوننت App
مسیر: /src/app/app.component.html
قالب کامپوننت app، قالب کامپوننت root یا اصلی برنامه است و شامل navbar اصلی است که فقط برای کاربرانی که احراز هویت شده اند نمایش داده می شود، یک دستورالعمل router-outlet که برای نمایش محتوای هر ویو بر اساس مسیر فعلی استفاده می شود و یک کامپوننت alert که برای نمایش پیام در هر جایی از برنامه استفاده می شود.
<!-- nav --> <nav class="navbar navbar-expand navbar-dark bg-dark" *ngIf="currentUser"> <div class="navbar-nav"> <a class="nav-item nav-link" routerLink="/">Home</a> <a class="nav-item nav-link" (click)="logout()">Logout</a> </div> </nav> <!-- main app container --> <div class="jumbotron"> <div class="container"> <div class="row"> <div class="col-sm-6 offset-sm-3"> <alert></alert> <router-outlet></router-outlet> </div> </div> </div> </div>
کامپوننت App
مسیر: /src/app/app.component.ts
کامپوننت app، کامپوننت اصلی یا همان root برنامه است و تگ root برنامه یعنی <app></app> را تعریف می کند. این کامپوننت به سرویس authentication سابسکرایب کرده است بنابراین به صورت پویا می تواند بر اساس ورود و خروجی کاربر navbar را مخفی کرده یا نمایش دهد. از آن جا که این کامپوننت، کامپوننت root است، نیازی به unsubscribe کردن نداریم، چون کامپوننت روت فقط زمانی نابود می شود که کل برنامه بسته شود و بسته شدن کل برنامه هم باعث unsubscribe شدن همه subscribe ها می شود.
کامپوننت app یک متد logout دارد که از طریق لینک logout موجود در navbar که در کد بالا تعریف کردیم فراخوانی می شود و کاربر را از برنامه خارج و به صفحه ورود (login) هدایت می کند.
import { Component } from '@angular/core'; import { Router } from '@angular/router'; import { AuthenticationService } from './_services'; import { User } from './_models'; @Component({ selector: 'app', templateUrl: 'app.component.html' }) export class AppComponent { currentUser: User; constructor( private router: Router, private authenticationService: AuthenticationService ) { this.authenticationService.currentUser.subscribe(x => this.currentUser = x); } logout() { this.authenticationService.logout(); this.router.navigate(['/login']); } }
ماژول App
مسیر: /src/app/app.module.ts
ماژول app، ماژول اصلی (root) برنامه را به همراه metadata هایی در مورد آن ماژول تعریف می کند. برای اطلاعات بیشتر در مورد ماژول های انگولار، می توانید به Angular – NgModules [1] مراجعه کنید. اینجا جایی است که fake backend provider اضافه شده است. برای تغییر به backend provider اصلی کافیست ارائه دهنده هایی که با کامنت “// providers used to create fake backend” مشخص شده اند را حذف کنید.
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { ReactiveFormsModule } from '@angular/forms'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; // used to create fake backend import { fakeBackendProvider } from './_helpers'; import { AppComponent } from './app.component'; import { routing } from './app.routing'; import { AlertComponent } from './_components'; import { JwtInterceptor, ErrorInterceptor } from './_helpers'; import { HomeComponent } from './home'; import { LoginComponent } from './login'; import { RegisterComponent } from './register'; @NgModule({ imports: [ BrowserModule, ReactiveFormsModule, HttpClientModule, routing ], declarations: [ AppComponent, AlertComponent, HomeComponent, LoginComponent, RegisterComponent ], providers: [ { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }, // provider used to create fake backend fakeBackendProvider ], bootstrap: [AppComponent] }) export class AppModule { }
App Routing
مسیر: /src/app/app.routing.ts
فایل app routing مسیرهای برنامه را تعریف می کند. هر مسیر شامل آدرس و کامپوننت مرتبط است. اگر به کد زیر توجه کنید، مسیر کامپوننت home با دادن AuthGuard به پراپرتی canActivate امن شه است.
import { Routes, RouterModule } from '@angular/router'; import { HomeComponent } from './home'; import { LoginComponent } from './login'; import { RegisterComponent } from './register'; import { AuthGuard } from './_guards'; const appRoutes: Routes = [ { path: '', component: HomeComponent, canActivate: [AuthGuard] }, { path: 'login', component: LoginComponent }, { path: 'register', component: RegisterComponent }, // otherwise redirect to home { path: '**', redirectTo: '' } ]; export const routing = RouterModule.forRoot(appRoutes);
فایل Index.html اصلی
مسیر: /src/index.html
فایل index.html اصلی، اولین فایلی است که توسط مرورگر لود می شود. Webpack تمام فایل های جاوا اسکریپت برنامه را با هم بسته بندی کرده و به درون فایل index.html تزریق می کند تا توسط مرورگر لود و اجرا شوند.
<!DOCTYPE html> <html> <head> <base href="/" /> <title>Angular 7 User Registration and Login Example</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- bootstrap css --> <link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet" /> <style> a { cursor: pointer } </style> </head> <body> <app>Loading...</app> </body> </html>
فایل Main.ts
مسیر: /src/main.ts
فایل main.ts توسط انگولار به عنوان فایل اصلی و برای راه اندازی برنامه استفاده می شود.
import './polyfills'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; platformBrowserDynamic().bootstrapModule(AppModule);
فایل Polyfills.ts
برخی از ویژگی های مورد استفاده در انگولار ۷ هنوز به طور native توسط همه مرورگرهای اصلی پشتیبانی نمی شوند. از polyfills به منظور افزودن این ویژگی ها به مرورگر در صورت نیاز استفاده می شود.
import 'core-js/es6/reflect'; import 'core-js/es7/reflect'; import 'zone.js/dist/zone';
فایل نوع های سفارشی (Custom Typings)
مسیر: /src/typings.d.ts
این فایل برای تعریف نوع های سفارشی که خارج از برنامه انگولار هستند استفاده می شود. کامپایل تایپ اسکریپت با کمک این فایل نوع های سفارشی شناسایی می کند و در هنگام کامپایل خطا نمی دهد. این فایل شامل اعلان شیء سراسری config است که توسط Webpack ایجاد شده است.
// so the typescript compiler doesn't complain about the global config object declare var config: any;
فایل Package.json
مسیر: /package.json
فایل package.json شامل اطلاعات پیکربندی پروژه و بسته های مورد نیاز برنامه (dependencies) است که موقع اجرای دستور npm install نصب می شوند.
{ "name": "angular-7-registration-login-example", "version": "1.0.0", "repository": { "type": "git", "url": "https://github.com/cornflourblue/angular-7-registration-login-example.git" }, "scripts": { "build": "webpack --mode production", "start": "webpack-dev-server --mode development --open" }, "license": "MIT", "dependencies": { "@angular/common": "^7.0.1", "@angular/compiler": "^7.0.1", "@angular/core": "^7.0.1", "@angular/forms": "^7.0.1", "@angular/platform-browser": "^7.0.1", "@angular/platform-browser-dynamic": "^7.0.1", "@angular/router": "^7.0.1", "core-js": "^2.5.7", "rxjs": "^6.3.3", "zone.js": "^0.8.26" }, "devDependencies": { "@types/node": "^10.12.0", "angular2-template-loader": "^0.6.2", "html-webpack-plugin": "^3.2.0", "raw-loader": "^0.5.1", "ts-loader": "^5.2.2", "typescript": "^3.1.3", "webpack": "4.23.1", "webpack-cli": "^3.1.2", "webpack-dev-server": "^3.1.10" } }
فایل tsconfig.json
مسیر: /tsconfig.json
این فایل مربوط به کامپایلر زبان تایپ اسکریپت است و چگونگی تبدیل شدن TypeScript به جاوا اسکریپت قابل فهم توسط مرورگر را پیکربندی می کند.
{ "compilerOptions": { "emitDecoratorMetadata": true, "experimentalDecorators": true, "lib": [ "es2015", "dom" ], "module": "commonjs", "moduleResolution": "node", "sourceMap": true, "suppressImplicitAnyIndexErrors": true, "target": "es5", "baseUrl": "src", "paths": { "@/*": [ "app/*" ] } } }
پیکربندی Webpack 4
مسیر: /webpack.config.js
Webpack 4.23 به منظور bundle کردن کل فایل های پروژه استفاده می شود. فایل bundle شده توسط مرورگر بارگذاری می شود. چگونگی bundle شدن فایل های پروژه در فایل webpack.config.js مشخص می شود. این ساده ترین حالت فایل webpack.config.js برای باندل کردن یک برنامه انگولار ۷ است که در آن ts-loader برای کامپایل کردن فایل های TypeScript، raw-loader برای لود کردن قالب های انگولار و HtmlWebpackPlugin برای تزریق اسکریپت باندل شده به بدنه فایل index.html استفاده می شود.
مسیر مستعار ‘@’ در فایل های webpack.config.js و tsconfig.json پیکربندی شده است به مسیر ‘/src/app’ اشاره می کند. بنابراین در مواردی مثل import کردن از ‘@’ استفاده کنیم.
const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const path = require('path'); module.exports = { entry: './src/main.ts', module: { rules: [ { test: /\.ts$/, use: ['ts-loader', 'angular2-template-loader'], exclude: /node_modules/ }, { test: /\.(html|css)$/, loader: 'raw-loader' }, ] }, resolve: { extensions: ['.ts', '.js'], alias: { '@': path.resolve(__dirname, 'src/app/'), } }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', filename: 'index.html', inject: 'body' }), new webpack.DefinePlugin({ // global app config object config: JSON.stringify({ apiUrl: 'http://localhost:4000' }) }) ], optimization: { splitChunks: { chunks: 'all', }, runtimeChunk: true }, devServer: { historyApiFallback: true } };
راهنمای لینک ها
- https://angular.io/guide/styleguide
- https://angular.io/docs/ts/latest/guide/ngmodule.html
- https://docs.npmjs.com/files/package.json
- https://www.typescriptlang.org/docs/handbook/tsconfig-json.html
- https://webpack.js.org/concepts
نوشته ساخت سیستم ثبت نام و ورود کاربر در انگولار ۷ – آموزش انگولار ۷ اولین بار در سورس سرا - آموزش برنامه نویسی. پدیدار شد.