[[JSライブラリ]] >
* Angular2 [#zc547dec]
#setlinebreak(on);

#contents
-- 関連
--- [[Angular2でFile APIを利用する]]
--- [[Angular2でファイルダウンロード]]
--- [[Angularで国際化対応]]

** 環境構築 [#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定義と共にリンクやコードから切り替える(後述を参照)

例)
#myhtml2(){{
<!-- headerコンポーネントを配置 -->
<app-header></app-header>

<!-- ルーターがコンポーネントを表示する場所 -->
<div id="content">
  <router-outlet></router-outlet>
</div>

<!-- footerコンポーネントを配置 -->
<app-footer></app-footer>
}}

#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 : &#123;&#123;value1&#125;&#125;</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 { 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 })
  ],  
  .
  .
})
}}
#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>&#123;&#123;i+1&#125;&#125;</td>
      <td>&#123;&#123;book.isbn&#125;&#125;</td>
      <td>&#123;&#123;book.title&#125;&#125;</td>
      <td>&#123;&#123;book.price&#125;&#125;</td>
      <td>&#123;&#123;book.date&#125;&#125;</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のインストール
#myterm2(){{
npm install jquery -save
}}

bootstrap を assets とは別のディレクトリにダウンロードする。
※angular-cli.jsonで読み込むので assets にはいらないんで。(後述)

#myterm2(){{
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>)


トップ   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS