B-Teck!

お仕事からゲームまで幅広く

【JavaScript/AngularJS】今更AngularJSに入門したメモ

AngularJSをサクッと覚える必要があったので勉強したメモ。
生のJSで扱おうと思うとアロー関数使うとうまく動かない箇所があったので、昔ながらの無名関数で書く。
中で呼び出すスクリプト等は学習時に利用した教材に合わせているため古い。
記事中のコードはここにあります

最小限のアプリケーションの作成

AngularJSの仕組みを使って画面に要素を表示するまで。

<!DOCTYPE html>
<!--  ng-appでsampleAppモジュールを関連付け  -->
<html lang="ja" ng-app="sampleApp">
<head>
    <meta charset="UTF-8">
    <title>sample</title>
    <!--  AngularJSの読み込み  -->
    <script src="//code.angularjs.org/1.5.0/angular.min.js"></script>
    <script src="app.js"></script>
    <!--  bootstrapの読み込み  -->
    <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"/>
</head>
<body>
<!--  ng-controllerでsampleControllerを関連付け  -->
<div ng-controller="sampleController">
    <!--  sampleControllerのScopeのmsgを出力  -->
    <h2>{{ msg }}</h2>
</div>
</body>
</html>
// sampleAppモジュールを作成
const sampleApp = angular.module('sampleApp', []);

// sampleAppモジュールにsampleControllerを関連付ける
// AngularJSの機能で$scopeサービスを利用してコントローラーに紐づく変数を定義する
sampleApp.controller('sampleController', ['$scope', function ($scope) {
    $scope.msg = 'Hello, World!';
}]);

ng-modelによる双方向バインディング

操作によって変更された値を即座に画面に反映する双方向バインディングの仕組みがある。
テキストボックスの操作に応じて表示が変化するのを確かめられるサンプル。

<!DOCTYPE html>
<html lang="ja" ng-app="sampleApp">
<head>
    <meta charset="UTF-8">
    <title>sample</title>
    <script src="//code.angularjs.org/1.5.0/angular.min.js"></script>
    <script src="app.js"></script>
    <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"/>
</head>
<body>
<div ng-controller="sampleController">
    <h2>{{ msg }}</h2>
    <!--  ng-modelによるbindingで入力に対応して表示が変わる  -->
    <input type="text" ng-model="msg">
</div>
</body>
</html>

ngRouteを使ったルーティング

AngularJSではURLの#より後ろの文字列をpathとしてルーティングを行うことができる。

<!DOCTYPE html>
<html lang="ja" ng-app="sampleApp">
<head>
    <meta charset="UTF-8">
    <title>sample</title>
    <script src="//code.angularjs.org/1.5.0/angular.min.js"></script>
    <!--  ルーティング用のモジュールを読み込む  -->
    <script src="//code.angularjs.org/1.5.0/angular-route.min.js"></script>
    <script src="app.js"></script>
    <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"/>
</head>
<body>
<!--  ルーティング結果を埋め込む場所をng-viewでマークする  -->
<div ng-view></div>
<!--  切り替えを確認するためにボタンを置く  -->
<a href="#/" class="btn">Sample</a>
<a href="#/sample2" class="btn">Sample2</a>
</body>
</html>
const sampleApp = angular.module('sampleApp', ['ngRoute']);

sampleApp
    .controller('sampleController', ['$scope', function ($scope) {
        $scope.msg = 'Hello, World!';
    }])
    .controller('sampleController2', ['$scope', function ($scope) {
        $scope.msg = 'Hello, NewWorld!';
    }]);


// #/ の場合と、 #/sample2の場合のルーティングを設定する
sampleApp.config(['$routeProvider', function ($routeProvider) {
    $routeProvider
        .when('/', {
            template: '<h2>{{ msg }}</h2>',
            controller: 'sampleController'
        })
        .when('/sample2', {
            template: '<h2>{{ msg }}</h2>',
            controller: 'sampleController2'
        })
}]);

ルーティング時にpathからパラメータを渡す

ルーティングのpathにパターンを定義して、処理の中で値として利用することができる。
パターンはいくつかあるけど、単純なケースのサンプル。

<!DOCTYPE html>
<html lang="ja" ng-app="sampleApp">
<head>
    <meta charset="UTF-8">
    <title>sample</title>
    <script src="//code.angularjs.org/1.5.0/angular.min.js"></script>
    <script src="//code.angularjs.org/1.5.0/angular-route.min.js"></script>
    <script src="app.js"></script>
    <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"/>
</head>
<body>
<div ng-view></div>
<a href="#/" class="btn">Sample</a>
<a href="#/sample2" class="btn">Sample2</a>
</body>
</html>
const sampleApp = angular.module('sampleApp', ['ngRoute']);

sampleApp.config(['$routeProvider', function ($routeProvider) {
    // #/ の場合と、 #/{何らかの文字列} の場合のルーティングを設定する
    // この定義の場合、#/{何らかの文字列}における何らかの文字列がparamにバインドされてControllerに渡される
    $routeProvider
        .when('/', {
            template: '<h2>{{ msg }}</h2>',
            controller: 'sampleController'
        })
        .when('/:param', {
            template: '<h2>{{ msg }}</h2>',
            controller: 'sampleController'
        })
}]);

// $routeParamsを利用してpathに含まれる文字列(paramに対応するもの)をViewに反映する
// 何も渡されなかったときはデフォルトの文字列を表示する
sampleApp
    .controller('sampleController',['$scope', '$routeParams', function ($scope, $routeParams) {
        $scope.msg = $routeParams.param || 'Hello, World!';
    }]);

templateを別のファイルに切り出す

ルーティングのサンプルでは直接記載したtemplateを別ファイルに切り出せるようにする。

