AngularでComponentライブラリつくった時のメモ

AngularでComponentライブラリを作りました。
その時に学んだことなどを雑に書いていきます。

ちなみに、こちらでComponent一覧を確認できます。(Storybook)
https://fei-components.firebaseapp.com/

ソースコードはこちら。npmで公開してます。
https://github.com/shuufei/fei-system
ドキュメントとかは全く用意できてないのでComponent使い方を知ろうとするとコード見るしかない状態です、、

## npm経由でComponentライブラリをinstallして使えるようにする
いろんなAngular Projectで使えるようにしたかったので、[Angular Library](https://angular.io/guide/creating-libraries)の機能を利用しました。
この機能を利用すれば、AngularのModuleとして提供できるようになるので、利用したいProjectのNgModuleでImportして使えるようになります。
今回はnpm経由でinstallして使えるようにしたかったので、作成したAngularのLibraryをnpmに公開しました。
これで、下記のように他のAngularのModule同様に、NgModuleでImportして使えるようになります。

```shell
# npmインストール
> npm install fei-system
```

```ts:app.module.ts
// 利用したいNgModuleでImportする

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { AppComponent } from './app.component';
import { FeiComponentsModule } from 'fei-system'; // 追記

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    FeiComponentsModule // 追記
  ],
  providers: [],
  bootstrap: [AppComponent],
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule { }
```

ちなみに、package名でimportできるようにするためには、package.jsonのmainにdefaultで読ませたいファイルを指定してあげれば良いです。
今回だと、おおもとのAngularProjectから公開したので、 `dis/{ライブラリ名}/bundles/{ライブラリ名}.umd.js` を指定しています。
https://github.com/shuufei/fei-system

## 外部から使いやすくて、汎用性の高いComponentを作る
今回は[Atomic Design](http://atomicdesign.bradfrost.com/)の考え方にそって、Componentを作成していきました。
使いやすくて汎用性の高いComponentをAngularで作る時にポイントになるのは下記かなと思います。(他にももっといろんなのありそうなのででてき次第追記)

- どのServiceにも依存させない。
- Serviceに依存したevent処理をさせたい時などはケースの応じて下記のいずれかで行う
    - OutputでevenをEmitさせて、親Componentでevent処理をする。
    - どのServiceにも依存しない表示専用Componentをつくって、それをwrapする形でComponentを作る。
- ComponentのInputで状態を変更できるようにする。
- Componentの`:host`のstyleにdisplayプロパティを指定。これを指定しないと、親Componentから利用する時に想定通りにstyleを割り当てられなくなる。例えば、親ComponentでCustomComponent自体のmarginが指定がきかなくなったりする。
    - https://angular.io/guide/component-styles#host


## styleのあてかた
通常、htmlにstyleをあてていく時には、タグのclass属性にclass名を指定して、stylesheetファイルでclassに対してプロパティを指定していくというやり方が主になると思います。

ただ、それだとちょっとしたstyleを指定したい時にすごく面倒です。
例えば、backgroundColorを赤にするstyleだけを割り当てたい場合でも、class名を考えて、class属性に指定して、stylesheetファイルでそのclassに対してbackground-color: red;を指定する、という手順が必要になります。
style属性に直接指定するということをすればまあ楽なんですが、プロジェクト共通で使うようなプロパティ値などを指定する場合はメンテが大変になってしまいます。

ということで、最小限のstyleが指定されたclassを定義しておいて、htmlのclass属性ではそのclassを指定していくことで、styleがわりあたるようにしました。
([bootstrap](https://getbootstrap.com/)を参考)

例えば、次のようなclassを事前に定義しておいて、html側でこのclassを利用していく感じです。

```css
.bg-color-red {
  background-color: red;
}
.bg-color-blue {
  background-color: blue;
}
```
```html
<div class="bg-color-red">
  <span>Error!</span>
</div>
```

ただ、classを事前に定義していく時に、値ごとにclassを定義していくのは、メンテナンス的にも面倒です。
なので、scssの機能を使い、map型でパラメータを定義して、そのmapのパラメータ分、classを定義するというふうにしています。
こうしておけば、map型で定義したパラメータだけをメンテしていけばよくなります。また、他のscssファイルでもパラメータ名を参照して使えるようになるので、より統一性を保つことができます。

```scss:variables.scss
$colors: (
  'blue500': #2D6CCA,
  'red500': #D33030,
  'yellow500': #F9E002,
  'green500': #49D460,
  'black500': #202020,
  'black400': #505050,
  'black300': #A0A0A0,
  'black200': #DDDDDD,
  'black50': #F2F2F2,
  'white': #FFFFFF,
  'primary-blue': #2D6CCA,
  'success': #49D460,
  'danger': #D33030
);

$typography: (
  'title': 20px,
  'subtitle': 18px,
  'base': 14px,
  'caption': 12px
);

$shadow: (
  '1': 0 4px 5px rgba(0,0,0,0.12),
  '2': 0 4px 8px rgba(0,0,0,0.25),
  '3': 0 8px 16px rgba(0,0,0,0.20),
  '4': 0 8px 16px rgba(0,0,0,0.35)
);

$round: (
  '1': 2px,
  '2': 3px,
  '3': 5px,
  '4': 10px
);

```

```scss:styles.scss
@import './variables.scss';

// colors
@mixin define-color($data:()) {
  @each $key, $val in $data {
    .color-#{$key} {
      color: $val;
    }
    .bg-color-#{$key} {
      background-color: $val;
    }
  }
}
@include define-color($data: $colors);

// typography
@import url('https://fonts.googleapis.com/css?family=Noto+Sans+JP:400,500,700&display=swap');
@import url('https://fonts.googleapis.com/css?family=Lato:400,700,900&display=swap');
@mixin define-typography($data:()) {
  body {
    font-family: 'Noto Sans JP', sans-serif;
    font-size: map-get($map: $typography, $key: 'base');
    font-weight: 400;
    letter-spacing: 0.3px;
    line-height: 1.3;
    color: map-get($map: $colors, $key: 'black500');
  }
  @each $key, $val in $data {
    .text-#{$key} {
      font-family: 'Noto Sans JP', sans-serif;
      font-size: $val;
      font-weight: 400;
      letter-spacing: 0.3px;
      line-height: 1.3;
      &.num {
        font-family: 'Lato', sans-serif;
        font-weight: 400;
      }
      &.bold {
        font-family: 'Noto Sans JP', sans-serif;
        font-weight: 500;
        &.num {
          font-family: 'Lato', sans-serif;
          font-weight: 700;
        }
      }
    }
  }
}
@include define-typography($data: $typography);

// border
@mixin define-border($data:()) {
  @each $key, $val in $data {
    .border-#{$key} {
      border: solid 1px $val;
    }
  }
}
@include define-border($data: $colors);

// border radius
@mixin define-round($data:()) {
  @each $key, $val in $data {
    .round-#{$key} {
      border-radius: $val;
    }
  }
}
@include define-round($data: $round);

// shadow
@mixin define-shadow($data:()) {
  @each $key, $val in $data {
    .shadow-#{$key} {
      -webkit-box-shadow: $val;
      box-shadow: $val;
    }
  }
}
@include define-shadow($data: $shadow);

```

上記のような定義が事前にあれば、htmlではそのclassを指定していくだけでstyleを割り当てることができます。

```html

<div class="bg-color-danger round-2 shadow-1">
  <span class="color-white font-caption bold">Error!</span>
</div>

```



また、今回はflex-boxなどの指定もclass名ベースで指定したかったので、bootstrapのbootstrap-grid.min.cssを読み込んで利用しています。
参考：https://getbootstrap.com/docs/4.3/utilities/flex/

上記に加え、paddingやmarginのclassも定義しておくと便利です。
bootstrap-grid.min.cssにはその定義が含まれているので、bootstrap-grid.min.cssを取り込んでいる場合は、自分で定義する必要はありません。
参考：https://getbootstrap.com/docs/4.3/utilities/spacing/


## 最後に

もっとドキュメントとかComponentを充実させて、他の人も使えるレベルまで持っていきたいなあ。
あとComponent作る時のはなしはどっかでちゃんとまとめたいなあ。