シェルスクリプトを利用して、tsファイルの先頭にimport文を差し込む

tsファイル内に文字列angularが存在する場合、ファイルの先頭にimport * as angular from 'angular;を追加。

# 改行コード
LF=$'\\\x0A'

# 結果を配列に
files=(`find ./client/{app,components} -type f -name "*.ts" -print0 | xargs -0 grep "angular" -l`)

for i in "${files[@]}"

# 各ファイルの1行目に差し込む
do sed -i '' -e "1s/^/import * as angular from 'angular';"$LF"/" $i;

# 終了
done;

参考

GithubのLabel設定 Export, Import

Getting Started | GitHub Developer Guide

Create an OAuth token

2段階認証をしている場合。

curl -i -u ${your_username} -H "X-GitHub-OTP: ${your_2fa_OTP_code}" -d '{"scopes": ["repo"], "note": "labels"}' https://api.github.com/authorizations

https://github.com/settings/tokens からでもおk

Export

curl -i -H "Authorization: token ${token}" https://api.github.com/repos/:owner/:repo/labels

https://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository

Import

curl -i -H "Authorization: token ${token}" -d '{ "name": "foo", "color": "ff0000" }' https://api.github.com/repos/${owner}/${repo}/labels
...

https://developer.github.com/v3/issues/labels/#create-a-label

テーブルとデータ

これは何か

データテーブルを表示する場合、どのようなデータの型が利用しやすいか、について整理してみました。

参考にしたのは以下2つのライブラリ

以下のようなテーブルを表示したい。

ID Date Name Age
1 March 5, 2017 Foo 26
2 March 6, 2017 Bar 31

データのみ用意

配列

const data = [
  [1, 'March 5, 2017', 'Foo', 26],
  [2, 'March 6, 2017', 'Bar', 31]
];
  • 一番シンプル
  • カラムタイトルなどは別途用意する必要がある(Htmlのテンプレート内など)

オブジェクト

const data = [
  { id: 1, date: 'March 5, 2017', name: 'foo', age: 26},
  { id: 2, date: 'March 6, 2017', name: 'bar', age: 31},
];
  • 1行目を利用して、カラムタイトルを作成
  • オブジェクトだからカラム項目の順番は保証できないはず

データとカラム用の設定データ

  • 汎用性が高いのはこっち

配列

const data = [
  [1, 'March 5, 2017', 'Foo', 26],
  [2, 'March 6, 2017', 'Bar', 31]
];
const columns = [
  { name: 'ID'},
  { name: 'Date'},
  { name: 'Name'},
  { name: 'Age'}
];

オブジェクト

const data = [
  { id: 1, date: 'March 5, 2017', name: 'foo', age: 26},
  { id: 2, date: 'March 6, 2017', name: 'bar', age: 31},
];
const columns = [
  { field: 'id', name: 'ID' },
  { field: 'date', name: 'Date' },
  { field: 'name', name: 'Name' },
  { field: 'age', name: 'Age' }
];
  • カラムタイトル(name)とプロパティ名(field)を別にできる
const data = [
  { id: 1, date: 'March 5, 2017', customer: { name: 'foo', age: 26 } },
  { id: 2, date: 'March 6, 2017', customer: { name: 'bar', age: 31} },
];
const columns = [
  { field: 'id', name: 'ID' },
  { field: 'date', name: 'Date' },
  { field: 'customer.name', name: 'Name' },
  { field: 'customer.age', name: 'Age' }
];
  • データのネストなども対応可能

複雑なテーブル

カラム設定データを用意する場合、以下の機能の実現も汎用的にできそう。

  • ソート機能のオンオフ
  • 合計値の表示
  • 平均値の表示
  • データと表示内容の変更
    • 2017-03-05 -> March 5, 2017 or 2017年3月5日

UI-Gridだとこのような感じで設定を追加しています。

// http://ui-grid.info/docs/#/tutorial/105_footer

