이 연재글은 Spring Rest api + Angular framework로 웹사이트 만들기의 1번째 글입니다.

Angular 프레임워크를 이용하여 Front 웹사이트를 만드는 실습을 해보겠습니다. Angular는 Front 웹을 만드는 프레임워크 이므로 데이터와 비즈니스 로직을 적용하려면 리소스를 제공하는 API서버가 따로 필요합니다.

리소스를 제공할 Backend API서버는 SpringBoot 기반의 Rest api를 사용할 것이며, 다음의 링크에서 확인할 수 있습니다. 어느정도 완성된 api 프로젝트로서 로그인/가입/게시판에 글쓰기 등의 기능을 제공하며 api를 이용하여 Angular 웹사이트를 구성할 예정입니다. 따라서 api에 대한 개발 내용은 다음 링크에서 자세히 확인하면 되고, 여기서는 api서버를 실행하고 웹사이트를 구축하는데 주력하여 설명하겠습니다.

http://www.daddyprogrammer.org/post/series/springboot2-make-rest-api/

Angular 개발환경 구축

Angular 환경은 node를 기반으로 하며 npm 명령을 통해 빠르게 개발환경을 구축할 수 있습니다.

node 설치

아래링크에서 LTS 버전을 다운로드 받아 설치합니다.

https://nodejs.org/ko/

typescript, angular-cli 설치

$ npm install -g typescript
$ npm install -g @angular/cli

프로젝트 생성

node를 설치하고 npm 명령을 통해 typescript, angular-cli를 설치하면 Angular 프로젝트를 개발하기위한 환경을 구성할 수 있습니다. ng new 명령을 통해 새로운 angular 프로젝트를 생성합니다. routing 설정은 = yes, 스타일시트 설정은 = CSS를 선택합니다. 터미널에서 code .을 입력하면 vscode에서 프로젝트가 바로 열립니다. code 명령이 작동하지 않으면 vscode pallete(cmd+shift+p)를 열어 path에 code를 적용시켜 주면 됩니다.

$ ng new angular-website
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? CSS
CREATE angular-website/angular.json (3665 bytes)
CREATE angular-website/package.json (1289 bytes)
CREATE angular-website/README.md (1031 bytes)
CREATE angular-website/tsconfig.json (543 bytes)
.... 생략
The file will have its original line endings in your working directory
    Successfully initialized git.
$ cd angular-website
$ code .

vscode에서는 아래와 같은 구조로 프로젝트가 열립니다.

서버 실행

프로젝트를 생성하고 별다른 설정없이 실행하여 기본 테스트 페이지를 확인할 수 있습니다. 간단한 명령으로 프로젝트를 생성하고 빠르게 실행해 볼 수 있는 점은 Angular의 큰 장점중 하나입니다.

$ ng serve
 10% building 3/3 modules 0 activei 「wds」: Project is running at http://localhost:4200/webpack-dev-server/
i 「wds」: webpack output is served from /
i 「wds」: 404s will fallback to //index.html

chunk {main} main.js, main.js.map (main) 11.5 kB [initial] [rendered]
chunk {polyfills} polyfills.js, polyfills.js.map (polyfills) 251 kB [initial] [rendered]
chunk {runtime} runtime.js, runtime.js.map (runtime) 6.09 kB [entry] [rendered]
chunk {styles} styles.js, styles.js.map (styles) 16.3 kB [initial] [rendered]
chunk {vendor} vendor.js, vendor.js.map (vendor) 4.1 MB [initial] [rendered]
Date: 2019-08-19T12:31:09.016Z - Hash: a2ac59fc1786b5eb92e5 - Time: 14780ms
** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ **
i 「wdm」: Compiled successfully.

기본 생성된 페이지 확인

기본설정으로 서버를 실행하면 localhost:4200에서 화면을 볼 수 있습니다.

Material UI 설치

material ui를 설치하면 웹 개발시 준수한 스타일의 ui 컴포넌트를 사용할 수 있습니다. 기본적인 사용법은 다음 링크에서 확인하십시요.

https://material.angular.io/guide/getting-started

