vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

AngularとSpringBootでWebページを作ってみたい その2

はじめに

引き続きWebページを作成すべくあれこれ試しています。

今回は、Angularでページを作っていく上で必要になった知識?も合わせてまとめることにします。

node_moduleをインストールし直す

1台のマシンだけで開発している場合は問題がないのですが、
他のマシンにプロジェクトを移したい場合、node_moduleは持っていってもエラーになってしまいます。
(.gitignoreに含まれてもいます)

そのため、プロジェクトを移したあとで「npm install」を行い、node_moduleをインストールし直す必要があります。

インストールされる内容は、プロジェクト直下にあるpackage.jsonのものです。

なお、この中のアプリのバージョン(下記参照)を更新したい場合は、手動で書き直すのでしょうか…?

package.json

{
  "name": "anime-crud-sample",
  "version": "0.0.0",
  "license": "MIT",
〜省略〜

ビルドデータの出力先の変更

以前書きましたが、
Angularで作成したページを実際サーバー上で表示するときは、「ng build」でビルドしたデータを使うことになります。

通常ではプロジェクト直下に作成される「dist」というフォルダの中にビルドしたデータが出力されるのですが、
これを任意の場所に出力するためには、プロジェクト直下にある.angular-cli.jsonを変更します。

.angular-cli.json

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "project": {
    "name": "anime-crud-sample"
  },
  "apps": [
    {
      "root": "src",
      "outDir": "ここを変更する"
〜省略〜

CSS Grid Layoutを使う

以前は下記を再現するには、tableを使用して結合などを利用していました。

f:id:mslGt:20170624021504j:plain

しかし、CSS Grid Layoutを使うことでより簡単に再現できるようになります。

さっそく試してみます。

元のHTML

< div class='top-news-item-area'>
    < div class='top-news-item-date'>2017年6月20日< /div>
    < div class='top-news-item-title'>ニュースだよ< /div>
    < div class='top-news-item-article'>中身です< /div>
< /div>

top-news-item-area(クラスを持つdiv)の中に、3つのdivが含まれています。

このHTMLに対して設定していきます。

top-news-item-area

まず、このLayoutが何行何列なのか、またそれぞれの高さ・幅を、
3つのdivの親であるtop-news-item-areaに設定します。

.top-news-item-area{
    display: grid;                  /* Grid layoutを有効にする */
    grid-template-rows: 50px 100px; /* 左から順に1行目、2行目の高さを指定 */
    grid-template-columns: 20% 1fr; /* 左から順に1列目、2列目の幅を指定 */
}
  • 今回は2行2列のため、grid-template-rowsとgrid-template-columnsにはそれぞれ2つずつ値を設定しています。
    行・列数を増やす場合は更に値を追加していきます。
  • grid-template-columnsで指定している「1fr」は、1列目で指定した幅の残りを2列目に全て割り当てるために使用しています。

top-news-item-date・top-news-item-title・top-news-item-article

Layoutの行・列数と高さ・幅が決まれば、あとは子となるdivの配置を決めるだけです。

.top-news-item-date{
    grid-row: 1 / 3;                /* 1行目から3行目まで(つまり2行全て)を指定する */
    grid-column: 1 / 2;             /* 1列目から2列目まで(つまり1列目)を指定する */
    border: 1px solid #000;
}
.top-news-item-title{
    grid-row: 1;                    /* 1行目を指定する */
    grid-column: 2;                 /* 2列目を指定する */
    border: 1px solid #000;
}
.top-news-item-article{
    grid-row: 2 / span 1;           /* 2行目から1行分(つまり2行目のみ)を指定する */
    grid-column: 2;                 /* 2列目を指定する */
    border: 1px solid #000;
}
  • top-news-item-titleのように1行または1列だけを指定する場合は、「/」以降の数字を省略できます。
  • 「/」以降を省略した場合、top-news-item-articleのgrid-rowと同じように、指定の行(または列)から1行(列)分が設定されます。

結果

ここまでのものを表示すると、下記のようになります。

f:id:mslGt:20170624021448j:plain

あとは角丸にしたり、marginなどで幅を調整したりすればOKです。

課題

ChromeFirefoxだけであれば問題なく表示できるのですが、
少なくともAngularでCSS Grid Layoutを使うと、警告出る場合があります。

IEでは、「grid-row: 1 / 3;」のように「grid-row(またはgrid-column)」で「/」以降の数字を指定できないためです。

手元にIEがないため未確認ですが、下記のようにすればIEにも対応できるようです。

.top-news-item-area{
    display: grid;
    display: -ms-grid;
    grid-template-rows: 50px 100px;
    grid-template-columns: 20% 1fr;
    -ms-grid-rows: 50px 100px;
    -ms-grid-columns: 150px 1fr;
}
.top-news-item-date{
    -ms-grid-row: 1;
    -ms-grid-row-span: 2;
    -ms-grid-column: 1;
    -ms-grid-column-span: 1;
    grid-row: 1 / 3;
    grid-column: 1 / 2;
    border: 1px solid #000;
}
.top-news-item-title{
    -ms-grid-row: 1;
    -ms-grid-column: 2;
    grid-row: 1;
    grid-column: 2;
    border: 1px solid #000;
}
.top-news-item-article{
    -ms-grid-row: 2;
    -ms-grid-column: 2;
    grid-row: 2;
    grid-column: 2;
    border: 1px solid #000;   
}

ただし警告は消えません。。。

まぁこれに関する警告をでないようにすれば(たぶんtslint.json)良いのでしょうが、
ちょっと気持ち悪いですね。。。

RxJSでJSONデータ取得

さて、CSS Grid Layoutで作ったフォームに、データを入れてみます。

Spring bootでGETリクエストをした時にJSONを返すようにしておき、
RxJSを使ってそれを受け取ってHTMLに渡す、という処理を行います。

Spring boot

今回は一旦GETリクエストがあった場合に、JSONデータを固定値で生成して返す、という処理をすることにします。

ということで、「spring-boot-starter-web」を含めてSpring bootのプロジェクトを作成し、下記のクラスを追加します。

News.java

public class News {
    public int id;
    public String createdDate;
    public String title;
    public String article;
}
  • JSONデータを返すための値を保持するクラスです。

CrudSampleController.java

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;

@RestController
public class CrudSampleController {
    @GetMapping("/topnewslist")
    public List newsList(){
        List newsItems = new ArrayList<>();
        News newsItem1 = new News();
        newsItem1.id = 0;
        newsItem1.createdDate = "2017.06.22";
        newsItem1.title = "うどん一杯目";
        newsItem1.article = "きつねうどん";
        newsItems.add(newsItem1);

        News newsItem2 = new News();
        newsItem2.id = 1;
        newsItem2.createdDate = "2017.06.23";
        newsItem2.title = "うどん二杯目";
        newsItem2.article = "味噌煮込みうどん";
        newsItems.add(newsItem2);

        return newsItems;
    }
}
  • Controllerクラスです。
  • localhost:8080/topnewslistにGETリクエストがあった場合にnewsList()が呼び出され、
    Newsクラスのリストを返します。
    (リストやクラスを戻り値にすると、自動でJSONとして受け取ることができるの便利ですね)

RxJS

フロント側に戻ります。

AngularのプロジェクトにはデフォルトでRxJSが含まれているため、
インストールなどは特にしなくてもそのまま使用できます。

GETリクエストを送るためには、app.module.tsにHTTPModuleを追加する必要があります。

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import {HttpModule} from '@angular/http';

import { AppComponent } from './app.component';
import { SlideAnimeComponent } from './slide-anime/slide-anime.component';
import { FormsModule } from '@angular/forms';
import { GlobalHeaderComponent } from './global-header/global-header.component';
import { ContactPageComponent } from './contact-page/contact-page.component';
import { routing } from './app.routing';
import { MainPageComponent } from './main-page/main-page.component';
import { TopBannerComponent } from './top-banner/top-banner.component';
import { TopNewsComponent } from './top-news/top-news.component';
import { MenulistPageComponent } from './menulist-page/menulist-page.component';

@NgModule({
  declarations: [
    AppComponent,
    SlideAnimeComponent,
    GlobalHeaderComponent,
    ContactPageComponent,
    MainPageComponent,
    TopBannerComponent,
    TopNewsComponent,
    MenulistPageComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    FormsModule,
    HttpModule,
    routing
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

あとはHTTPとRxJSを使ってJSONデータを取得します。

top-news.ts

export interface TopNews {
    id: number;
    createdDate: string;
    title: string;
    article: string;
}
  • サーバー側から受け取ったJSONデータを格納するためのインターフェースです。

top-news.component.ts

import { Component, OnInit } from '@angular/core';
import { TopNews } from '../top-news';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';

@Component({
  selector: 'app-top-news',
  templateUrl: './top-news.component.html',
  styleUrls: ['./top-news.component.css']
})
export class TopNewsComponent implements OnInit {
  newsList: TopNews[];
  
  constructor(private http_: Http) {
    http_.get("/topnewslist")
        .map(response => response.json()) /* response.json()の「()」を忘れないこと */
        .subscribe(gotNews => this.newsList = gotNews);
  }
  ngOnInit() {
  }
}
  • 今回は初回ロード時のみデータを読み込むため、constructorで、DIでインスタンスを受け取ったらそのまま処理を行っています。
  • constructor以外でも「this.http_」でアクセスできるため(なんだか不思議な感じがしますが)、任意のタイミングで実行することも可能です。
  • 「map(response => response.json())」で、response.json()の「()」がついていなくてもエラーにはならないのですが、
    戻り値が変わってしまい、subscribeの時に値が取り出せなくなってしまいます。

注意点

上記コードをng serveを使い、ビルドしない状態で実行するとエラーが発生します。

それは、同一ドメイン以外のURLにアクセスしに行く場合に必要なXMLHttpRequestの設定が抜けており、
ブラウザによってアクセスを禁止されるためです。

ただ今回は、最終的には同じドメインでやり取りをすることになるため、
ng buildでビルドを行い、Spring bootのプロジェクトに組み込んだ状態で確認を行うことにします。

おわりに

今回はほとんど触れられませんでしたが、RxJSももう少し突っ込んで調べてみたいと思います。

参照

Angular

CSS Grid Layout

DI