User Show/Edit 만들기

소스코드

이 게시물에는 코드작성이 포함되어 있습니다. 소스코드를 받으신 후 진행해 주세요. MEAN Stack/개발 환경 구축에서 설명된 프로그램들(git, npm, atom editor)이 있어야 아래의 명령어들을 실행할 수 있습니다.

이 게시물의 소스코드는 기본사이트 만들기 / Loading Bar 만들기(Angular Material)에서 이어집니다.

angular-site.git 을 clone 한 적이 있는 경우: 터미널에서 해당 폴더로 이동 후 아래 명령어들을 붙여넣기합니다. 폴더 내 모든 코드가 이 게시물의 코드로 교체됩니다. 이를 원치 않으시면 이 방법을 선택하지 마세요.

git reset --hard
git pull
git reset --hard c2aab37
git reset --soft 974e233
npm install
atom .

angular-site.git 을 clone 한 적이 없는 경우: 터미널에서 코드를 다운 받을 폴더로 이동한 후 아래 명령어들을 붙여넣기하여 angular-site.git 을 clone 합니다.

git clone https://github.com/a-mean-blogger/angular-site.git
cd angular-site
git reset --hard c2aab37
git reset --soft 974e233
npm install
atom .

- Github에서 소스코드 보기: https://github.com/a-mean-blogger/angular-site/tree/c2aab3757ae136185b3e58635d05535f16c81a0a


기본 사이트 만들기의 마지막 강의 입니다. user의 조회, 수정, 삭제 기능을 추가하여 CURD를 완성합시다.

이번 강의에서 추가되는 코드는 다음과 같습니다.

  • User-new component로 user의 조회 view를 만듭니다.
  • User-edit component로 user의 수정 form을 만듭니다.
  • User Resolve를 사용해서 user를 조회, 수정하기 전에 user data를 준비합니다.
  • 삭제 기능은 User-edit component안에 만듭니다.

즉 새로운 내용이 없으므로 복습이라고 생각하시면 되겠습니다.

폴더 구조


주황색은 변경된 파일, 녹색은 새로 생성된 파일, 회색은 변화가 없는 파일입니다.

아래 명령어로 user-edit, user-show component를 생성해 줍시다.

$ ng g component user-edit --spec false
$ ng g component user-show -spec false

--spec false를 options으로 사용하면 .spec.ts를 생성하지 않습니다.

Resolve는 ng 명령어로 만들 수 없으므로 user.resolve.ts를 직접 만들어 줍시다.

코드

user.resolve.ts 부터 살펴봅시다.

// src/app/user.resolve.ts

import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
import { UserService } from './user.service';
import { User } from './user';

@Injectable()
export class UserResolve implements Resolve<User> {

  constructor(
    private userService: UserService,
  ) {}

  resolve(route: ActivatedRouteSnapshot) {
    return this.userService.show(route.params['username']);
  }
}

Users.resolve.ts랑 비교해서 user service에서 index가 아니라 show를 호출한다는 점이 다릅니다. 또한 username을 route param에서 가져오고 있습니다. 나중에 route 부분에서 username param을 제공하는 부분을 살펴보겠습니다.

다음은 user-show.component입니다.

// src/app/user-show/user-show.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';

import { User } from '../user';

import { AuthService } from '../auth.service';

@Component({
  selector: 'app-user-show',
  templateUrl: './user-show.component.html',
  styleUrls: ['./user-show.component.css']
})
export class UserShowComponent implements OnInit {
  user: User;

  constructor(
    private route: ActivatedRoute,
    public authService: AuthService,
  ) {
    this.user = this.route.snapshot.data['user'];
  }

  ngOnInit() {
  }

}

user를 route에서 가져오고 있습니다.

