技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

AngularJS をセットアップする

AngularJS とは

GoogleOSS コミュニティで作成されるフロントエンドフレームワークです。

angular.jp

セットアップ

Angular のコマンドラインツールをまずインストールします。

npm install -g @angular/cli

プロジェクトを作成していきます。
ng new my-app で作成です。

$ ng new my-app
? Would you like to share pseudonymous usage data about this project with the Angular Team
at Google under Google's Privacy Policy at https://policies.google.com/privacy. For more
details and how to change this setting, see https://angular.io/analytics. No
Global setting: disabled
Local setting: No local workspace configuration file.
Effective status: disabled
? Would you like to add Angular routing? No
? Which stylesheet format would you like to use? CSS
...

質問には下記で設定しています

  1. この利用データをAngularチームと共有しても良いですか?No
  2. Angular のルーティング機能要ります?No
  3. スタイルシートのフォーマットは? CSS

プロジェクトが出来上がったら npm run start

$ npm run start

> my-app@0.0.0 start
> ng serve

? Would you like to enable autocompletion? This will set up your terminal so pressing TAB while typing Angular CLI commands will show possible options and autocomplete arguments. (Enabling
 autocompletion will modify configuration files in your home directory.) Yes
Appended `source <(ng completion script)` to `C:\Users\azale\.bashrc`. Restart your terminal or run the following to autocomplete `ng` commands:

    source <(ng completion script)
✔ Browser application bundle generation complete.

Initial Chunk Files   | Names         |  Raw Size
vendor.js             | vendor        |   1.95 MB | 
polyfills.js          | polyfills     | 328.93 kB | 
styles.css, styles.js | styles        | 226.36 kB | 
main.js               | main          |  45.98 kB | 
runtime.js            | runtime       |   6.51 kB | 

                      | Initial Total |   2.54 MB

Build at: 2023-05-25T13:02:39.178Z - Hash: 8a48f1dd49bbdda6 - Time: 7126ms

** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ **


√ Compiled successfully.

恒例のコードリーディング

プロジェクト作成時点での構成はこんな感じ。

CSS と HTML/TS で構成されているようだ。
TS は TypeScript の事で、静的型付けの JavaScript として Microsoft が開発している言語だ。

根っこから見ていく訳だが、index.html は本気で見るものが無い。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>MyApp</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root></app-root>
</body>
</html>

強いて言えば、app-root タグが一つあるだけ。
気を取り直して、main.ts を見てみるが、こっちも大したことは書いていない。

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));

app.module を読み込んで起動する…それだけの様に見える。
あくまで起動部分という事だろう。

では app ディレクトリ配下を見ていく。

app.module.ts を開くと

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

中身としては、app.component をモジュールのエントリポイントにするという定義だけだ。
しかしデコレータで定義をしていくとはなんとも癖のある感じだ…

app.component.ts を覗くと、これが実際の本体らしい。

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'my-app';
}

恐ろしく空っぽである。
title = 'my-app'; を画面にバインドするつもりなんだろうな…というのだけは予想できる。 しかしここまで空だと初見ではかなり戸惑う

テンプレートは長いので色々省略して

<div class="content" role="main">

  <!-- Highlight Card -->
  <div class="card highlight-card card-small">

    <svg id="rocket" xmlns="http://www.w3.org/2000/svg" width="101.678" height="101.678" viewBox="0 0 101.678 101.678">
      <title>Rocket Ship</title>
      <g id="Group_83" data-name="Group 83" transform="translate(-141 -696)">
        <circle id="Ellipse_8" data-name="Ellipse 8" cx="50.839" cy="50.839" r="50.839" transform="translate(141 696)" fill="#dd0031"/>
        <g id="Group_47" data-name="Group 47" transform="translate(165.185 720.185)">
          <path id="Path_33" data-name="Path 33" d="M3.4,42.615a3.084,3.084,0,0,0,3.553,3.553,21.419,21.419,0,0,0,12.215-6.107L9.511,30.4A21.419,21.419,0,0,0,3.4,42.615Z" transform="translate(0.371 3.363)" fill="#fff"/>
          <path id="Path_34" data-name="Path 34" d="M53.3,3.221A3.09,3.09,0,0,0,50.081,0,48.227,48.227,0,0,0,18.322,13.437c-6-1.666-14.991-1.221-18.322,7.218A33.892,33.892,0,0,1,9.439,25.1l-.333.666a3.013,3.013,0,0,0,.555,3.553L23.985,43.641a2.9,2.9,0,0,0,3.553.555l.666-.333A33.892,33.892,0,0,1,32.647,53.3c8.55-3.664,8.884-12.326,7.218-18.322A48.227,48.227,0,0,0,53.3,3.221ZM34.424,9.772a6.439,6.439,0,1,1,9.106,9.106,6.368,6.368,0,0,1-9.106,0A6.467,6.467,0,0,1,34.424,9.772Z" transform="translate(0 0.005)" fill="#fff"/>
        </g>
      </g>
    </svg>

    <span>{{ title }} app is running!</span>

