Добро пожаловать в этот учебник по созданию полнофункционального приложения с помощью Amplication.
Мы будем шаг за шагом создавать приложение Todos
, используя Angular для фронтенда и Amplication для бэкенда.
Если вы застряли, у вас возникли вопросы или вы просто хотите поздороваться с другими разработчиками Amplication, такими же как вы, то присоединяйтесь к нашему Discord!
Оглавление
- Шаг 1 — Добавление модуля HttpClientModule
- Шаг 2 — Запросы авторизации
- Шаг 3 — Компонент Auth
- Шаг 4 — Вход в систему
- Шаг 5 — Подведение итогов
Шаг 1 — Добавление модуля HttpClientModule
Чтобы позволить пользователям войти в приложение Todos
, нам нужно будет запросить у них имя пользователя и пароль, а затем проверить их на бэкенде. Для выполнения HTTP-запроса к бэкенду мы будем использовать Angular HttpClientModule
. Сначала откройте web/src/app/app.module.ts
и добавьте import HttpClientModule
:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
+ import { HttpClientModule } from '@angular/common/http';
Затем добавьте HttpClientModule
в imports
в декораторе @NgModule
:
@NgModule({
declarations: [
AppComponent,
TaskComponent,
TasksComponent,
CreateTaskComponent
],
imports: [
BrowserModule,
ReactiveFormsModule,
+ HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Мы захотим абстрагировать некоторые переменные, такие как наш API url, в многократно используемый ресурс. В web/src/environments/environment.ts
и web/src/environments/environment.prod.ts
добавьте следующие свойства в экспорт environment
:
export const environment = {
production: false,
+ apiUrl: 'http://localhost:3000',
+ jwtKey: 'accessToken',
};
Мы хотим настроить Angular HttpClientModule
на использование токена доступа пользователя при выполнении запросов к бэкенду и легкий доступ к библиотеке axios, поэтому нам понадобится настроить перехватчик, а также некоторые другие функции. В терминале перейдите в каталог web
и запустите:
ng g s JWT
Затем замените содержимое сгенерированного файла (web/src/app/jwt.service.ts
) следующим кодом:
import { Injectable } from '@angular/core';
import {
HttpInterceptor,
HttpEvent,
HttpRequest,
HttpHandler,
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from '../environments/environment';
@Injectable({
providedIn: 'root',
})
export class JWTService implements HttpInterceptor {
get jwt(): string {
return localStorage.getItem(environment.jwtKey) || '';
}
set jwt(accessToken: string) {
localStorage.setItem(environment.jwtKey, accessToken);
}
get isStoredJwt(): boolean {
return Boolean(this.jwt);
}
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
if (request.url.startsWith(environment.apiUrl)) {
request = request.clone({
setHeaders: { Authorization: `Bearer ${this.jwt}` },
});
}
return next.handle(request);
}
}
Теперь каждый запрос, который делает Angular HttpClientModule
, будет принимать токен доступа JWT пользователя, который будет храниться в локальном хранилище, и присваивать его заголовку Authorization каждого запроса.
Кроме того, мы добавили get
ter, который проверяет, существует ли уже токен доступа в локальном хранилище, и set
ter для сохранения токена доступа в локальном хранилище.
Наконец, нам нужно настроить JWTService
в AppModule
. Откройте web/src/app/app.module.ts
и импортируйте JWTService
и HTTP_INTERCEPTORS
:
- import { HttpClientModule } from '@angular/common/http';
+ import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
+ import { JWTService } from './jwt.service';
import { AppComponent } from './app.component';
Затем добавьте и настройте JWTService
в providers
декоратора @NgModule
:
- providers: [],
+ providers: [
+ { provide: HTTP_INTERCEPTORS, useClass: JWTService, multi: true },
+ ],
bootstrap: [AppComponent]
})
export class AppModule { }
Шаг 2 — Запросы авторизации
Вместо того чтобы вызывать конечные точки API непосредственно из наших компонентов, мы абстрагируем логику запросов, чтобы в случае необходимости внести изменения в поведение кода мы могли сделать это в одном месте.
В терминале перейдите в каталог web
и запустите:
ng g s auth
В верхней части вновь созданного файла (web/src/app/auth.service.ts
) мы импортируем JWTService
и HttpClient
и некоторые другие зависимости.
import { Injectable } from '@angular/core';
+ import { HttpClient } from '@angular/common/http';
+ import { of } from 'rxjs';
+ import { catchError, mergeMap } from 'rxjs/operators';
+ import { JWTService } from './jwt.service';
+ import { environment } from '../environments/environment';
В AuthService
установите JWTService
и HttpClient
в качестве аргументов конструктора:
export class AuthService {
constructor(private http: HttpClient, private jwt: JWTService) { }
}
Теперь добавьте метод me
:
me() {
const url = new URL('/api/me', environment.apiUrl).href;
return this.jwt.isStoredJwt
? this.http.get(url).pipe(catchError(() => of(null)))
: of(null);
}
me
проверит, сохранен ли у нас маркер доступа, потому что если его нет, то этот запрос не будет успешным. Если токен существует, то будет сделан запрос GET
к конечной точке /api/me
, которую мы создали в шаге 3. При успешном выполнении запроса будет возвращен объект user текущего пользователя.
Далее добавьте метод login
:
login(username: string, password: string) {
const url = new URL('/api/login', environment.apiUrl).href;
return this.http
.post(url, {
username,
password,
})
.pipe(
catchError(() => of(null)),
mergeMap((result: any) => {
if (!result) {
alert('Could not login');
return of();
}
this.jwt.jwt = result.accessToken;
return this.me();
})
);
}
login
сделает запрос POST
к конечной точке /api/login
, отправив имя пользователя и пароль нашего пользователя. В случае неудачи запроса, например, когда пользователь не существует, появится предупреждение, уведомляющее пользователя о неудаче. При успешном запросе маркер доступа будет сохранен в локальном хранилище, а затем будет вызвана функция me
, которая вернет объект user текущего пользователя.
Затем добавьте метод signup
:
signup(username: string, password: string) {
const url = new URL('/api/signup', environment.apiUrl).href;
return this.http
.post(url, {
username,
password,
})
.pipe(
catchError(() => of(null)),
mergeMap((result: any) => {
if (!result) {
alert('Could not sign up');
return of();
}
this.jwt.jwt = result.accessToken;
return this.me();
})
);
}
signup
сделает запрос POST
к конечной точке /api/signup
, которую мы также создали в Шаге 3, отправив имя пользователя и пароль нашего нового пользователя. В случае неудачи запроса, например, если имя пользователя уже используется, появится предупреждение, уведомляющее пользователя о неудаче. В случае успешного запроса токен доступа будет сохранен в локальном хранилище, а затем будет вызвана функция me
для возврата объекта user текущего пользователя.
Наконец, нам нужно добавить AuthService
в AppModule
. Откройте web/src/app/app.module.ts
и импортируйте AuthService
:
+ import { AuthService } from './auth.service';
import { JWTService } from './jwt.service';
Затем добавьте и настройте AuthService
в providers
в декораторе @NgModule
:
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: JWTService, multi: true },
+ AuthService,
],
bootstrap: [AppComponent]
})
export class AppModule { }
Шаг 3 — Компонент Auth
Нам нужен компонент, который будет собирать имя пользователя и пароль от пользователя, а затем делать соответствующий запрос с помощью функций, которые мы только что добавили. В терминале перейдите в каталог web
и запустите:
ng g c auth
Откройте следующие файлы и замените их содержимое на следующее:
web/src/app/auth/auth.component.ts
import { Component, Output, EventEmitter } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { AuthService } from '../auth.service';
@Component({
selector: 'app-auth',
templateUrl: './auth.component.html',
styleUrls: ['./auth.component.css'],
})
export class AuthComponent {
@Output() setUser = new EventEmitter<string>();
authForm = this.fb.group({
username: '',
password: '',
confirm: '',
});
isLogin = true;
constructor(private fb: FormBuilder, private auth: AuthService) {}
onSubmit() {
const { username, password, confirm }: { [key: string]: string } =
this.authForm.getRawValue();
if (!username || !password) return;
let authResult;
if (!this.isLogin && password !== confirm) {
return alert('Passwords do not match');
} else if (!this.isLogin) {
authResult = this.auth.signup(username.toLowerCase(), password);
} else {
authResult = this.auth.login(username.toLowerCase(), password);
}
authResult.subscribe({ next: (result: any) => this.setUser.emit(result) });
}
}
web/src/app/auth/auth.component.html
<form [formGroup]="authForm" (ngSubmit)="onSubmit()">
<h2>{{isLogin ? "Login" : "Sign Up"}}</h2>
<input name="username" type="text" placeholder="username" formControlName="username" required />
<input name="password" type="password" placeholder="password" formControlName="password" required />
<input *ngIf="!isLogin" name="confirmPassword" type="password" placeholder="confirm password"
formControlName="confirm" required />
<button type="submit">Submit</button>
<button type="button" (click)="isLogin = !isLogin">
{{isLogin ? "Need an account?" : "Already have an account?"}}
</button>
</form>
Этот компонент отображает форму, предлагающую пользователю ввести имя пользователя и пароль для входа в систему. Если у пользователя еще нет учетной записи, то кнопка в нижней части страницы переключает форму на создание новой учетной записи, что добавляет новое поле для подтверждения пароля.
При отправке вызывается метод login
или signup
из AuthService
, и результат обрабатывается эмиттером события @Output() setUser
.
Шаг 4 — Вход в систему
Когда компонент аутентификации создан, нам остается только показать его пользователям. Начните с добавления свойства user
к AppComponent
в web/src/app/app.component.ts
, например:
export class AppComponent {
tasks: any[] = [];
+ user: any;
Далее мы добавим метод в AppComponent
для установки свойства user
. Хотя мы могли бы напрямую установить значение, в конечном итоге мы захотим вызвать некоторый код, когда пользователь будет установлен, поэтому мы реализуем это таким образом.
setUser(user: any) {
this.user = user;
}
Затем обновите шаблон AppComponent
(web/src/app/app.component.html
), чтобы он выглядел следующим образом:
<ng-container *ngIf="user; else auth">
<app-create-task (addTask)="addTask($event)"></app-create-task>
<app-tasks [tasks]="tasks" (completed)="completed($event)"></app-tasks>
</ng-container>
<ng-template #auth>
<app-auth (setUser)="setUser($event)"></app-auth>
</ng-template>
Итак, на верхнем уровне шаблона компонента у нас есть два дочерних элемента, <ng-container>
и <ng-template>
. Поведение <ng-container>
очень похоже на то, как <>
используется в React, где мы удерживаем элементы без добавления дополнительных элементов в DOM. Контейнер <ng-container>
отображается, если свойство user
существует в AppComponent
, иначе отображается содержимое <ng-template>
. Внутри <ng-template>
мы добавили элемент app-auth
. Когда элемент app-auth
(AuthComponent
) испускает событие setUser
, свойство user
элемента AppComponent
присваивается его методом setUser
. Если есть значение user
, то мы переключим шаблон на отображение списка дел.
Не ожидается, что пользователь будет входить в систему каждый раз, особенно если учесть, что мы храним токен доступа JWT пользователя. Мы обновим AppComponent
, чтобы вызывать метод me
из AuthService
при инициации компонента. Таким образом, мы сможем присвоить свойство user
как можно раньше.
Начните с импорта OnInit
и AuthService
, а затем установите AppComponent
для реализации хука жизненного цикла OnInit
.
- import { Component } from '@angular/core';
+ import { Component, OnInit } from '@angular/core';
+ import { AuthService } from './auth.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
- export class AppComponent {
+ export class AppComponent implements OnInit {
Затем добавьте конструктор, в котором AuthService
будет задан в качестве единственного аргумента.
constructor(private auth: AuthService) {}
Затем добавьте эту реализацию крючка жизненного цикла OnInit
:
ngOnInit(): void {
this.auth.me().subscribe({ next: (user) => (this.user = user) });
}
Теперь, если свойство user
имеет значение, что происходит только при входе в систему, приложение будет показывать задачи пользователя. Если свойство user
не имеет значения, то пользователю будет показан экран авторизации, который при входе или регистрации пользователя установит свойство user
с помощью события setUser
элемента app-auth
(AuthComponent
).
Шаг 5 — Завершение
Запустите приложение и попробуйте создать новую учетную запись.
Вернитесь на следующей неделе к пятому шагу, или посетите сайт Amplication docs для полного руководства сейчас!
Чтобы просмотреть изменения для этого шага, посетите здесь.