かえるの井戸端雑記

開発日誌的な記事だったり備忘録だったり。まとめ記事と言うよりは、七転八倒の様子を小説みたいに読んで眺めてもらえればと。

Angular2を使ってみる 4. 書いたコードを理解する

まえおき

とりあえずは環境構築ができて動くことまでは確認できた。次は前に書いたTypeScriptのCodeとかも理解していくことにする。

といっても前に参考にしたpageを読み込むだけなんだけど。

しかしいつものpatternだとこれで満足して止まる気がする。次の課題も探さないとな。……何かserver daemonを用意してserverのstatusでもとってきてweb interfaceで描画でもできるようにしてみようか?

前は何をしたか

以下のsourceのうち、<my-app>で囲んだところが書き換わるようなcodeになった。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>Hello Angular 2!</title>
  </head>
  <body>
    <my-app>Loading…</my-app>
    <script src="./bundle.js"></script>
  </body>
</html>

このmy-appのところ、描画されたあとの要素を見てみるとこうなっている。

<my-app ng-version="4.0.1">
    <hello-world>
    <h1>Hello World!</h1>
</hello-world>
</my-app>

reactのcomponentとかの考え方を思い出すけど、自前のdomを作ってそこに働きかける処理を書いている感じかなあ。

でもってbundle.jsとはなんだろう

いつの間生成されたお前。いや絶対buildで作ったんだろうけど。とすればpackage.jsに書いてあるはず。

  "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1",
      "tsc": "tsc -p .",
      "webpack": "webpack ./index.js --output-filename ./bundle.js",
      "build": "npm run tsc && npm run webpack"
  }

npm run buildの内容がbuildで定義された中身に当たり、npm run tscを実行して次にnpm run webpackを実行してる。webpackでindex.jsを元にbundle.jsを生成しているわけか。

webpackの中の処理とかは今追いかけると深追いになりそうなのであとで。どうせfile増やしたり減らしたりcssも使いたいとかなってきたら関わる。し

tscというのはtypescript compilerという感じだろうか。

% find . -type f -print | grep tsc
./node_modules/typescript/bin/tsc
./node_modules/typescript/lib/tsc.js
./tsconfig.json

あったあった。

% ./node_modules/typescript/bin/tsc -h
Version 2.2.2
Syntax:   tsc [options] [file ...]

Examples: tsc hello.ts
          tsc --outFile file.js file.ts
          tsc @args.txt

まあそうっぽい。ついでにgoogleで調べてみたら普通にTypeScriptのCompile Commandだと説明が出ていた。

で、package.jsのscriptに定義されているのはtsc -pなわけで、これについてはhelpを見ると

-p FILE OR DIRECTORY, –project FILE OR DIRECTORY Compile the project given the path to its configuration file, or to a folder with a ‘tsconfig.json

とある。tsconfig.jsonに沿ってcompileしてくれる、と。でもってその中身を見れば確かにcompilerOptionsとかfilesGlobとかfilesという定義がある。

だいたい把握できた。

ではTypeScriptでどのように実装されているか

一番表層の部分をさらったので次は実装部分。

index.tsを見てみると、

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

platformBrowserDynamic().bootstrapModule(AppModule);

importはまあわかる。AppModuleは自分で作ったものだし。core-jsやzone.js周りはまあなんか必要なんだろうなあで今は流す。

でもplatformBrowserDynamicってなんだ。

NgModule導入について · Angular Info

そして、NgModuleで宣言したモジュールを使ってアプリケーションを起動するための、新しいbootstrap関数があります。

(中略)

NgModuleで作ったモジュールを、各プラットフォームのbootstrapModuleメソッドで起動するという流れになります。

ということらしい。ではNgModuleとは何か。

NgModule導入について · Angular Info

NgModuleは、ディレクティブやパイプ、サービスなどをひとまとめにしたモジュールを宣言するためのAPIです。 @Componentなどと同じようにデコレータを使って宣言します。

npm iしてAngualr 2のHello World!を書くところまで【改】 - Qiita

Angular 2では、アプリケーションを開発していく際に、まずNgModuleという単位でclassを宣言し、そこにアプリケーション内で用いるコンポーネントやサービスを登録していくという流れで進めていきます。また、このNgModuleはライブラリ開発者にとっても、提供するひとつのパッケージの単位として扱うことができます。