$ npm install --save @angular/material @angular/cdk @angular/animations
+ @angular/cdk@8.1.3
+ @angular/material@8.1.3
+ @angular/animations@8.2.2
added 3 packages from 1 contributor, updated 1 package and audited 17101 packages in 19.322s
found 0 vulnerabilities

프로젝트에 Material 테마 추가

프로젝트에 적용될 material 테마를 세팅합니다. 테마는 총 4개를 지원하며 여기서는 그중 indigo-pink.css를 사용하겠습니다. 다른 테마 정보는 아래 링크에서 확인 하도록 합니다.

https://material.angular.io/guide/theming

// src/styles.css 
@import "~@angular/material/prebuilt-themes/indigo-pink.css";

Material Module 추가

material ui를 사용하기 위해서는 사용할 module을 등록해 줘야 합니다. 해당 설정은 app.module.ts에 설정해도 되지만, material module에는 추가되는 내용이 많으므로 app.module.ts가 비대해질수 있습니다. 그러므로 material module을 개별 파일로 구성하고 app.module.ts에 import해 사용하겠습니다.

src/app 하위에 modules 디렉토리를 생성하고 ng generate module 명령으로 모듈을 생성합니다. 약자로 ng g m 모듈명 으로 커맨드를 실행해도 됩니다.

$ cd src/app
$ mkdir modules
$ cd modules
$ ng g m material
CREATE src/app/modules/material/material.module.ts (192 bytes)

생성된 module 파일을 다음과 같이 수정합니다.

// material.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
  MatSidenavModule,
  MatToolbarModule,
  MatIconModule,
  MatListModule,
  MatCardModule,
  MatButtonModule,
  MatTableModule,
  MatInputModule,
  MatDialogModule,
  MatSliderModule
} from '@angular/material';

@NgModule({
  imports: [
    CommonModule,
    MatCardModule,
    MatInputModule,
    MatButtonModule,
    MatTableModule,
    MatSidenavModule,
    MatToolbarModule,
    MatIconModule,
    MatListModule,
    MatDialogModule,
    MatSliderModule
  ],
  exports: [
    MatCardModule,
    MatInputModule,
    MatButtonModule,
    MatTableModule,
    MatSidenavModule,
    MatToolbarModule,
    MatIconModule,
    MatListModule,
    MatDialogModule,
    MatSliderModule
  ],
  declarations: []
})
export class MaterialModule { }

모듈을 생성하면 app.module.ts의 imports에 추가해야 다른 컴포넌트에서 사용할수 있습니다. 다음과 같이 추가합니다. 이때 BrowserAnimationsModule, MaterialModule 두개를 추가합니다.

// app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MaterialModule } from './modules/material/material.module';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    MaterialModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

프로젝트에 Material Icon 추가

Material에서는 Icon도 기본으로 제공합니다. 관련 Icon정보는 다음 링크를 확인하면 됩니다.

https://material.io/resources/icons/?icon=delete_forever&style=baseline

Icon의 경우는 npm install 아닌 css를 추가하는 방식으로 사용합니다. 아래처럼 index.html의 head 태그에 스타일 주소를 추가합니다.

// src/index.html
<head>
  ... 생략
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>

flex-layout 설치

반응형 웹 구현을 위해 flex-layout을 설치합니다.

$ npm install @angular/flex-layout@latest --save

app.module.ts에 flex-layout 모듈을 등록합니다.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MaterialModule } from './modules/material/material.module';
import { FlexLayoutModule } from '@angular/flex-layout';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    MaterialModule,
    FlexLayoutModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

hammer.js 설치

모바일에서는 제스쳐 기능이 필요할수 있습니다. 해당 기능을 제공하는 hammer.js를 설치합니다. 웹 전용이면 따로 설치는 필요없습니다.

$ npm install --save hammerjs

src/main.ts에 hammer.js를 import 합니다.

// src/main.ts
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import 'hammerjs';

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));

기본적인 웹 레이아웃 구성

위에서 한 설정을 바탕으로 header, footer, 모바일용 왼쪽 메뉴를 가진 화면을 구성해 보겠습니다.

home 화면 생성