<!-- src/app/user-show/user-show.component.html -->
<div class="page page-users">

  <div class="buttons">
    <a [routerLink]="['/','users']" class="btn btn-default">Back</a>
    <a *ngIf="authService.isLoggedIn() && authService.getCurrentUser()._id == user._id"
      [routerLink]="['/','users', user.username, 'edit']"class="btn btn-default">Edit</a>
  </div>

  <form class="user-form form-horizontal">
    <div class="contentBox">
      <h3 class="contentBoxTop">{{user.username}}</h3>
      <fieldset disabled>
        <div class="form-group">
          <label for="name" class="col-sm-3">Name</label>
          <div class="col-sm-9">
            <input class="form-control" type="text" id="name" name="name" [value]="user.name">
          </div>
        </div>
        <div class="form-group">
          <label for="email" class="col-sm-3">Email</label>
          <div class="col-sm-9">
            <input class="form-control" type="text" id="email" name="email" [value]="user.email">
          </div>
        </div>
      </fieldset>
    </div>
  </form>

</div>

user.show.compoent.css는 비어있습니다.

다음은 user-edit.component를 살펴봅시다.

// src/app/user-edit/user-edit.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

import { User } from '../user';
import { ApiResponse } from '../api-response';

import { UtilService } from '../util.service';
import { UserService } from '../user.service';
import { AuthService } from '../auth.service';


@Component({
  selector: 'app-user-edit',
  templateUrl: './user-edit.component.html',
  styleUrls: ['./user-edit.component.css']
})
export class UserEditComponent implements OnInit {
  user: User;
  errorResponse: ApiResponse;
  form: FormGroup;
  formErrors = {
    'currentPassword':'',
    'username':'',
    'name':'',
    'email':'',
    'newPassword':'',
    'passwordConfirmation':'',
  };
  formErrorMessages = {
    'username': {
      'required': 'Username is required!',
      'pattern': 'Should be 4-12 characters!',
    },
    'currentPassword': {
      'required': 'Username is required!',
    },
    'name': {
      'required': 'Name is required!',
      'pattern': 'Should be 4-12 characters!',
    },
    'email': {
      'pattern': 'Should be a vaild email address!',
    },
    'newPassword': {
      'pattern': 'Should be minimum 8 characters of alphabet and number combination!',
    },
    'passwordConfirmation': {
      'match': 'Password Confirmation does not matched!',
    },
  };
  buildForm(): void {
    this.form = this.formBuilder.group({
      currentPassword:["", [Validators.required]],
      username:[this.user.username, [Validators.required, Validators.pattern(/^.{4,12}$/)]],
      name:[this.user.name, [Validators.required, Validators.pattern(/^.{4,12}$/)]],
      email:[this.user.email, [Validators.pattern(/^[a-zA-Z0-9._%+-][email protected][a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/)]],
      newPassword:["", [Validators.pattern(/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,16}$/)]],
      passwordConfirmation:[""],
    }, {
      validator: this.customValidation,
    });

    this.form.valueChanges.subscribe(data => {
      this.utilService.updateFormErrors(this.form, this.formErrors, this.formErrorMessages);
    });
  };

  customValidation(group: FormGroup) {
    var password = group.get('newPassword');
    var passwordConfirmation = group.get('passwordConfirmation');
    if(password.dirty && passwordConfirmation.dirty && password.value != passwordConfirmation.value){
        passwordConfirmation.setErrors({'match': true});
    }
  }

  constructor(
    private route: ActivatedRoute,
    private router: Router,
    private formBuilder: FormBuilder,
    private utilService: UtilService,
    private userService: UserService,
    public authService: AuthService,
  ) {
    this.user = this.route.snapshot.data['user'];
    this.buildForm();
  }

  ngOnInit() {
  }

  submit() {
    this.utilService.makeFormDirtyAndUpdateErrors(this.form, this.formErrors, this.formErrorMessages);
    if(this.form.valid){
      this.userService.update(this.user.username, this.form.value)
      .then(data =>{
        this.router.navigate(['/', 'users', this.user.username]);
      })
      .catch(response =>{
        this.errorResponse = response;
        this.utilService.handleFormSubmitError(this.errorResponse, this.form, this.formErrors);
      });
    }
  }

  delete() {
    var answer = confirm("Do you want to delete your account?");
    if(answer){
      this.userService.destroy(this.user.username)
      .then(data =>{
        this.authService.logout();
      })
      .catch(response =>{
        this.errorResponse = response;
        this.utilService.handleFormSubmitError(this.errorResponse, this.form, this.formErrors);
      });
    }
  }

}