ほほう。というかcomponentでdecorateしているあたりでもう自分の中ではreactのcomponentと同じ意味合いに見えた。要するにdomとlogicが一つになったobjectのこと。

なのでplatformBrowserDynamic().bootstrapModule(AppModule);はrenderしているというだけの話。……moduleが複数になってきたらどうするんだろうとか疑問はあるけどまたの機会に。次はそれをやってもいいなあ。

Moduleの実装

ということでapp.module.tsを見てみる。ちなみにbuildした後だからapp.module.jsもある。……これgit commitする時に取り除いた方がいいなあ。というかsourceとdistは領域分けよう。今度。

で、sourceだ。

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

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

componentはまた別途定義したのね。importとbootstrapはなんとなく意味がわかるけどdeclarationsって難だろう。いやcomponentを登録しているんだろうけど。

NgModule導入について · Angular Info

このプロパティは、そのモジュールの中で宣言されているディレクティブとパイプを登録する場所です。 このディレクティブにはもちろんコンポーネントも含みます。

(中略)

NgModuleのdeclarationsに登録されたディレクティブやパイプは、そのモジュール内でならどこでも使えるようになります。 つまり、自作したディレクティブ・コンポーネント・パイプはすべてdeclarationsに登録しておけばよいです。

ああ参照したいdirective、component、pipeを登録しておくところなのか。確かにそういうの定義しないとjsに落とし込むときにscopeの仕様との兼ね合いでつらそう。

ところでcomponentはわかるけどdirective、pipeってなんぞや。

directive

Attribute Directives - ts - GUIDE

There are three kinds of directives in Angular:

  1. Components—directives with a template.
  2. Structural directives—change the DOM layout by adding and removing DOM elements.
  3. Attribute directives—change the appearance or behavior of an element, component, or another directive.

Components are the most common of the three directives. You saw a component for the first time in the QuickStart guide.

Structural Directives change the structure of the view. Two examples are NgFor and NgIf. Learn about them in the Structural Directives guide.

Attribute directives are used as attributes of elements. The built-in NgStyle directive in the Template Syntax guide, for example, can change several element styles at the same time.

日本語だとこっちでも。

Angular2でカスタム構造ディレクティブ作成 | VPSサーバーでWebサイト公開 備忘録 ~Linux、MySQLからAJAXまで

コンポーネント   ・最も一般的なディレクティブで、テンプレートと共に定義。   ②構造ディレクティブ(Structural directives)   ・DOM用要素を追加、除去する事によってDOMのレイアウトを変更する。 ・NgFor、NgIf、NgSwitchのビルトインディレクティブが代表的。   ③属性ディレクティブ(Attribute directives)   ・要素の外観や振る舞いを変更するディレクティブ。 ・NgStyleビルトインディレクティブは、いくつかの要素スタイルを同時に変更できる。

ほほう。directiveというのがangularでのobjectの一単位っぽいな。

pipe

Pipes - ts - GUIDE

Pipes transform displayed values within a template.

Every application starts out with what seems like a simple task: get data, transform them, and show them to users. Getting data could be as simple as creating a local variable or as complex as streaming data over a WebSocket.

Once data arrive, you could push their raw toString values directly to the view, but that rarely makes for a good user experience. For example, in most use cases, users prefer to see a date in a simple format like April 15, 1988 rather than the raw string format Fri Apr 15 1988 00:00:00 GMT-0700 (Pacific Daylight Time).

Clearly, some values benefit from a bit of editing. You may notice that you desire many of the same transformations repeatedly, both within and across many applications. You can almost think of them as styles. In fact, you might like to apply them in your HTML templates as you do styles.

Introducing Angular pipes, a way to write display-value transformations that you can declare in your HTML.

Angular2のPipeで文字列を操作する | Yuhiisk

Pipeはテンプレート内での、文字列操作の仕組みです。

Angular2でカスタムPipeを定義する | Yuhiisk

数字を入力すると、それに紐付いた文字列を出力するPipeを定義してみましょう。 「サーバーのAPIから取得する値は数字だけど、ブラウザでの表示は文字列」といったよくある場面を想定します。

なる。文字列変換してのreturn、と。apiの受け口かなあ。