<!--  元々templateの中に書いてたhtmlをここに書く  -->
<h2>{{ msg }}</h2>
const sampleApp = angular.module('sampleApp', ['ngRoute']);

// templateUrlに切り出したhtmlを指定する
sampleApp.config(['$routeProvider', function ($routeProvider) {
    $routeProvider
        .when('/', {
            templateUrl: 'template.html',
            controller: 'sampleController'
        })
        .when('/:param', {
            templateUrl: 'template.html',
            controller: 'sampleController'
        })
}]);

sampleApp
    .controller('sampleController',['$scope', '$routeParams', function ($scope, $routeParams) {
        $scope.msg = $routeParams.param || 'Hello, World!';
    }]);

controllerAsを使って$scopeを使わないでcontrollerを書く

ng-controllerの記述を ng-controller="sampleController as main" のように変えたり、$routeProviderの指定をcontollerAs に変えることによって$scopeを利用せず記述することができる。
Angularの考え方に近く、後期に書かれたAngularJSはこちらで書かれていることが多いっぽい。

<!--  $routeProviderで指定したctrlの別名から値を参照する  -->
<h2>{{ ctrl.msg }}</h2>
const sampleApp = angular.module('sampleApp', ['ngRoute']);

sampleApp.config(['$routeProvider', function ($routeProvider) {
    $routeProvider
        // controllerとcontrollerAsを分けて書く場合
        .when('/', {
            templateUrl: 'template.html',
            controller: 'sampleController',
            controllerAs: 'ctrl'
        })
        // templateに埋め込む形で書く場合
        .when('/:param', {
            templateUrl: 'template.html',
            controller: 'sampleController as ctrl'
        })
}]);

sampleApp
    .controller('sampleController',['$routeParams', function ($routeParams) {
        // $scopeじゃなくてthisに値を設定する
        this.msg = $routeParams.param || 'Hello, World!';
    }]);

AngularJSのService

ビジネスロジックをまとめるための仕組み。
AngularJSの仕組みで名称を指定すると自動でDIされる。($scope、$routeProvider、$routeParamsなど)。
$scope以外は基本的にシングルトンなので、コンポーネント間の値の共有に使われたりする。
Serviceは後述の方法で自作してAngularJSから理由することができる。

Serviceを自作する

const sampleApp = angular.module('sampleApp', ['ngRoute']);

sampleApp.config(['$routeProvider', function ($routeProvider) {
    $routeProvider
        .when('/', {
            templateUrl: 'template.html',
            controller: 'sampleController',
            controllerAs: 'ctrl'
        })
        .when('/:param', {
            templateUrl: 'template.html',
            controller: 'sampleController as ctrl'
        })
}]);

// Serviceを作成してモジュールに登録する
sampleApp.service('sampleService', function () {
    this.firstName = '太郎';
    this.lastName = '山田';
    this.fullName = () => this.firstName + ' ' + this.lastName;
});

// 登録したServiceをInjectして処理を呼び出す
sampleApp
    .controller('sampleController', ['$routeParams', 'sampleService', function ($routeParams, sampleService) {
        // $scopeじゃなくてthisに値を設定する
        this.msg = ($routeParams.param || 'Hello, World!') + ' ' + sampleService.fullName();
    }]);

AngularJSのDirective

Directiveを自作する

AngularJSの世界におけるDirectiveはコンポーネントのようなもので、独自に定義したtemplateや処理をelementやattributeの形で埋め込めるもの。
今回はelementとして扱う単純な例と、親から子のDirectiveにどのように値を渡すかのサンプルを書く

<!DOCTYPE html>
<html lang="ja" ng-app="sampleApp">
<head>
    <meta charset="UTF-8">
    <title>sample</title>
    <script src="//code.angularjs.org/1.5.0/angular.min.js"></script>
    <script src="app.js"></script>
    <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"/>
</head>
<body>
<div ng-controller="sampleController as ctrl">
    <!--  双方向バインディングでディレクティブに値を渡す  -->
    <sample-directive-a value="ctrl.msg"></sample-directive-a>
    <!--  文字列でディレクティブに値を渡す  -->
    <sample-directive-b value="ctrl.msg"></sample-directive-b>
    <!--  interpolationで渡すとディレクティブに渡る文字列自体が変化するので見た目上双方向バインディングのものと同じになる  -->
    <sample-directive-b value="{{ ctrl.msg }}"></sample-directive-b>
    <!--  ディレクティブに関数を渡す  -->
    <sample-directive-c function="ctrl.upperMsg()"></sample-directive-c>
    <!--  値の変更の確認用  -->
    <input type="text" ng-model="ctrl.msg">
</div>
</body>
</html>
const sampleApp = angular.module('sampleApp', []);

// Directiveに渡す用の値を作るController
sampleApp
    .controller('sampleController', [function () {
        this.msg = 'Hello, World!';
        this.upperMsg = function () {
            return this.msg.toUpperCase();
        }
    }]);

// Directive
sampleApp
    .directive('sampleDirectiveA', () => {
        return {
            restrict: 'E', // elementとしてのみ呼び出せるよう制限
            template: '<h2>{{ value }}</h2>',
            scope: {
                value: '=' // scopeを=にすると親から渡されたその値は双方向バインディング状態になる
            }
        }
    })
    .directive('sampleDirectiveB', () => {
        return {
            restrict: 'E',
            template: '<h2>{{ value }}</h2>',
            scope: {
                value: '@' // scopeを@にすると文字列で渡されたことになる。
            }
        }
    })
    .directive('sampleDirectiveC', () => {
        return {
            restrict: 'E',
            template: '<h2>{{ function() }}</h2>',
            scope: {
                function: '&' // scopeを&にすると関数や配列を渡されたことになる。
            }
        }
    });

参考