user-new와 비교해서 form 처리하는 내용이 다르고, 마지막에 user service의 destroy함수를 호출하는 delete 함수가 있습니다.

<!-- src/app/user-edit/user-edit.component.html -->

<div class="page page-users">

  <div class="buttons">
    <a [routerLink]="['/','users',user.username]" class="btn btn-default">Back</a>
    <span *ngIf="authService.isLoggedIn() && authService.getCurrentUser()._id == user._id"
      (click)="delete()"
      class="btn btn-default delete">Delete</span>
  </div>

  <form class="user-form form-horizontal"
    [formGroup]="form"
    (ngSubmit)="submit()"
    class="login-form form-horizontal"
  >
    <div class="contentBox">
      <h3 class="contentBoxTop">Edit User</h3>
      <fieldset>
        <div class="form-group" [ngClass]="{'has-error': formErrors.currentPassword}">
          <label for="currentPassword" class="col-sm-12 control-label">Current Password*</label>
          <div class="col-sm-9 col-sm-offset-3">
            <input class="form-control" type="password" formControlName="currentPassword" id="currentPassword">
            <span *ngIf="formErrors.currentPassword" class="help-block">{{formErrors.currentPassword}}</span>
          </div>
        </div>
        <hr/>
        <div class="form-group" [ngClass]="{'has-error': formErrors.username}">
          <label for="username" class="col-sm-3 control-label">Username*</label>
          <div class="col-sm-9">
            <input class="form-control" type="text" formControlName="username" id="username">
            <span *ngIf="formErrors.username" class="help-block">{{formErrors.username}}</span>
          </div>
        </div>
        <div class="form-group" [ngClass]="{'has-error': formErrors.name}">
          <label for="name" class="col-sm-3 control-label">Name*</label>
          <div class="col-sm-9">
            <input class="form-control" type="text" formControlName="name" id="name">
            <span *ngIf="formErrors.name" class="help-block">{{formErrors.name}}</span>
          </div>
        </div>
        <div class="form-group" [ngClass]="{'has-error': formErrors.email}">
          <label for="email" class="col-sm-3 control-label">Email</label>
          <div class="col-sm-9">
            <input class="form-control" type="text" formControlName="email" id="email">
            <span *ngIf="formErrors.email" class="help-block">{{formErrors.email}}</span>
          </div>
        </div>
        <div class="form-group" [ngClass]="{'has-error': formErrors.newPassword}">
          <label for="newPassword" class="col-sm-12 control-label">New Password</label>
          <div class="col-sm-9 col-sm-offset-3">
            <input class="form-control" type="password" formControlName="newPassword" id="newPassword">
            <span *ngIf="formErrors.newPassword" class="help-block">{{formErrors.newPassword}}</span>
          </div>
        </div>
        <div class="form-group" [ngClass]="{'has-error': formErrors.passwordConfirmation}">
          <label for="passwordConfirmation" class="col-sm-12 control-label">Password Confirmation</label>
          <div class="col-sm-9 col-sm-offset-3">
            <input class="form-control" type="password" formControlName="passwordConfirmation" id="passwordConfirmation">
            <span *ngIf="formErrors.passwordConfirmation" class="help-block">{{formErrors.passwordConfirmation}}</span>
          </div>
        </div>
        <p>
          <small>*Required</small>
        </p>
      </fieldset>
      <div *ngIf="errorResponse?.message" class="alert alert-danger">
        {{errorResponse?.message}}
      </div>
    </div>
    <div class="buttons">
      <button type="submit" class="btn btn-default">Submit</button>
    </div>
  </form>

</div>

user-new.component.htmld에서 수정하여 이 template을 만들었습니다.

/* src/app/user-edit/user-edit.component.css */
.buttons .delete {
  color: #860505;
  float: right;
}

다음으로 route과 메인 module을 살펴봅시다.

// src/app/app-routing.module.ts

import { NgModule }             from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { AuthGuard } from './auth.guard';
import { UsersResolve } from './users.resolve';
import { UserResolve } from './user.resolve';

import { WelcomeComponent } from './welcome/welcome.component';
import { Error404Component } from './error404/error404.component';
import { LoginComponent } from './login/login.component';
import { UserNewComponent } from './user-new/user-new.component';
import { UserIndexComponent } from './user-index/user-index.component';
import { UserShowComponent } from './user-show/user-show.component';
import { UserEditComponent } from './user-edit/user-edit.component';

