JSライブラリ > Angular2 †環境構築 †準備 †node 6.9.x かつ npm 3.x.x が必要なので、必要に応じてインストール、バージョンアップする。 nodejs のインストール、バージョンアップ †Angular CLI のインストール †npm install -g @angular/cli アップデート †npm uninstall -g @angular/cli npm cache clean npm install -g @angular/cli@latest プロジェクトの作成 †ng new my-app ローカルPC上でサーバ起動 †ng serve ビルド †ビルドコマンド †ng build オプション ビルドオプション †
Angular CLI メモ †ng new my-app // my-appプロジェクトの作成 ng g component hoge // hogeコンポーネントの作成 ng g service hoge // hogeサービスの作成 ng test // jasmin + karmaテスト。 ng lint // Typescriptのtslintを実行。記法、エラーのチェック。 ng e2e // e2e(End to End)テスト Component †Angular2 では各ページをや部品を Component として定義する。 Componentの生成 †ng generate component hello Componentの配置 †app.component.html などに以下の通り記載 <app-コンポーネント名></app-コンポーネント名> もしくは PATH定義と共にリンクやコードから切り替える(後述を参照) 例) <!-- headerコンポーネントを配置 --> <app-header></app-header> <!-- ルーターがコンポーネントを表示する場所 --> <div id="content"> <router-outlet></router-outlet> </div> <!-- footerコンポーネントを配置 --> <app-footer></app-footer> Component の継承 †親コンポーネント ( base/base.component.ts ) . . export class BaseComponent implements OnInit { loading = false; . . 子コンポーネント( child1/child1_component.ts ) . . import { BaseComponent } from '../base/base.component'; . . export class Child1Component extends BaseComponent implements OnInit { books = []; ngOnInit() { this.loading = true; // ← 親コンポーネントで定義した変数にアクセス const params = { 'var1' : 'ABC' }; this.api.getBooks( params, (results) => { if (results.items) { this.books = results.items; } this.loading = false;// ← 親コンポーネントで定義した変数にアクセス }, (error) => { this.loading = false;// ← 親コンポーネントで定義した変数にアクセス } ); } } 子コンポーネントのHTML ( child1/child1_component.html ) . . <!-- loading 中は loading コンポーネントを表示 --> <app-loading *ngIf="loading"></app-loading> 環境変数を利用する †環境の定義 †.angular-cli.json の app.environments 配下に環境名を追加し、ファイルを用意する。 { . . "apps": [ { . . "environments": { "dev": "environments/environment.ts", "prod": "environments/environment.prod.ts", "test": "environments/environment.test.ts" <-- test環境を追加 } } ], 環境変数の定義 †environments 配下の environment.環境名.ts というファイルで環境毎の値を定義する。 例) src/environments/environment.test.ts export const environment = { production: false, apiUrl: "http://xxx.xxx.xxx/api/" }; 環境変数の利用 †サービスからの利用例( src/app/api-base.service.ts ) . . import { environment } from '../environments/environment'; . . @Injectable() export class ApiBaseService { doGet (url: string, params: Object, callback: Function, errorCallback: Function) { url = environment.apiUrl + url; . . } 環境を指定してビルド †ng build --env=test 環境を指定してローカルサーバ起動 †ng serve -e test データバインディング †app.module.ts . . import { FormsModule } from '@angular/forms'; // 追加 . . @NgModule({ . . imports: [ . . FormsModule, // 追加 . . bind1.component.html <div style="padding:10px;"> 片方向データバインディング <span>value1 : {{value1}}</span> </div> <div style="padding:10px;"> 双方向データバインディング ※厳密には プロパティ/イベントの片方向バインディングを同時に指定する事により実現。 <input type="text" [(ngModel)]="value1" > </div> <div style="padding:10px;"> イベントバインディング <button (click)="clickButton1()">テスト</button> </div> <div style="padding:10px;"> プロパティバインディング <button [disabled]="isDisabled">テスト</button> </div> bind1.component.ts export class Bind1Component implements OnInit { isDisabled = false; value1 = 'Test'; clickButton1(){ this.value1 = 'TEST2'; this.isDisabled = !this.isDisabled; } ヘッダとフッタ †<p>ヘッダー</p> <router-outlet></router-outlet> <p>フッター</p> ※ router-outlet の箇所にコンポーネントが埋め込まれる ログイン認証などがあるケース等でヘッダやメニューの内容を動的に変更したい場合は、コンポーネント化する方が良い。 <app-header></app-header> <router-outlet></router-outlet> <app-footer></app-footer> ページ切り替え †コンポーネントの作成 †ng g component page1 ng g component page2 URLとコンポーネントの関連を定義 †app.module.ts . import { RouterModule } from '@angular/router'; . import { Page1Component } from './page1/page1.component'; import { Page2Component } from './page2/page2.component'; . . @NgModule({ declarations: [ AppComponent, . . ], imports: [ BrowserModule, RouterModule.forRoot([ { path: 'page1', component: Page1Component }, { path: 'page2', component: Page2Component } ], { useHash: true }) ], . . }) リンクによる切り替え †<a routerLink="/page1" routerLinkActive="active">ページ1に切り替え</a> <a routerLink="/page2" routerLinkActive="active">ページ2に切り替え</a> コードから切り替え †https://codezine.jp/article/detail/9887 page1.component.html <button (click)="changePage()">ページ2に切り替え</button> page1.component.ts import { Router } from '@angular/router'; constructor(private router:Router) { } changePage() { this.router.navigate(['./page2']); } 画面遷移時のパラメータ †PATH定義 †app.module.ts . . @NgModule({ declarations: [ AppComponent, . . Page3Component, ], imports: [ BrowserModule, HttpModule, RouterModule.forRoot([ . . { path: 'page3/:id', component: Page3Component } ], { useHash: true }) ], providers: [], bootstrap: [AppComponent] }) パラメータ付きで画面切り替え †page1.component.ts import { Router } from '@angular/router'; constructor(private router:Router) { } changePage() { this.router.navigate(['./page3', 1234, {'msg': 'Hello!'} ]); // page3/1234 に遷移 } パラメータの受け取り †page3.component.ts import { ActivatedRoute } from '@angular/router'; import { Params } from '@angular/router'; . . export class Page3Component implements OnInit { constructor(private route:ActivatedRoute) { } ngOnInit() { console.log('page3 init'); this.route.params.forEach((params: Params) => { console.log(params['id']); // 1234 console.log(params['msg']); // Hello! }); } } 非同期通信 †参考: https://codezine.jp/article/detail/10009 素でComponentに実装する場合 †list1/list1.component.html <table id="books" class="books table" style="width:100%;"> <thead> <th>No</th> <th>Isbn</th> <th>Title</th> <th>Price</th> <th>Date</th> </thead> <tbody> <tr *ngFor="let book of books; let i = index;" (click)="onClick(i)" (dblclick)="onDblClick(i)"> <td>{{i+1}}</td> <td>{{book.isbn}}</td> <td>{{book.title}}</td> <td>{{book.price}}</td> <td>{{book.date}}</td> </tr> <tr *ngIf="nodata"> <td colspan="5" style="text-align:center;">データはありません</td> </tr> </tbody> </table> list1/list1.component.ts // import { Http, Response, Headers, RequestOptions } from '@angular/http'; // 非推奨 import { HttpClient } from '@angular/common/http'; . . export class List1Component implements OnInit { nodata = false; loading = false; books = []; constructor(protected http: HttpClient) { } ngOnInit() { this.loading = true; // this.nodata = false; const httpGetObservable = this.http.get('https://xxx.xxx.xxx/api/books/'); httpGetObservable.subscribe( res => { this.nodata = true; /* @angular/http を使用する場合(非推奨) if (res.text()) { var results = JSON.parse(res.text()); if (results.items) { this.books = results.items; } if (this.books.length == 0) { this.nodata = true; } } */ const result: any = res; if (result.items) { this.books = result.items; this.loading = false; if (this.books.length > 0) { this.nodata = false; } } }, error => { console.error(error.status + ':' + error.statusText); } ); } } 通信処理をサービスに分離する †TODO: RxJS
Observable
environments/environment.ts export const environment = { production: false, apiUrl: "http://xxx.xxx.xxx/api/" }; api-base.service.ts import { Injectable } from '@angular/core'; // import { Http, Response, Headers, RequestOptions } from '@angular/http'; // 非推奨 import { HttpClient } from '@angular/common/http'; import { HttpHeaders } from '@angular/common/http'; import { HttpParams } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; import { environment } from '../environments/environment'; import 'rxjs/add/observable/throw'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/catch'; @Injectable() export class ApiBaseService { vars = {}; // constructor (private http: Http) { constructor (private http: HttpClient) { console.log('ApiBaseService constructor!'); } doGet (url: string, params: Object, callback: Function, errorCallback: Function) { let apiUrl = environment.apiUrl + url; // クエリストリングの組み立て let delimiter = '?'; for (const key in params) { if (typeof(key) === 'string' && params[key]) { apiUrl = apiUrl + delimiter + key + '=' + params[key]; delimiter = '&'; } } // 追加ヘッダ const options = { headers: { 'x-text-header1': 'test123' } }; // const httpGetObservable = this.http.get(apiUrl, params); const httpGetObservable = this.http.request('get', apiUrl, options); httpGetObservable.subscribe( res => { if (callback) { callback(res); } }, error => { if (errorCallback) { errorCallback(error); } } ); } doPost (url: string, params: Object, callback: Function, errorCallback: Function) { url = environment.apiUrl + url; // 追加ヘッダ、ボディ部のデータ指定 const options = { headers: { 'x-text-header1': 'test123' }, body: JSON.stringify(params) }; // const httpGetObservable = this.http.post(url, params); const httpGetObservable = this.http.request('post', url, options); httpGetObservable.subscribe( res => { if (callback) { callback(res); } }, error => { if (errorCallback) { errorCallback(error); } } ); } } api.service.ts import { Injectable } from '@angular/core'; import { ApiBaseService } from './api-base.service'; import 'rxjs/add/observable/throw'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/catch'; @Injectable() export class ApiService extends ApiBaseService { getBooks(params:Object, callback:Function, errorCallback:Function) { this.doGet('books/', params, callback, errorCallback); } } app.module.ts . . import { ApiService } from './api.service'; . . @NgModule({ . . providers: [ ApiService ], . . }) list2/list2.component.ts import { Component, OnInit } from '@angular/core'; import { ApiService } from '../api.service'; import { BaseComponent } from '../base/base.component'; @Component({ selector: 'app-list2', templateUrl: './list2.component.html', styleUrls: ['./list2.component.css'] }) export class List2Component extends BaseComponent implements OnInit { books = []; constructor(private api: ApiService) { } ngOnInit() { this.loading = true; var params = { 'var1' : 'ABC' }; this.api.getBooks( params, (results)=>{ if (results.items) { this.books = results.items; } this.loading = false; }, (error)=>{ this.loading = false; } ); } } リロード対応 †その1 ... useHash を使用する @NgModule({ imports: [ RouterModule.forRoot(routes, { useHash: true }) ], . . URL が http://xxx.xxx.xxx/#/page1 のようになる為、リロードしても index.html がロードされる。 jQueryの使用 †npm install jquery --save npm install @types/jquery --save-dev xxxx.component.ts import * as $ from 'jquery'; . . 以上で $('xxxx') 等で普通に使用できる。 bootstrapの使用 †普通に npm install して使おうとしたらエラーになった。 失敗手順 †npm install bootstrap --save .angular-cli.json "styles": [ + "../node_modules/bootstrap/dist/css/bootstrap.min.css", "styles.css" ], "scripts": [ "../node_modules/jquery/dist/jquery.min.js", + "../node_modules/bootstrap/dist/js/bootstrap.min.js" ], で ng serve すると以下のエラーになる。 ng serve . . ERROR in ./node_modules/css-loader?{"sourceMap":false,"importLoaders":1}!./node_modules/postcss-loader/lib?{"ident":"postcss","sourceMap":false}!./node_modules/bootstrap/dist/css/bootstrap.min.css Module build failed: BrowserslistError: Unknown browser major 対応 †bootstrap 4.0.0-beta.2 だとOKらしいので、4.0.0-beta.2 に変更して npm install し直せばOK。 package.json を修正 - "bootstrap": "^4.0.0", + "bootstrap": "4.0.0-beta.2", 再インストール npm install でOK。 対応その2 †bootstrap をAngularのコンポーネントから触る事ないなら(というか触る事ないので)、assets に放り込んでしまえば早い。 index.html <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>MyApp2</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> + <link rel="stylesheet" type="text/css" href="assets/css/bootstrap.min.css"> </head> <body> <app-root></app-root> + <script src="assets/js/jquery-3.3.1.min.js"></script> + <script src="assets/js/bootstrap.min.js"></script> </body> </html> で、bootstrap の npm モジュールは削除してもOK。( .angular-cli.json からも消してOK ) 対応その3 †jQuery だけは Angularで使う。という場合は、こんな感じ。 ・上記の index.html の追記は削除して。 で jQueryのインストール npm install jquery -save bootstrap を assets とは別のディレクトリにダウンロードする。 mkdir -p src/assets-min/css mkdir -p src/assets-min/js wget https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css -O src/assets-min/css/bootstrap.min.css wget https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js -O src/assets-min/js/popper.min.js wget https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js -O src/assets-min/js/bootstrap.min.js .angular-cli.json "styles": [ + "assets-min/css/bootstrap.min.css", "styles.css" ], "scripts": [ + "../node_modules/jquery/dist/jquery.min.js", + "assets-min/js/popper.min.js", + "assets-min/js/bootstrap.min.js" ], コーディングルールなど †Typescript の記法チェック( ng lint ) まで行うなら、以下のルールは守っておいた方が良い。 文字列の括りはシングルクォーテーション †import { ActivatedRoute } from "@angular/router"; // NG import { ActivatedRoute } from '@angular/router'; // OK コロンの後はスペース1個分あける †constructor(private http:Http, private router:Router, private route:ActivatedRoute) { // NG constructor(private http: Http, private router: Router, private route: ActivatedRoute) { // OK 不要なスペースは削除(スペースは1個) †import { ActivatedRoute } from '@angular/router'; // NG ... from の前のスペースが2個ある import { ActivatedRoute } from '@angular/router'; コメント行の最初の1文字目はスペースにする †//NG // OK 行末に不要なスペースを記述しない †let var1 = ''; 値の変更を行わない変数は const とする †for (const key in params) { . . } 演算子の間などにはスペースを1個入れる †const var1 = "ABC"; const var2 = var1+'001'; // NG const var3 = var1 + '001'; // OK 参考 †Angular2 CodeZine「次世代Webアプリケーションフレームワーク「Angular」の活用」連載一覧 ルーター(URLとリンクでコンポーネント表示を切り替える) コンポーネントのライフサイクル コンポーネントとモジュール サービスと依存性注入 非同期HTTP通信 |