웹사이트 최초 접근시 표시할 기본 화면을 생성합니다. –flat 옵션을 주면 디렉토리를 생성하지 않습니다. 여기서 home은 인덱스 화면이므로 따로 디렉토리를 생성하지 않겠습니다. –spec false 옵션을 사용하면 *.spec파일을 생성하지 않습니다.

$ cd src/app
$ mkdir component
$ cd component
$ ng g c home --flat --spec false 

라우팅에 index 경로 추가

angular에서는 리소스(URL) 관리를 위해 Routing 파일을 작성해야 합니다. 라우팅 파일 내에는 특정 path로 접근했을때 어떤 컴포넌트를 실행하여 화면을 구성할지 정보가 나열되어 있습니다. home 컴포넌트를 생성하였으므로 아래와 같이 라우팅 정보를 등록합니다.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './component/home.component';

const routes: Routes = [
  {path: '', component: HomeComponent}
];

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

app.component.html 수정

<router-outlet></router-outlet> 부분만 남기고 내용을 모두 삭제합니다. router-outlet은 주소에 따라 해당 리소스의 내용(컴포넌트)으로 채워집니다. Angular는 SPA(Single Page Application) 특성을 가지고 있는데, 내용이 변경되어도 페이지 전체를 리프레시 하지 않고 변경되는 부분만 router-outlet에 로드함으로서 SPA를 구현하게 됩니다.

// app.component.html
<router-outlet></router-outlet>

이제 서버를 구동하고 localhost:4200에 접속하면 homeComponent의 내용이 브라우저에 노출 됩니다.

$ ng serve

레이아웃 구성

app.component.html 수정

아래 html은 flex-layout 및 material ui를 이용하여 모바일 및 일반 브라우저를 다 수용할수 있는 반응형 웹을 구성한 것입니다. 주의깊게 봐야할 부분은 fxHide.gt-xs입니다. 특정사이즈 이하로 내려가면, 즉 모바일 브라우저에서 구동할 경우 header 메뉴가 사라지게 됩니다. 일반 브라우저일때는 가로 사이즈가 넉넉하므로 header 메뉴가 노출되지만 모바일 브라우저일때는 헤더 메뉴가 사라지고 좌측 버튼을 눌러서 왼쪽 메뉴를 호출하도록 ui가 제공됩니다.

// app.component.html
<div class="wrapper">
  <mat-sidenav-container>
      <mat-sidenav  #sidenav role="navigation">
        <mat-nav-list>
            <a mat-list-item>
              <mat-icon class="icon">input</mat-icon>
              <span class="label">로그인</span>
            </a>
            <a mat-list-item>
              <mat-icon class="icon">home</mat-icon>
                <span class="label">홈</span>
            </a>
            <a mat-list-item>
              <mat-icon class="icon">person</mat-icon>
                <span class="label">내정보</span>
            </a>
            <a mat-list-item>
              <mat-icon class="icon">dashboard</mat-icon>
              <span class="label">게시판</span>
            </a>
            <a mat-list-item>
              <mat-icon class="icon">input</mat-icon>
              <span class="label">로그아웃</span>
            </a>
        </mat-nav-list>
      </mat-sidenav>
      <mat-sidenav-content>
        <mat-toolbar color="primary">
         <div fxHide.gt-xs>
           <button mat-icon-button (click)="sidenav.toggle()">
            <mat-icon>menu</mat-icon>
          </button>
        </div>
         <div>
          <a routerLink="/">
            <mat-icon class="icon">home</mat-icon>
            <span class="label">홈</span>
          </a>
         </div>
         <div fxFlex fxLayout fxLayoutAlign="flex-end" fxHide.xs>
            <ul fxLayout fxLayoutGap="20px" class="navigation-items">
                <li>
                  <a>
                    <mat-icon class="icon">input</mat-icon>
                    <span  class="label">로그인</span>
                    </a>
                </li>
                <li>
                  <a>
                    <mat-icon class="icon">person</mat-icon>
                    <span class="label">내정보</span>
                  </a>
                </li>
                <li>
                  <a>
                    <mat-icon class="icon">dashboard</mat-icon>
                    <span class="label">게시판</span>
                  </a>
                </li>
                <li>
                  <a>
                    <mat-icon class="icon">input</mat-icon>
                    <span class="label">로그아웃</span>
                  </a>
                </li>
            </ul>
         </div>
        </mat-toolbar>
        <main>
          <router-outlet></router-outlet>
        </main>
      </mat-sidenav-content>
    </mat-sidenav-container>
    <footer>
      <div>
        <span><a href="http://www.daddyprogrammer.org">HappyDaddy's Angular Website</a></span>
      </div>
      <div>
        <span>Powered by HappyDaddy ©2018~2019. </span>
        <a href="http://www.daddyprogrammer.org">Code licensed under an MIT-style License.</a>
      </div>
    </footer>