実際の使い方はここが参考になりそう。

qiita.com

話を戻す

何の話だったっけ。ああそうそうcodeを読んでいたんだ。

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

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

とりあえずBootstrapしているのはAppComponentなのでそれが呼び出されると思ってよさそう。きっとその中でHelloWorldComponentも使われるんだろう、と。

となれば次はapp.component.tsを見てみる。べきなんだけど。BrowserModuleが気になる。

BrowserModule

npm iしてAngualr 2のHello World!を書くところまで【改】 - Qiita

importsは自分のNgModuleに他のNgModuleを取り込む際に使います。Angular 2が提供している各種モジュールや、サードパーティのモジュールをここに記述します。今回はAngular 2のBrowserModuleを使います。

そうではなく。

BrowserModule - ts - API

The ng module for the browser.

お、おう。

まあ作っているmoduleがNgModule準拠だから、NgModule準拠でのbrowsingに使う処理系統が必要で、それを呼び込んでいる……みたいな感じなのかな。

Angular2のモジュール、コンポーネント、bootstrapの概要 | VPSサーバーでWebサイト公開 備忘録 ~Linux、MySQLからAJAXまで

○imports属性   ・モジュールのリストを指定。 ・BrowserModuleは、すべてのブラウザーアプリがインポートしなければならないモジュール。   それで良さそう。

NgModule FAQs - ts - COOKBOOK

Should I import BrowserModule or CommonModule?

The root application module (AppModule) of almost every browser application should import BrowserModule from @angular/platform-browser.

BrowserModule provides services that are essential to launch and run a browser app.

BrowserModule also re-exports CommonModule from @angular/common, which means that components in the AppModule module also have access to the Angular directives every app needs, such as NgIf and NgFor.

Do not import BrowserModule in any other module. Feature modules and lazy-loaded modules should import CommonModule instead. They need the common directives. They don’t need to re-install the app-wide providers.

BrowserModule throws an error if you try to lazy load a module that imports it.

Importing CommonModule also frees feature modules for use on any target platform, not just browsers.

うーん。BrowserModule provides services that are essential to launch and run a browser app.とあるから必要なのはわかるんだけどどういう働きをする代物なのかを知りたい。

らちがあかないのでsource codeも見てみる。./node_modules/@angularに潜ってからfind . -type f -print | xargs grep BrowserModuleで調べる。ひどいことになった。

% find . -type f -print | xargs grep BrowserModule
(省略)
./platform-browser/src/browser.d.ts:export declare class BrowserModule {
./platform-browser/src/browser.d.ts:    constructor(parentModule: BrowserModule);
./platform-browser/src/platform-browser.d.ts:export { BrowserModule, platformBrowser } from './browser';
./platform-browser/testing/src/browser.metadata.json:[{"__symbolic":"module","version":3,"metadata":{"platformBrowserTesting":{"__symbolic":"error","message":"Reference to a local symbol","line":16,"character":6,"context":{"name":"_TEST_BROWSER_PLATFORM_PROVIDERS"}},"BrowserTestingModule":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"NgModule"},"arguments":[{"exports":[{"__symbolic":"reference","module":"@angular/platform-browser","name":"BrowserModule"}],"providers":[{"provide":{"__symbolic":"reference","module":"@angular/core","name":"APP_ID"},"useValue":"a"},{"__symbolic":"reference","module":"@angular/platform-browser","name":"ɵELEMENT_PROBE_PROVIDERS"},{"provide":{"__symbolic":"reference","module":"@angular/core","name":"NgZone"},"useFactory":{"__symbolic":"reference","module":"./browser_util","name":"createNgZone"}}]}]}]}}},{"__symbolic":"module","version":1,"metadata":{"platformBrowserTesting":{"__symbolic":"error","message":"Reference to a local symbol","line":16,"character":6,"context":{"name":"_TEST_BROWSER_PLATFORM_PROVIDERS"}},"BrowserTestingModule":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"NgModule"},"arguments":[{"exports":[{"__symbolic":"reference","module":"@angular/platform-browser","name":"BrowserModule"}],"providers":[{"provide":{"__symbolic":"reference","module":"@angular/core","name":"APP_ID"},"useValue":"a"},{"__symbolic":"reference","module":"@angular/platform-browser","name":"ɵELEMENT_PROBE_PROVIDERS"},{"provide":{"__symbolic":"reference","module":"@angular/core","name":"NgZone"},"useFactory":{"__symbolic":"reference","module":"./browser_util","name":"createNgZone"}}]}]}]}}}]