const routes: Routes = [
  { path: '',  component: WelcomeComponent },
  { path: 'login', component: LoginComponent },
  { path: 'users/new',  component: UserNewComponent },
  { path: 'users', canActivate: [AuthGuard],
    children: [
      { path: '',
        component: UserIndexComponent,
        resolve: {
          users: UsersResolve,
        }
      },
      { path: ':username',
        component: UserShowComponent,
        resolve: {
          user: UserResolve
        }
      },
      { path: ':username/edit',
        component: UserEditComponent,
        resolve: {
          user: UserResolve
        }
      },
    ]
  },
  { path: '**', component: Error404Component },
];

@NgModule({
  imports: [ RouterModule.forRoot(routes) ],
  exports: [ RouterModule ]
})
export class AppRoutingModule {}


//...생략
import { UsersResolve } from './users.resolve';
import { UserResolve } from './user.resolve';

//...생략
import { UserIndexComponent } from './user-index/user-index.component';
import { UserShowComponent } from './user-show/user-show.component';
import { UserEditComponent } from './user-edit/user-edit.component';

@NgModule({
  declarations: [
    //...생략
    UserIndexComponent,
    UserShowComponent,
    UserEditComponent,
  ],
  //...생략
  providers: [
    //...생략
    UsersResolve,
    UserResolve,
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

이번에 생성한 UserResolve, UserShowComponent, UserEditComponent들을 route과 메인 module의 위치에 넣어줍니다.

실행 결과

Angular-CLI 명령어 사용해서 서버를 실행합시다. 

ng serve --open

로그인한 후  Users 메뉴를 누르고 username을 눌러봅시다.

해당 user에 대한 정보가 나오고, 로그인 된 본인의 username을 누르면 edit버튼이 보입니다. 이 버튼을 누릅시다.

자신의 정보를 수정하거나 삭제할 수 있습니다.

마치며..

이것으로 Angular 2 기본사이트 강의가 끝이 났습니다. 수고하셨습니다! 혹시나 이 코드를 기본으로 해서 웹사이트를 제작하고 싶으신 분은 그냥 가져가서 쓰셔도 됩니다. 

댓글

S
Sungjae Park 2017.10.13
안녕하세요. 혹시 angular2에서 DB연결은 어떻게 처리 합니까?
I
Ian H 2017.10.13
@Sungjae Park,
Angular는 REST API를 통해 DB를 조작합니다. 즉, REST API가 없으면 REST API를 만들어야 합니다.
이승윤 2017.10.20
감사합니다~ 정말 많은 도움됬습니다.
I
Ian H 2017.10.21
@이승윤,
수고하셨습니다!!
미뉴엣 2017.10.21
한마디로 정의하겠습ㄴ다. 실무에 꼭 필요한 부분만 핸들링한 강좌.. 책보다 승윤님 강좌가 최선의 선택이 아닌가 싶습니다. 벌써부터 다음강좌가 기대되는데 게시판은 어떻가요?^^
I
Ian H 2017.10.24
@미뉴엣,
감사합니다. 지금은 웹사이트 개발이랑은 조금 거리가 있는 것을 준비중에 있고.. 언젠가는 Angular 게시판도 하겠습니다^^
한지훈 2017.11.20
안녕하세요 일주일째 이블로그로 공부하고있는데요.. 노드제이제스로 작성하신 게시판을 앵귤러2에서 구현해보려고하는데 디비연동을 어디서 해야할지 감이안잡힙니다ㅠㅠ 앵귤러에서 디비연동하려면 어떻게하는게 좋을까요...
한지훈 2017.11.20
그리고 글써주셔서 감사합니다 정말잘보고있어요
I
Ian H 2017.11.20
@한지훈,
user 생성과 마찬가지로 게시판 API를 만드셔서 접근하실 수 있습니다. Angular에서 DB는 항상 API를 통해서 조작할 수 있습니다!
댓글쓰기

이 글에 댓글을 다시려면 SNS 계정으로 로그인하세요. 자세히 알아보기

UP