- 追加された行はこの色です。
- 削除された行はこの色です。
- Angular2 へ行く。
[[JSライブラリ]] >
* Angular2 [#zc547dec]
#setlinebreak(on);
#contents
-- 関連
--- [[Angular2でFile APIを利用する]]
--- [[Angular2でファイルダウンロード]]
** 環境構築 [#k06a1dbf]
#html(<div style="padding-left:10px;">)
*** 準備 [#h9e5a0b7]
#html(<div style="padding-left:10px;">)
node 6.9.x かつ npm 3.x.x が必要なので、必要に応じてインストール、バージョンアップする。
#html(</div>)
*** nodejs のインストール、バージョンアップ [#q8e6cfe8]
#html(<div style="padding-left:10px;">)
[[nodebrew で nodejs を管理する>Node.js#na4b0284]] を参照
#html(</div>)
*** Angular CLI のインストール [#hfe4bab4]
#html(<div style="padding-left:10px;">)
#myterm2(){{
npm install -g @angular/cli
}}
#html(</div>)
*** アップデート [#a8100dc4]
#html(<div style="padding-left:10px;">)
#myterm2(){{
npm uninstall -g @angular/cli
npm cache clean
npm install -g @angular/cli@latest
}}
#html(</div>)
*** プロジェクトの作成 [#j022d407]
#html(<div style="padding-left:10px;">)
#myterm2(){{
ng new my-app
}}
#html(</div>)
*** ローカルPC上でサーバ起動 [#fa0ed94f]
#html(<div style="padding-left:10px;">)
#myterm2(){{
ng serve
}}
#html(</div>)
#html(</div>)
** ビルド [#b16b21bc]
#html(<div style="padding-left:10px;">)
*** ビルドコマンド [#s90ef0c8]
#html(<div style="padding-left:10px;">)
#myterm2(){{
ng build オプション
}}
#html(</div>)
*** ビルドオプション [#z2389519]
#html(<div style="padding-left:10px;">)
|オプション|使用例|説明|h
|--output-path=出力先|ng build --output-path=/var/www/site1 | ファイルの出力先を指定する |
|--bh アプリケーションルート|ng build --bh /dir1/dir2/| 例えば http://example.com/dir1/dir2/ 配下で Angularアプリを動作させる場合|
|--prod|ng build --prod | 本番用に出力。各出力ファイルは minify され、ファイル名の末尾にランダムな文字列が付与される。&br; 例) main.45f2a42eedaf6eab8df0.bundle.js |
|--env=環境名|ng build --prod --env=prod|環境名を指定してビルドする。(左記の場合、環境変数として environment.prod.ts が使用される)|
#html(</div>)
#html(</div>)
** Angular CLI メモ [#ccafa2fe]
#html(<div style="padding-left:10px;">)
#myterm2(){{
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)テスト
}}
#html(</div>)
** Component [#jb0f7daf]
#html(<div style="padding-left:10px;">)
Angular2 では各ページをや部品を Component として定義する。
コンポーネントは html、css、TypeScript で構成され、各処理からそれらを利用する事ができる。
*** Componentの生成 [#cdb645b7]
#html(<div style="padding-left:10px;">)
#myterm2(){{
ng generate component hello
}}
#html(</div>)
*** Componentの配置 [#xc3fe667]
#html(<div style="padding-left:10px;">)
app.component.html などに以下の通り記載
#myhtml2(){{
<app-コンポーネント名></app-コンポーネント名>
}}
もしくは PATH定義と共にリンクやコードから切り替える(後述を参照)
#html(</div>)
*** Component の継承 [#n011a460]
#html(<div style="padding-left:10px;">)
親コンポーネント ( base/base.component.ts )
#mycode2(){{
.
.
export class BaseComponent implements OnInit {
loading = false;
.
.
}}
子コンポーネント( child1/child1_component.ts )
#mycode2(){{
.
.
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 )
#myhtml2(){{
.
.
<!-- loading 中は loading コンポーネントを表示 -->
<app-loading *ngIf="loading"></app-loading>
}}
#html(</div>)
#html(</div>)
** 環境変数を利用する [#d41b49ba]
#html(<div style="padding-left:10px;">)
*** 環境の定義 [#db7a7616]
#html(<div style="padding-left:10px;">)
.angular-cli.json の app.environments 配下に環境名を追加し、ファイルを用意する。
#mycode2(){{
{
.
.
"apps": [
{
.
.
"environments": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts",
"test": "environments/environment.test.ts" <-- test環境を追加
}
}
],
}}
#html(</div>)
*** 環境変数の定義 [#xdc9b927]
#html(<div style="padding-left:10px;">)
environments 配下の environment.環境名.ts というファイルで環境毎の値を定義する。
例) src/environments/environment.test.ts
#mycode2(){{
export const environment = {
production: false,
apiUrl: "http://xxx.xxx.xxx/api/"
};
}}
#html(</div>)
*** 環境変数の利用 [#vc148474]
#html(<div style="padding-left:10px;">)
サービスからの利用例( src/app/api-base.service.ts )
#mycode2(){{
.
.
import { environment } from '../environments/environment';
.
.
@Injectable()
export class ApiBaseService {
doGet (url: string, params: Object, callback: Function, errorCallback: Function) {
url = environment.apiUrl + url;
.
.
}
}}
#html(</div>)
*** 環境を指定してビルド [#c673164c]
#html(<div style="padding-left:10px;">)
#myterm2(){{
ng build --env=test
}}
#html(</div>)
*** 環境を指定してローカルサーバ起動 [#ybf0ed8e]
#html(<div style="padding-left:10px;">)
#myterm2(){{
ng serve -e test
}}
#html(</div>)
#html(</div>)
** データバインディング [#f2e217aa]
#html(<div style="padding-left:10px;">)
app.module.ts
#mycode2(){{
.
.
import { FormsModule } from '@angular/forms'; // 追加
.
.
@NgModule({
.
.
imports: [
.
.
FormsModule, // 追加
.
.
}}
bind1.component.html
#myhtml2(){{
<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
#mycode2(){{
export class Bind1Component implements OnInit {
isDisabled = false;
value1 = 'Test';
clickButton1(){
this.value1 = 'TEST2';
this.isDisabled = !this.isDisabled;
}
}}
#html(</div>)
** ヘッダとフッタ [#v0b06e65]
#html(<div style="padding-left:10px;">)
#myhtml2(){{
<p>ヘッダー</p>
<router-outlet></router-outlet>
<p>フッター</p>
}}
※ router-outlet の箇所にコンポーネントが埋め込まれる
ログイン認証などがあるケース等でヘッダやメニューの内容を動的に変更したい場合は、コンポーネント化する方が良い。
#myhtml2(){{
<app-header></app-header>
<router-outlet></router-outlet>
<app-footer></app-footer>
}}
#html(</div>)
** ページ切り替え [#kaa64d0b]
#html(<div style="padding-left:10px;">)
*** コンポーネントの作成 [#x8fe4804]
#html(<div style="padding-left:10px;">)
#myterm2(){{
ng g component page1
ng g component page2
}}
#html(</div>)
*** URLとコンポーネントの関連を定義 [#e0bb4876]
#html(<div style="padding-left:10px;">)
app.module.ts
#mycode2(){{
.
.
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 })
],
.
.
})
}}
#html(</div>)
*** リンクによる切り替え [#nb8a3776]
#html(<div style="padding-left:10px;">)
#myhtml2(){{
<a routerLink="/page1" routerLinkActive="active">ページ1に切り替え</a>
<a routerLink="/page2" routerLinkActive="active">ページ2に切り替え</a>
}}
#html(</div>)
*** コードから切り替え [#r581f503]
#html(<div style="padding-left:10px;">)
https://codezine.jp/article/detail/9887
https://angular.io/api/router/Router#navigate
page1.component.html
#myhtml2(){{
<button (click)="changePage()">ページ2に切り替え</button>
}}
page1.component.ts
#mycode2(){{
import { Router } from '@angular/router';
constructor(private router:Router) {
}
changePage() {
this.router.navigate(['./page2']);
}
}}
#html(</div>)
#html(</div>)
** 画面遷移時のパラメータ [#b1447a9f]
#html(<div style="padding-left:10px;">)
*** PATH定義 [#o44a9a42]
#html(<div style="padding-left:10px;">)
app.module.ts
#mycode2(){{
.
.
@NgModule({
declarations: [
AppComponent,
.
.
Page3Component,
],
imports: [
BrowserModule,
HttpModule,
RouterModule.forRoot([
.
.
{ path: 'page3/:id', component: Page3Component }
], { useHash: true })
],
providers: [],
bootstrap: [AppComponent]
})
}}
#html(</div>)
*** パラメータ付きで画面切り替え [#v39d74e5]
#html(<div style="padding-left:10px;">)
page1.component.ts
#mycode2(){{
import { Router } from '@angular/router';
constructor(private router:Router) {
}
changePage() {
this.router.navigate(['./page3', 1234, {'msg': 'Hello!'} ]); // page3/1234 に遷移
}
}}
#html(</div>)
*** パラメータの受け取り [#h40618a4]
#html(<div style="padding-left:10px;">)
page3.component.ts
#mycode2(){{
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!
});
}
}
}}
#html(</div>)
#html(</div>)
** 非同期通信 [#s2b63e35]
#html(<div style="padding-left:10px;">)
参考: https://codezine.jp/article/detail/10009
*** 素でComponentに実装する場合 [#g5766842]
#html(<div style="padding-left:10px;">)
list1/list1.component.html
#myhtml2(){{
<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
#mycode2(){{
// 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);
}
);
}
}
}}
#html(</div>)
*** 通信処理をサービスに分離する [#dd2d731a]
#html(<div style="padding-left:10px;">)
#TODO(RxJS, Observable)
environments/environment.ts
#mycode2(){{
export const environment = {
production: false,
apiUrl: "http://xxx.xxx.xxx/api/"
};
}}
api-base.service.ts
#mycode2(){{
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
#mycode2(){{
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
#mycode2(){{
.
.
import { ApiService } from './api.service';
.
.
@NgModule({
.
.
providers: [ ApiService ],
.
.
})
}}
list2/list2.component.ts
#mycode2(){{
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;
}
);
}
}
}}
#html(</div>)
#html(</div>)
** リロード対応 [#f7563fb0]
#html(<div style="padding-left:10px;">)
その1 ... useHash を使用する
#mycode2(){{
@NgModule({
imports: [ RouterModule.forRoot(routes, { useHash: true }) ],
.
.
}}
URL が http://xxx.xxx.xxx/#/page1 のようになる為、リロードしても index.html がロードされる。
#html(</div>)
** jQueryの使用 [#a4dec830]
#html(<div style="padding-left:10px;">)
#myterm2(){{
npm install jquery --save
npm install @types/jquery --save-dev
}}
xxxx.component.ts
#mycode2(){{
import * as $ from 'jquery';
.
.
}}
以上で $('xxxx') 等で普通に使用できる。
#html(</div>)
** bootstrapの使用 [#zc734321]
#html(<div style="padding-left:10px;">)
普通に npm install して使おうとしたらエラーになった。
*** 失敗手順 [#oe629ee7]
#html(<div style="padding-left:10px;">)
#myterm2(){{
npm install bootstrap --save
}}
.angular-cli.json
#mycode2(){{
"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 すると以下のエラーになる。
#myterm2(){{
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
}}
#html(</div>)
*** 対応 [#xa446794]
#html(<div style="padding-left:10px;">)
bootstrap 4.0.0-beta.2 だとOKらしいので、4.0.0-beta.2 に変更して npm install し直せばOK。
package.json を修正
#mycode2(){{
- "bootstrap": "^4.0.0",
+ "bootstrap": "4.0.0-beta.2",
}}
再インストール
#myterm2(){{
npm install
}}
でOK。
*** 対応その2 [#n958b4ff]
#html(<div style="padding-left:10px;">)
bootstrap をAngularのコンポーネントから触る事ないなら(というか触る事ないので)、assets に放り込んでしまえば早い。
index.html
#myhtml2(){{
<!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>
}}
#html(</div>)
で、bootstrap の npm モジュールは削除してもOK。( .angular-cli.json からも消してOK )
*** 対応その3 [#uc8bc849]
#html(<div style="padding-left:10px;">)
jQuery だけは Angularで使う。という場合は、こんな感じ。
・上記の index.html の追記は削除して。
・bootstrap の npm モジュールも削除。( package.json からbootstrapの行を削除して npm install )
で
jQueryのインストールとbootstrap関連ライブラリをダウンロード。
bootstrap は assetsとは別のディレクトリにダウンロード。
jQueryのインストール
#myterm2(){{
npm install jquery -save
}}
bootstrap を assets とは別のディレクトリにダウンロードする。
※angular-cli.jsonで読み込むので assets にはいらないんで。(後述)
#myterm2(){{
npm install jquery -save
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
#mycode2(){{
"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"
],
}}
#html(</div>)
#html(</div>)
** コーディングルールなど [#s3d390cf]
#html(<div style="padding-left:10px;">)
Typescript の記法チェック( ng lint ) まで行うなら、以下のルールは守っておいた方が良い。
*** 文字列の括りはシングルクォーテーション [#s28c634a]
#html(<div style="padding-left:10px;">)
#mycode2(){{
import { ActivatedRoute } from "@angular/router"; // NG
import { ActivatedRoute } from '@angular/router'; // OK
}}
#html(</div>)
*** コロンの後はスペース1個分あける [#w1dfe911]
#html(<div style="padding-left:10px;">)
#mycode2(){{
constructor(private http:Http, private router:Router, private route:ActivatedRoute) { // NG
constructor(private http: Http, private router: Router, private route: ActivatedRoute) { // OK
}}
#html(</div>)
*** 不要なスペースは削除(スペースは1個) [#g0676bdb]
#html(<div style="padding-left:10px;">)
#mycode2(){{
import { ActivatedRoute } from '@angular/router'; // NG ... from の前のスペースが2個ある
import { ActivatedRoute } from '@angular/router';
}}
#html(</div>)
*** コメント行の最初の1文字目はスペースにする [#c40f1bf7]
#html(<div style="padding-left:10px;">)
#mycode2(){{
//NG
// OK
}}
#html(</div>)
*** 行末に不要なスペースを記述しない [#m7b7f419]
#html(<div style="padding-left:10px;">)
#mycode2(){{
let var1 = '';
}}
#html(</div>)
*** 値の変更を行わない変数は const とする [#q81f5955]
#html(<div style="padding-left:10px;">)
#mycode2(){{
for (const key in params) {
.
.
}
}}
#html(</div>)
*** 演算子の間などにはスペースを1個入れる [#nadfeb9f]
#html(<div style="padding-left:10px;">)
#mycode2(){{
const var1 = "ABC";
const var2 = var1+'001'; // NG
const var3 = var1 + '001'; // OK
}}
#html(</div>)
#html(</div>)
** 参考 [#kd1ea843]
#html(<div style="padding-left:10px;">)
Angular2
https://angular.io/
CodeZine「次世代Webアプリケーションフレームワーク「Angular」の活用」連載一覧
https://codezine.jp/article/corner/653
ルーター(URLとリンクでコンポーネント表示を切り替える)
https://codezine.jp/article/detail/9700
コンポーネントのライフサイクル
https://codezine.jp/article/detail/10046
コンポーネントとモジュール
https://codezine.jp/article/detail/9700
サービスと依存性注入
https://codezine.jp/article/detail/9779
非同期HTTP通信
https://codezine.jp/article/detail/10009
#html(</div>)