なるほど、{{ 変数名 }} でバインドするらしい。
結果として出力されるタグも確認する。

こちらでも ShadowDOM は使わないご様子。

コンポーネントの追加

Angular はコンポーネントを自前で追加もできるが、コマンドから作成できるようだ。

$ ng generate component example
CREATE src/app/example/example.component.html (22 bytes)
CREATE src/app/example/example.component.spec.ts (566 bytes)
CREATE src/app/example/example.component.ts (206 bytes)
CREATE src/app/example/example.component.css (0 bytes)
UPDATE src/app/app.module.ts (400 bytes)

モジュールとコンポーネントは異なる概念のようだ…
example/example.component.ts を覗いてみると

import { Component } from '@angular/core';

@Component({
  selector: 'app-example',
  templateUrl: './example.component.html',
  styleUrls: ['./example.component.css']
})
export class ExampleComponent {

}

実に空っぽの定義、デフォルトで作成されるコンポーネントモジュール名-コンポーネント名 で作成されるようだ。
example/example.component.html の中身はもっとシンプルで

<p>example works!</p>

実にシンプルだ。
コンポーネントの ShadowDOM 化は encapsulation: ViewEncapsulation.ShadowDom か…
早速 @Component に突っ込んでみる

import { Component, ViewEncapsulation } from '@angular/core';

@Component({
  selector: 'app-example',
  templateUrl: './example.component.html',
  styleUrls: ['./example.component.css'],
  encapsulation: ViewEncapsulation.ShadowDom,
})
export class ExampleComponent {
}

この状態で、app/aoo.component.html をこんな感じに書き換えた

<div class="content" role="main">
  <span>{{ title }} app is running!</span>
  <app-example></app-example>
</div>

さあ結果は?

きちんと ShadowDOM するようになったね。
親から子にデータを渡すのは @Input 、逆に子→親の通知は、イベントもしくは @Output するらしい

example.component.ts をこんな風に書き換えて

import { Component, ViewEncapsulation, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-example',
  templateUrl: './example.component.html',
  styleUrls: ['./example.component.css'],
  encapsulation: ViewEncapsulation.ShadowDom,
})
export class ExampleComponent {
  @Input('name') nameValue : String = 'John Doe';

  @Output() voted = new EventEmitter<boolean>();
  didVote = false;

  vote(agreed: boolean) {
    this.voted.emit(agreed);
    this.didVote = true;
  }
}

画面 example.component.html をこんな風に設定する

<p>Hello {{nameValue}} !</p>
<button type="button" (click)="vote(true)"  [disabled]="didVote">Agree</button>
<button type="button" (click)="vote(false)" [disabled]="didVote">Disagree</button>

コンポーネントは、vote のイベントを待ち受けるようにする。
app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'my-app';
  testUser = 'ExampleName';

  // 以下追加
  agreed = 0;
  disagreed = 0;
  onVoted(agreed: boolean) {
    if (agreed) {
      this.agreed++;
    } else {
      this.disagreed++;
    }
  }
}

で、app.component.html では

<div class="content" role="main">

  <span>{{ title }} app is running!</span>
  <app-example [name]="testUser" (voted)="onVoted($event)"></app-example>

  <p>Vote state {{agreed}} / {{disagreed}}</p>
</div>

なるほどこんな感じか。

リリース

npm run build コマンドを実行すれば、dist ディレクトリに静的ファイルが出力される。
構成は Vue の方がシンプルだが、まぁ良いのではなかろうか?

ドキュメント

ドキュメントは日本語があるのはありがたいね。

angular.jp

感想

Angular は独自の記法が大分多い一方、かなり細かく挙動制御が出来そうだ。
柔軟性が高いというよりかは、CoC を取り入れつつ、オプションが豊富なのだろう。

学習コストだけでいえば多分下記の様な印象。

React > LWC > Vue > Angular

LWC は標準 HTML に極力準拠しようとしている所が確かにあるので、独自路線を突っ走る REACT とは学習的にはタメを張る覚えやすさではないかな…。
ただし、慣れと大規模化を考えると、LWC と Angular は捨てがたい…