일반적인 애플리케이션이라면, 로그인 폼에 입력한 계정이 유효한지 체크하기 위해 회원을 담당하는 api서버로 http요청을 할 것입니다.
그러나 이 어플리케이션은 Angular에만 집중하기 위해 api서버를 따로 두지 않고, Angular에서 기본적으로 제공하는 In-memory DB Service를 이용했습니다.
(mockable.io를 사용할 수도 있으나 무료 버전은 유효 기간이 정해져 있으므로 In-memory DB로 결정했습니다.)
이 장에서는 In-memory DB Service를 만들고, 이전 장에서 입력한 Form에서 로그인 버튼을 클릭했을 때 In-memory DB로부터 해당 계정이 유효한지 체크하는 구현부를 살펴보겠습니다.
Prerequisite
HttpClientModule, rxjs
1. In-memory DB Service구성
1) In-memory Web API를 Npm으로부터 install합니다.
npm install angular-in-memory-web-api --save
2) InMemoryDbService를 구현한 InMemoryData 클래스를 작성합니다. 이는 API를 호출했을 때, 반환시킬 데이터를 정의하는 클래스입니다.
import { InMemoryDbService } from 'angular-in-memory-web-api';
export class InMemoryData implements InMemoryDbService {
createDb() {
const users = [
{ id: 1, loginId: 'sks', name: 'Mr. Son', email: 'sks@sys4u.co.kr', passwd: '1234' },
{ id: 2, loginId: 'lsj', name: 'Ms. Su', email: 'lsj@sys4u.co.kr', passwd: '1234' }
];
return { users : users };
}
}
- 4라인 : createDb() 메소드는 InMemoryDbService를 구현하기 위해 정의되어야 하는 메서드입니다.
- 5라인 : 특정 URL을 호출했을 때, 반환되는 배열을 정의하는 변수입니다. 각 object는 id라는 key를 필수로 가지고 있어야 합니다.
- 9라인 : api/users라는 URL로 호출하면 users 배열을 반환시키라는 의미입니다. ecma6 문법에 의해, key와 value가 같으므로 다음과 같이 key를 생략할 수 있습니다.
return { users };
3) InMemoryData가 동작하도록 AppModule에 선언해줍니다.
[app.module.ts]
..생략..
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryData } from './core/inmemory/inmemory.data';
@NgModule({
..생략..
imports: [
..생략..
HttpClientInMemoryWebApiModule.forRoot(
InMemoryData, { dataEncapsulation: false }
)
],
..생략..
})
export class AppModule {}
위와 같은 작업을 마치면, api/[key]로 요청을 보냈을 때 배열 형태의 value값이 반환됩니다. 여기서는 api/users이 될 것입니다.
api라는 prefix url은 In-Memory api의 url 명명 규칙이므로 변경할 수 없습니다.
In-Memory DB Web api는 실제 애플리케이션에서 쓰기 위한 용도가 아니므로 많은 기능을 제공하지 않습니다. 따라서 조회 기능도 제한적인데, 단 1개의 조회 조건만 허용합니다.
로그인 계정 유효 체크를 하기 위해서는 loginId와 password 2가지로 조회해야 하는데 In-Memory DB는 이 기능을 제공하지 않으므로 loginId에 대해서만 조회 후, 반환되는 데이터에 대해 password를 직접 비교하도록 구성했습니다.
2. Authentication(사용자 인증)
In-memory db에 대한 http요청을 하는 것은 어렵지 않아 보입니다. 그럼 과연 어디서 http요청을 해야 보다 좋은 구조가 될지 생각해봅시다.
1) 로그인을 클릭할 때 발생하는 이벤트이므로 LoginFormComponent에서 호출할 수 있습니다. 그러나 Component에서는 로직 처리를 하지 않고 UI 변경에 대한 작업만 수행하기를 권장하므로 이 구조는 좋은 방법이 아닙니다.
2) 따라서 Service를 하나 두고, LoginFormComponent에서 해당 Service를 호출하는 방법도 생각할 수 있을 것입니다.
그런데 In-Memory DB는 조회 조건을 1개만 허용한다는 특징 때문에 loginId를 기준으로 조회한 후, 입력한 passwd와 반환된 passwd의 값을 비교하는 로직이 필요합니다.
따라서 login을 수행하는 기능에 login/passwd가 일치하는지 확인하는 기능이 속하게 됩니다. 이는 SRP(Single Response Principle)에 위배되는 구조라고 생각할 수 있습니다.
3) 그러므로 login/passwd가 일치하는지 확인하는 기능은 AuthService에서 구현하고, UserService가 AuthService를 호출하는 구조도 생각해 볼 수 있습니다.
만약 In-memory DB를 사용하지 않는다면, 빨간 박스가 서버사이드의 역할이 될 것입니다.
이 애플리케이션에서는 3번처럼 UserService, AuthService를 나누어 구현했습니다. (다만, LoginFormComponent에서 UserService를 호출하지 않고 ngrx/store, ngrx/effect를 이용했습니다. 이에 대한 내용은 다음장에서 확인하실 수 있습니다.)
1) UserService에서 AuthService를 주입받아 계정 정보가 유효한지에 대한 체크를 하는 authenticate 메서드를 호출합니다.
[user.service.ts]
@Injectable({
providedIn: 'root'
})
export class UserService {
constructor(
private authService: AuthService) {
}
login(user: User): Observable<User> {
return this.authService.authenticate(user);
}
}
2) AuthService의 authenticate()는 In-memory Web Url을 호출한 뒤 계정이 유효한지에 대해 체크하는 로직을 구현합니다.
[auth.service.ts]
..import 생략..
@Injectable({
providedIn: 'root'
})
export class AuthService {
constructor(
private http: HttpClient) {
}
authenticate(user: User): Observable<any> {
return this.http.get<User[]>(`${environment.API_URL}users/?loginId=${user.loginId}`).
pipe(
map(users => users[0]),
switchMap(returnedUser => {
if (returnedUser === undefined ||
returnedUser.passwd !== user.passwd
) {
return throwError('No Such loginId or Password.');
}
return of(returnedUser);
}),
map(returnedUser => {
return { ...returnedUser, passwd: null };
})
)
}
}
- 11라인 : loginId로 조회한 결과를 User배열의 Observable로 반환받습니다.
- 13라인 : 배열로 온 User배열 중 첫 번째 User만 필요하므로 변환합니다.
- 14~21라인 : loginId로 일치하는 유저가 없거나, 있어도 passwd가 입력된 데이터와 다르다면 에러를 발생시킵니다.
- 23라인 : 최종적으로 반환된 User에 passwd는 null값으로 변경합니다.
${environment.API_URL}은 environment.ts/environment.prod.ts에 정의합니다.
export const environment = {
production: false,
API_URL: 'api/'
};
Conclusions
위에서 잠깐 언급했듯, 이 애플리케이션에서는 LoginFormComponent가 UserService를 직접 사용하지 않고, ngrx/store, ngrx/effect를 이용해 다른 방식으로 구현했습니다.
ngrx/store, ngrx/effect를 써서 애플리케이션의 구조를 어떻게 변화시킬 수 있는지 다음 장에서 살펴보겠습니다.
[reference]
'Front-end Framework > Angular' 카테고리의 다른 글
[실전 어플리케이션 만들기] 6. Directive (0) | 2020.10.20 |
---|---|
[실전 어플리케이션 만들기] 5. 사용자 인증(2) - ngrx/store, ngrx/effect (0) | 2020.10.20 |
[실전 어플리케이션 만들기] 3. 로그인 Form 작성 (0) | 2020.10.20 |
[실전 어플리케이션 만들기] 2. Feature Modules과 Routing (0) | 2020.10.20 |
[실전 어플리케이션 만들기] 1. 프로젝트 구조 및 생성 (0) | 2020.10.20 |
댓글