./node_modules/@angular/platform-browser/src/browser.d.tsがそれっぽい。

import { ErrorHandler, ModuleWithProviders, PlatformRef, Provider } from '@angular/core';
export declare const INTERNAL_BROWSER_PLATFORM_PROVIDERS: Provider[];
/**
 * @security Replacing built-in sanitization providers exposes the application to XSS risks.
 * Attacker-controlled data introduced by an unsanitized provider could expose your
 * application to XSS risks. For more detail, see the [Security Guide](http://g.co/ng/security).
 * @experimental
 */
export declare const BROWSER_SANITIZATION_PROVIDERS: Array<any>;
/**
 * @stable
 */
export declare const platformBrowser: (extraProviders?: Provider[]) => PlatformRef;
export declare function initDomAdapter(): void;
export declare function errorHandler(): ErrorHandler;
export declare function _document(): any;
/**
 * The ng module for the browser.
 *
 * @stable
 */
export declare class BrowserModule {
    constructor(parentModule: BrowserModule);
    /**
     * Configures a browser-based application to transition from a server-rendered app, if
     * one is present on the page. The specified parameters must include an application id,
     * which must match between the client and server applications.
     *
     * @experimental
     */
    static withServerTransition(params: {
    appId: string;
    }): ModuleWithProviders;
}

しまった構文がよくわからない。でも違う気がする。exportとあるしparentModule:BrowserModuleと定義してるし。

TypeScript プログラミング - アンビエント宣言 - 宣言

アンビエント宣言は、他のコンポーネント(例えば Web ブラウザや既存の JavaScript ライブラリ)から変数や関数などが提供されることを TypeScript コンパイラに伝えます。これは、既存の JavaScript ライブラリに静的型付けし、TypeScript で利用可能になることを意味します。 次に示すのは、アンビエント宣言の書式です。

ほら。ということはこれwrapperじゃないのかもしかして……。

それより、ここでimportされている@angular/coreを探せばBrwoserModuleの定義もあるかな。

なかった。再びfindの旅へ。