$scope.gridOptions = {
    showGridFooter: true,
    showColumnFooter: true,
    enableFiltering: true,
    columnDefs: [
        { field: 'name', width: '13%' },
        { field: 'address.street',aggregationType: uiGridConstants.aggregationTypes.sum, width: '13%' },
        { field: 'age', aggregationType: uiGridConstants.aggregationTypes.avg, aggregationHideLabel: true, width: '13%' },
        { name: 'ageMin', field: 'age', aggregationType: uiGridConstants.aggregationTypes.min, width: '13%', displayName: 'Age for min' },
        { name: 'ageMax', field: 'age', aggregationType: uiGridConstants.aggregationTypes.max, width: '13%', displayName: 'Age for max' },
        { name: 'customCellTemplate', field: 'age', width: '14%', footerCellTemplate: '<div class="ui-grid-cell-contents" style="background-color: Red;color: White">custom template</div>' },
        { name: 'registered', field: 'registered', width: '20%', cellFilter: 'date', footerCellFilter: 'date', aggregationType: uiGridConstants.aggregationTypes.max }
    ],
    data: data,
    onRegisterApi: function(gridApi) {
            $scope.gridApi = gridApi;
    }
};

angular-translate読み直し

半年前にまとめたもの。こっちに転載。


これは何か

設定例

/*@ngInject*/
export function translateConfig($translateProvider) {
  $translateProvider

  // angular-translate-loader-static-files
  // assets/lang/[langKey].json から非同期で言語ファイルを読み込む
  .useStaticFilesLoader({
    prefix: 'assets/lang/',
    suffix: '.json'
  })

  // 言語keyの設定
  .registerAvailableLanguageKeys(['en', 'ja'], {
    // en_US などは en と認識させる
    'en_*': 'en',
    'ja_*': 'ja',
    // そのほかの言語keyは全て en にする
    '*': 'en'
  })

  // ブラウザ環境から優先言語を自動取得
  // (以前に設定されたkeyがあれば、そちらを優先する <- angular-translate-storage-local利用時)
  .determinePreferredLanguage()

  // enable escaping of HTML
  // https://angular-translate.github.io/docs/#/guide/19_security
  .useSanitizeValueStrategy('escape')

  // angular-translate-handler-log
  .useMissingTranslationHandlerLog()

  // angular-translate-storage-local
  .useLocalStorage();
}

以下3つのextensionを利用している

  • angular-translate-loader-static-files
    • 非同期読み込み
  • angular-translate-handler-log
    • 対応するメッセージがない場合にエラーを表示
  • angular-translate-storage-local
    • 前回選択した言語コードを保存
    • (fallback で cookie を利用しているので angular-translate-storage-cookie も読み込まれる)

逆引き

言語ファイル(JSON)の書き方

  • 通常:
{
  "TRANSLATION_ID": "This is a concrete translation for a specific language."
}
  • ショートカット:
// bar.foo でアクセス可
{
  "bar": {
    "foo": {
      "foo": "This is my text."
    }
  }
}
  • リンク(いい機能だなあ):
{
  "SOME_NAMESPACE": {
    "OK_TEXT": "OK"
  },
  "ANOTHER_NAMESPACE": {
    "OK_TEXT": "@:SOME_NAMESPACE.OK_TEXT"
  }
}

参考: https://angular-translate.github.io/docs/#/guide/02_getting-started

$translate利用時に、非同期読み込みされたメッセージを反映

  • Two-way binding されないので 明示的に処理を書く必要がある
    • $translateChangeSuccessを利用
/*@ngInject*/
app.controller('Ctrl', ($scope, $translate, $rootScope) => {
  $rootScope.$on('$translateChangeSuccess', () => {
    $translate('HEADLINE').then(
      // 成功
      translation => $scope.headline = translation,
      // 失敗
      translationId => $scope.headline = translationId
    );
  });
});

参考: https://angular-translate.github.io/docs/#/guide/03_using-translate-service

変数を利用

言語JSONファイル:

{
  "TRANSLATION_ID": "{{username}} is logged in."
}

$translate利用時

$translate('TRANSLATION_ID', { username: 'PascalPrecht' });

$filter利用時

パターン1:

<div>{{ 'TRANSLATION_ID' | translate:'{ username: "PascalPrecht" }' }}</div>

パターン2:

<div>{{ 'TRANSLATION_ID' | translate:translationData }}</div>
angular.module('myApp')
/*@ngInject*/
.controller('Ctrl', ($scope) => { 
  $scope.translationData = {
    username: 'PascalPrecht'
  };
});

そのほかの方法もある

参考: https://angular-translate.github.io/docs/#/guide/06_variable-replacement