</div>

app.component.css 수정

CSS의 기능은 주석으로 대체하였습니다. css에 적용한 내용은 app.component.html에 적용이 됩니다.

  /* 세로 컨텐츠들의 반응형 처리 */
  .wrapper {
    display: flex;
    flex-direction: column;
    height: 100%;
  }
  
  /* 메뉴 반응형 처리 */
  mat-sidenav-container {
    display: flex;
    flex: 1;
  }
  
  /* 상단 메뉴 꽉차도록 처리 */
  mat-sidenav-content {
    width: 100%
  }
  
  /* 왼쪽 메뉴 최소 가로 사이즈 지정 */
  mat-sidenav {
    min-width: 200px;
  }
  
  /* 메뉴의 anchor 태그 스타일 지정 최초는 흰색, 클릭시 회색으로 변경 */
  a {
    text-decoration: none;
    color: white;
  }
  
  a:hover,
  a:active {
    color: lightgray;
  }
  
  /* 상단메뉴 스타일 지정. 마우스오버시 커서 형태 지정 */
  .navigation-items {
    list-style: none;
    padding: 0;
    margin: 0;
    cursor: pointer;
  }
  
  /* 상단 메뉴 아이콘 스타일 지정 */
  .icon {
    display: inline-block;
    height: 25px;
    margin-bottom: 2px;
    margin-right: 15px;
    vertical-align: middle;
    width: 15%;
  }
  
  /* 풋터 스타일 지정 */
  footer {
    padding: 10px 0 10px 0;
    font-size: 12px;
    line-height: 20px;
    background: #3f51b5;
    color: #fff;
    text-align: center;
  }

style.css 수정

모든 컴포넌트에 전역으로 적용할 스타일의 경우엔 style.css에 적용하면 됩니다.

......... 생략
html, body { /* 세로로 컨텐츠를 꽉 채우도록, 컨텐츠에 여백은 0 으로 */
    height: 100%;
    margin: 0;
}

반응형 웹페이지 확인

서버를 실행하고 크롬 브라우저에서 localhost:4200을 열어 화면을 확인 합니다. 상단 헤더와 하단 풋터 그리고 중앙에 HomeComponent 내용이 잘 표시되는것을 확인 할 수 있습니다.

이번에는 모바일 화면을 확인하기 위해 크롬의 개발자 모드를 실행하고 아래와 같이 빨간 테두리 부분을 클릭합니다.

클릭시 헤더 메뉴는 사라지고 왼쪽에 좌측 메뉴를 호출할 수 있도록 버튼이 생기면서 UI가 변경되는것을 확인 할 수 있습니다. 풋터의 경우 가로가 줄어들면서 컨텐츠가 표시됩니다.

메뉴버튼을 누르면 아래와 같이 왼쪽 메뉴가 펼쳐집니다. 일반 브라우저의 헤더에 표시하던 메뉴 내용을 좌측 메뉴를 통해 사용할 수 있게 되었습니다.

여기까지 진행하면 기본 화면 구성이 완료됩니다. 다음 장부터는 각각의 메뉴에 대한 화면 구현 실습을 진행해보겠습니다.

실습에 사용한 소스는 아래 Github 링크에서 확인 가능합니다.

https://github.com/codej99/angular-website.git – feature/initializelayout 브랜치

Spring Boot2로 백엔드 서버(RestAPI) 구축하기 시리즈

연재글 이동
[다음글] Spring Rest api + Angular framework로 웹사이트 만들기(2) – 로그인/가입(HttpClient, Proxy, Validation)