Schematicsで、もうちょっとだけgenerateされるファイルをカスタマイズ

前回投稿した[Schematicsでちょっとだけgenerateされるファイルをカスタマイズ](https://qiita.com/cilly/items/e9514adad2df8a223ee1) にもうちょっとだけ手を加えてみた話です。

```ts
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { SomeService } from '~some.service';

@Injectable({
  providedIn: null,
})
export class MyService {

  constructor( private someService: SomeService ) { }
}
```

- こんな感じで作成したい
- 設定ファイルをプロジェクト側に置いて、プロジェクト毎でimportするもの、constructorにセットするものを設定したい

こんな条件のものを考えてみました。

# 実際にやってみた

my-schematicsをinstallするプロジェクト直下に以下の様な設定ファイルを設置したとして

```json:[someProjectPath]/.schematics.json
{
  "rules": {
    "rxjs": {
      "imports": [
        "import { Observable } from 'rxjs';",
        "import { map } from 'rxjs/operators';"
      ]
    }
    "foo": {
      "imports": [
        "import { SomeService } from '~some.service';"
      ],
      "constructors": [
        "private someService: SomeService"
      ]
    }
  },
  "service": {
    "hoge": [ "rxjs", "foo" ]
  }
}
```

`schema.json`と`schema.ts`に `property`, `interface`をそれぞれ `hoge`を追加

index.tsとtemplateを例えば以下みたいな感じにすると。

**`[key: string]`の記述がプレビューだと消えてしまうので意図的に`\`を記述してます**

```ts:my-schematics/src/service/index.ts
import { Rule, Tree, chain } from '@angular-devkit/schematics';

import buildService from '@schematics/angular/service';
import { Schema as ServiceOptions } from './schema';

const DEFAULT_STATE = { imports: [], constructors: [] };

interface State {
  imports: string[];
  constructors: string[];
}

interface Service {
  \[key: string]: string[];
}

interface Rules {
  \[key: string]: {
    imports: string[];
    constructors: string[];
  };
}


const getSettings = ( options: any, service: Service, ruleGroups: Rules ): State => {
  if ( !service ) {
    return DEFAULT_STATE;
  }
  const triggers  = Object.keys( service );
  if ( !triggers.length ) {
    return DEFAULT_STATE;
  }
  return triggers.reduce( ( state: State, trigger: string ) => {
    if ( !options[trigger] ) {
      return state;
    }
    const rules   = service[trigger];
    if ( !rules || !Array.isArray( rules ) ) {
      return state;
    }
    rules.forEach( ( rule ) => {
      const value = ruleGroups[rule];
      if ( !value ) {
        return;
      }
      const { imports, constructors } = value;
      if ( Array.isArray( imports ) ) {
        imports.forEach( importValue => typeof importValue === 'string' && state.imports.push( importValue ) );
      }
      if ( Array.isArray( constructors ) ) {
        constructors.forEach( constructorValue => typeof constructorValue === 'string' && state.constructors.push( constructorValue ) );
      }
    } );

    return state;
  }, { ...DEFAULT_STATE } );
};

export default function ( options: ServiceOptions ): Rule {
  return ( tree: Tree ) => {

    const buf                   = tree.read( '.schematics.json' );
    const settings              = {
      imports: '',
      constructors: 'constructor('
    };
    if ( !!buf ) {
      const content             = JSON.parse( buf.toString( 'utf-8' ) );
      const { imports, constructors } = getSettings( options, content.service, content.rules );
      imports.forEach( ( value, index ) => {
        if ( index > 0 ) {
          settings.imports      += '\n';
        }
        settings.imports        += value;
      } );
      constructors.forEach( ( value, index ) => {
        if ( index > 0 ) {
          settings.constructors += ',';
        }
        settings.constructors   += ` ${value}`;
      } );
    }
    settings.constructors       += ' ) { }';

    return chain([
      buildService( { ...options, ...settings } ),
    ]);
  }
}
```

```ts:my-schematics/src/service/files/__name@dasherize@if-flat__/__name@dasherize__.service.ts.template
import { Injectable } from '@angular/core';
<%= imports %>

@Injectable({
  providedIn: <% if(root) { %>'root'<% } else { %>null<% } %>
})
export class <%= classify(name) %>Service {

  <%= constructors %>
}
```

`ng g my-schematics:service --name my --hoge`の結果、主題のserviceが作られます。

# まとめ

- `ng g`したときにgenerateされるファイルに対して、痒いとこに手が届かないと感じることがあるのを解消したい。
- プロジェクト毎で結構決まった記述がある（importするものだったり）
- でも元のパラメータを引き継ぎたい。
- その場合の処理をあまり書きたくない（今後angular-cli側で更新があった場合のメンテ面も考えて）
- せっかくパッケージ化したんだから、プロジェクト個別の対応ではなく、ある程度いくつものプロジェクトに対応させたい。
- 追加、変更をそれなりに簡略化したい。
- そのためにはプロジェクト側で設定ファイルを用意してプロジェクト固有の設定ができるようにしたい。

こんな感じの気持ちから前回と今回の投稿にいたりました。

同じ様な気持ちを持ってる方がいれば、こんな感じでSchematicsを使うのも一つですね。というお話です。