設定されている言語を取得

$rootScope.$on('$translateChangeEnd', (event, args) => {
  // args.language
});
// or
$translate.proposedLanguage() ||  $translate.use()
// proposedLanguage() -> 非同期読み込み中の言語keyを取得

利用言語を設定する

  • preferredLanguageを利用
  • ブラウザ環境から優先言語を自動取得する場合はdeterminePreferredLanguageを利用
    • (内部的にnavigator.languages[0] navigator.languageなどを利用している)

参考: https://angular-translate.github.io/docs/#/guide/07_multi-language

en_US en_SGなどをenとして扱う

  • registerAvailableLanguageKeysを利用

参考: https://angular-translate.github.io/docs/#/guide/09_language-negotiation

Unit Testing

非同期読み込みをしている場合、ローダー書き換える

beforeEach(module('app', ($provide, $translateProvider) => {
  $provide.factory('customLoader', $q => 
    () =>  $q.resolve()
  );
  $translateProvider.useLoader('customLoader');
}));

アプリを起動させる時の流れ

  • we register a asynchronous loader
  • we define our preferred language
  • $translate service is instantiated the first time it gets injected
  • angular-translate notices that there’s no language locally available
  • it looks if there’s a registered asynchronous loader
  • the asynchronous loader is called with the preferred language locale
  • the translation data is loaded and ready to be used

参考: https://angular-translate.github.io/docs/#/guide/22_unit-testing-with-angular-translate

webpack2にした

Migrating from v1 to v2

今の開発環境ではwebpackに任せているタスクが少ないため、かなり簡単に移行できた。

grunt-webpackを利用してるのでこれもv2に変更。
GitHub - webpack-contrib/grunt-webpack: integrate webpack into grunt build process

webpackで処理が止まってしまったので、keepalive: trueを追加して完了。
(以前から必要だったような気もしないではない)

ユニットテスト(Karma)もTypeScriptで: karma-webpack編

以前の記事
ユニットテスト(Karma)もTypeScriptで - ryotah’s blog

これだと、モジュールのimportができないねえ。

というわけでkarma-webpackを試そうと思います。
GitHub - webpack-contrib/karma-webpack: Use webpack with karma.

テスト環境は Karma Chrome Jasmine。

// karma.conf.js

var webpackConfig = require('./webpack.config');
module.exports = function(config) {
  config.set({
    mime: {
      'text/x-typescript': ['ts','tsx']
    },
    basePath: '',
    frameworks: ['jasmine'],
    patterns to load in the browser
    files: [
      'test/**/*.spec.ts'
    ],
    exclude: [
    ],
    preprocessors: {
      'test/**/*.spec.ts': ['webpack'],
    },
    webpack: {
      module: webpackConfig.module,
      resolve: webpackConfig.resolve
    },
    reporters: ['progress'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    singleRun: false,
    concurrency: Infinity
  })
}

基本的にはkarm initでkarma.conf.jsを生成した後、 preprocessorswebpackの部分を追加するだけでおk。

これに加えて、Chromeでテストが実行されなかったので mime: {'text/x-typescript': ['ts','tsx']} の設定も追加しています。

ng test ends with "Executed 0 of 0 ERROR" · Issue #2838 · angular/angular-cli · GitHub


他のファイルも参考程度に。

.
├── karma.conf.js
├── node_modules
├── package.json
├── src
├── test
├── tsconfig.json
└── webpack.config.js
// package.json

{
  "name": "webpack2-karm",

  // (省略)

  "devDependencies": {
    "@types/jasmine": "^2.5.43",
    "jasmine-core": "^2.5.2",
    "karma": "^1.5.0",
    "karma-chrome-launcher": "^2.0.0",
    "karma-jasmine": "^1.1.0",
    "karma-webpack": "^2.0.2",
    "ts-loader": "^2.0.1",
    "typescript": "^2.2.1",
    "webpack": "^2.2.1"
  }
}
// tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs"
  },
  "exclude": [
    "node_modules"
  ]
}
// webpack.config.js

var path = require('path');

module.exports = {
  entry: './src/foo',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'foo.bundle.js'
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js']
  },
  module: {
    rules: [
      { test: /\.tsx?$/, use: 'ts-loader' }
    ]
  }
}