./platform-browser/@angular/platform-browser.es5.js:var BrowserModule = (function () {
./platform-browser/@angular/platform-browser.es5.js:    function BrowserModule(parentModule) {
./platform-browser/@angular/platform-browser.es5.js:            throw new Error("BrowserModule has already been loaded. If you need access to common directives such as NgIf and NgFor from a lazy loaded module, import CommonModule instead.");

あった。これだ。node_modules/@angular/platform-browser/@angular/platform-browser.es5.jsか。

/**
 * The ng module for the browser.
 *
 * \@stable
 */
var BrowserModule = (function () {
    /**
     * @param {?} parentModule
     */
    function BrowserModule(parentModule) {
        if (parentModule) {
            throw new Error("BrowserModule has already been loaded. If you need access to common directives such as NgIf and NgFor from a lazy loaded \
module, import CommonModule instead.");
        }
    }
    /**
     * Configures a browser-based application to transition from a server-rendered app, if
     * one is present on the page. The specified parameters must include an application id,
     * which must match between the client and server applications.
     *
     * \@experimental
     * @param {?} params
     * @return {?}
     */
    BrowserModule.withServerTransition = function (params) {
        return {
            ngModule: BrowserModule,
            providers: [
                { provide: APP_ID, useValue: params.appId },
                { provide: TRANSITION_ID, useExisting: APP_ID },
                SERVER_TRANSITION_PROVIDERS,
            ],
        };
    };
    return BrowserModule;
}());

下にもちょろちょろとあったけど割愛。どちらにせよこれだけ見てもよくわからない。でも名前からしてbrowserに表示する部分の管理をしているような感じがする。

FN1611004 | Angular 2: とにかくAngular 2でコードを書いて動かす | HTML5 : テクニカルノート

Angular 2のモジュールからはNgModule関数とBrowserModuleクラスをimportして(「TypeScript入門 10: モジュール ー exportとimport」参照)、アプリケーションのクラスにデコレータ(@NgModule)の宣言を加えます(「TypeScriptTypeScript入門 12: デコレータ(Decorator)を使う」参照)。BrowserModuleクラスは、アプリケーションをブラウザで動かすために用いられます。

うんそういうことなんだろうなあ。

NgModules - ts - GUIDE

The metadata imports a single helper module, BrowserModule, which every browser app must import.

BrowserModule registers critical application service providers. It also includes common directives like NgIf and NgFor, which become immediately visible and usable in any of this module’s component templates.

The declarations list identifies the application’s only component, the root component, the top of the app’s rather bare component tree.

Angular 2 NgModule Intro - Ahead Of Time Compilation And Lazy Loading

the root module in the case of web applications imports the BrowserModule, which for example provides Browser specific renderers, and installs core directives like ngIf, ngFor, etc.

slides.com

これを見るとBrowserModule以外にも、HttpModule、RouterModule、FormsModule、MaterialModuleというのもあるらしい。ああこれらはNgModuleが提供しているものなのか。

NgModules - ts - GUIDE

Importing BrowserModule made all of its public components, directives, and pipes visible to the component templates in AppModule.

More accurately, NgIf is declared in CommonModule from @angular/common.

CommonModule contributes many of the common directives that applications need, including ngIf and ngFor.

BrowserModule imports CommonModule and re-exports it. The net effect is that an importer of BrowserModule gets CommonModule directives automatically.

Many familiar Angular directives don’t belong to CommonModule. For example, NgModel and RouterLink belong to Angular’s FormsModule and RouterModule respectively. You must import those modules before you can use their directives.

To illustrate this point, you’ll extend the sample app with ContactComponent, a form component that imports form support from the Angular FormsModule.

あー。なんとなくわかってきた。要は普通の操作に必要なmethodが入ったmodule群ということなんだこれ。たぶん。それがbrowser周りの処理だったりhttp周りであったりformであったり……ここでいうrouterが何かは知らないけどそういうものもあるんだろう。#include <stdio.h>みたいなものという。

うんなんか納得した。

app.component.ts

全力で脱線したけど意味は理解できたのですっきり。さて戻ってこよう。

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

@Component({
    selector: 'my-app',
    template: `
    <hello-world></hello-world>
`
})
export class AppComponent {}

もうこれ以上なくわかりやすい。my-appに<hello-world><hello-world>を入れ込むだけ。で、そのhello-worldはapp.module.tsでdeclarationsに定義されていたHelloWorldComponentのことで、hello-world.component.tsに書いてある。

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

@Component({
    selector: 'hello-world',
    template: `
    <h1>Hello World!</h1>
`
})
export class HelloWorldComponent {}

もう何も考えなくてもいい。selectorは任意の名前のdomの名前を定義しているだけだろうし、templateはそれと置き換えられる内容だろうし。

おおよそ元々のpageでやっていたことの意味がわかった気がするので今回はここまで。

考え事

案の定こちらもdomもjsの中で定義してしまおうという感じになったわけだけど。これどういう風にpageを構成するのがいいんだろう。最終的に管理しやすくなるのはどういう風に実装した物なのかまだ想像しきれない。

あんまり細かく分けてもpage構成変えたいとき大変そうだし。操作しなければならない最小限だけを入れ込むべきなのかなーと思うけど、page中にいくつもcomponent作ってそれらの動作を連係させてとかすると結局全部管理下にみたいなことにもなりそうだし。

すると分け方としては、

  • data生成とurlのrouting制御 => server
  • 大枠のpageの定義 => template engineなり静的htmlなりに任せる
  • serverとのdata通信とlogicへの流し込み => 別途js?
  • domとstyleとlogic(js) => angular2やreact+redux

こんな感じのlayerに分かれていくのかな。このserverとのdata通信(apiをたたいたりesやwebsocketを保持したり)とcomponentのlogicへのdataの流し込みという処理がどう挟まるべきか見えきらないけどここは将来の課題かなあ。それとももう何かいい物があるんだろうか。

次の課題候補

  • NgModuleでのmoduleを増やして制御対象を増やしてみる
  • その前にsrcとdistの領域をわけてbuildできるようにしてみる

このあたりかな。