|
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通信 |