はまったりひらめいたり…とか…

Angularや.NETやAzureやその他色々。

ASP.net MVC4 + Angular v2 + webpack

以前にASP.Net4+Angularで記事を書いたことがありましたが

当時、R.C版だったこともあるので、現在のVer.で作り直してみようと思います。

といっても前回より環境や情報が整ったこともあり、そんなに難しい内容にはならないと思います。

ただ割と無茶な作り方をしているので、素直にCoreかMVC5を使用することをオススメします。

ASP.net MVC4 + Angular v2について、公式にガイドが出ています。

angular.io

ただ、EmptyなプロジェクトにAngularを乗っけるものなので簡易気味です。

簡易な状態でとりあえず動かしてみたい!ということであれば

公式のガイド通りにやったほうが良いとおもいます。

前半は、AngularのWebpackのIntroductionの通りに進みます

angular.io

その後、ASP.netMVC4で使用できるよう調整していきます。

注意

  • 今回のものについては僕がとった方法となるので、絶対条件について記述しているものではありません。

  • Angularは「~2.4.0」を対象としています。Ver.が変わると、この方法でもエラーが発生することは十二分にありえます。

参考サイト

文中で出てきてますが、今回参考にしたサイトをまとめて掲載します。

angular.io

angular.io

stackoverflow.com

github.com

kiyokura.hateblo.jp

stackoverflow.com

stackoverflow.com

Angularのソースを構築する

上記のAngularのWebpackのIntroductionの内容をすべて行います。

記事作成時点だとチュートリアルの内容は古かったようなので

Webpackを使用したAngularのBuild時点でエラーが発生した場合は、下記URLの内容を試してみましょう。

stackoverflow.com

また、下記のようなエラーが発生する場合はnode.jsがv6.9.4以下の状態の可能性があります。

outputOptions.children = options.map(o => o.stats);

そして、VisualStudioのタスクランナーで上記のエラーが発生している場合は下記URLの対策を行います。

github.com

チュートリアルで行っている主たるところは下記の内容ですね。

それぞれの内容については今回言及することではないと思うので省きます。

  • TypeScript開発環境の構築:tsConfigの設定

  • Webpackビルド設定(Webpack.config)

  • Unitテストシステムの構築(karima.conf)

チュートリアルを行った上で、Angular単体でビルドがうまくいっているか確認してみましょう。

VisualStudioのBuildでななく、コンソールで「webpack」を実行し確認します。

VisualStudioの画面で「のみ」TypeScriptソースでエラーが出ているときは

AngularのBuild時点でエラーが発生していないが

VisualStudioのエラー一覧でエラーが発生している場合、VisualStudio側の構成に原因があることがあります。

AngularのBuild時はプロジェクトのnode_module内のTypeScriptを参照してくれますが

VisualStudioではnode_moduleの設定を見ずに、VisualStudioが持っているTypeScriptの設定で

エラー判断を行っていることがあるようです。

そんな場合は、下記のそれぞれを行うことで解消できる可能性があります。

TypeSript2.xのダウンロード

使用するTypeSCriptとVisualStudioのTypeScriptが一致していない可能性があります。

VisualStudioのTypeScriptExtensionをインストールしましょう。

Download TypeScript for Visual Studio 2015 - 日本語 from Official Microsoft Download Center

プロジェクトファイルのTypeScriptVerの修正

TypeScriptの旧Verが使用されている環境でプロジェクトを作成した場合

プロジェクトで使用されるTypeScriptVersionがプロジェクト内に残っている場合があります

.cproj内のTypeScriptVersionを変更します。

<TypeScriptToolsVersion>2.x</TypeScriptToolsVersion>

環境変数に残存するTypeSrript情報の削除

VisualStudioの古いTypeScriptバージョン情報が、環境変数に残っている可能性があります。

下記記事の内容をもとに、環境変数の情報を修正しましょう。

kiyokura.hateblo.jp

ASP.netで使うためのソースの修正

Webpackの修正

現在、バンドルされたファイルは[dist]ディレクトリに格納されています。

VisualStudioの初期配置のパス上には[dist]は存在しないのでちょいと使いづらいです。

[Scripts]ディレクトリに変更しましょう。

webpack.dev.js

output: {
-   path: helpers.root('dist'),
+   path: helpers.root('Scripts'),
    publicPath: 'http://localhost:8080/',
    filename: '[name].js',
    chunkFilename: '[id].chunk.js'
}

ASP側のRouting設定の変更

クライアントはSPA構成で作成することになるので、ASP側のルーティング等は現状必要ありません。

また、Angularのルーティングで生成されたURLで404を吐かないようにする必要があります。

Web.configで404をが発生した場合、ベースページに飛ばすのも良いかもしれませんが

リダイレクトが発生しまくるので得策ではないかと思われます。

ASPのルーティングとうまく組み合わせて使えば、簡易なLazyLoadingとしても使えるような気がします

(Angular+WebPackでLazyLoadingする機能はありますが…)

ひとまず、ベースとなるページを残して、あとは潰してしまい、それに合わせてRoutingの設置を変更します。

RouteConfig.cs

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
-   routes.MapRoute(
-       name: "Default",
-       url: "{controller}/{action}/{id}",
-       defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
-   );
+   routes.MapRoute(
+       name: "Default",
+       url: "{*url}",
+       defaults: new { controller = "Home", action = "Index" }
+   );
}

BundleConfigの変更

バンドルして生成された3種のJavascriptファイルは、BundleConfigでBundleして出力します。

起点をindex.htmlではなくしたので、ComponenntのStylesに関しては、今回の場合は使用し辛いので cs CSSもComponentで取り込まず、外部から直接使用します。

app.component.ts

@Component({
    selector: 'my-app',
    templateUrl: './app.component.html',
-   styles: ['./app.component.css']
})

なのでCSSもSCSS等でまとめて作っちゃうのがいいかもしれませんね。

今回はチュートリアルで作成したCSSをSCSSで作成し直し、Styles.scssで作り直し

生成されたStyles.cssをバンドルします。

public static void RegisterBundles(BundleCollection bundles)
{
    bundles.Add(new ScriptBundle("~/bundles/src").Include(
                "~/Scripts/polyfills.js",
                "~/Scripts/vendor.js",
                "~/Scripts/app.js"
                ));
    bundles.Add(new StyleBundle("~/public/css").Include(
                "~/public/css/styles.css"));
}

_Layout.cshtmlの変更

_Layout.cshtmlの構成をAngularのindex.html同様の構成に変更してきます

<!DOCTYPE html>
<html>
<head>
    <base href="/">
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - Sample</title>
    @Styles.Render("~/public/css")
</head>
<body>
    <div class="container body-content">
        @RenderBody()
    </div>
    @Scripts.Render("~/bundles/src")
    @RenderSection("scripts", required: false)
</body>
</html>

Home.cshtmlを変更

Angularの起点をHome.cshtmlに作成します。

html <my-app>NOW LOADING...</my-app>

## App_Start系の変更

App_start等のIdentity等々を潰してまわります。

まぁ残しておいても良いですが。

Angularソースの変更

webconfigを使用した場合、相対ファイルパスで表示するimg等のオブジェクトデータは

Webconfigバンドル時によしなにしてScriptファイルにつっこまれます。

つまり、今回のようにASP.netのcshtmlに乗っける場合は非常に使いにくいです。

例えば下記のようにHTMLを記述してもAngularのBuild時点で存在しないとして怒られて終了します。

<img src = "Script/assets/hogehoge" />

なのでバインド変数を使用します。

app.component.ts

export class AppComponent {
    private angularPng: string = "public/images/angular.png";
}

app.component.html

<main>
    <h1>Hello from Angular App with Webpack</h1>
    <img [src]="angularPng"/>
</main>

VisualStudioでデバッグしましょう

通りましたね?

f:id:TakasDev:20170219112455p:plain

Advanced

root配下に切ったディレクトリにWebAppを配置したばあい

Angularで設定するBASE_HREFを[/]で設定していたら

切ったディレクトリ内に配置していても、AngularのRoutingはRootから生成されます。

そうなった場合、ASP.netAPIやimage等のObjectの使用に難が生じますので

BASE_HREFを動的に変更させる必要があります。

そしてそのHREFをAngularで取得すれば、API等とのやりとり等に使用できるようになります。

stackoverflow.com

stackoverflow.com

最後に

一応、今回作成したものは、下記に乗せています。

github.com

もくもく会でもくもくしたお話

Angular2ばっかりもアレなんで、下記のもくもく会に参加してXamarinばっかり考える時間を設けました。

jxug.connpass.com

今回はいうほどはまるポイントがなかったというか

クリティカルな参考サイトが多かったため、参考サイトの紹介程度です。

成果

  • EnumデータをバインドするPicker

  • ListView表示用のEnumデータコンバータ

  • SQLiteを使用したViewModelベースデータの登録と削除と画面反映

↓動作結果↓ f:id:TakasDev:20161010020434g:plain

EnumデータをバインドするPicker

  • 参考サイト

intellitect.com

qiita.com

ListView表示用のEnumデータコンバータ

  • 参考サイト

qiita.com

forums.xamarin.com

SQLiteを使用したViewModelベースデータの登録と削除と画面反映

  • 参考サイト

www.buildinsider.net

他内容で勉強中に行き当たった興味深い参考サイト

arteksoftware.com

Angular2のDIで遊ぶ

Angular2のDIについて

ちまちま書いてたんですが度重なるR.C版リリースと

正式版リリースでかなりゴテゴテに回りました。

目指すところ

  • Angular2のDIをざっくりと理解する

  • Angular2のDIの書き方を理解する。

DI

一般的にDependency Injection(依存注入)と呼ぼれています。

ASP.netだとUnityで実現されているものですかね?

「 Providerから提供されているインスタンスを特定の変数にInject(注入)する仕組み 」

qiita.com

Angular2においては、上位(または同じ)Componentで

Provideされたインスタンスが注入されるような動作となっています。

//DIされる対象
@Injectable()
export class HogeService{
    ...
}

@Component({
    selector: "hoge"
    template: "hogeComponent",
    provides: [HogeService]
})
export class HogeComponent{
    constructor(
        //インスタンスの注入
        _hogeService: HogeService
    ){}
    ...
}

上記のような使い方です。

これで何が嬉しいかというと、コンポーネント間でのデータのやり取りが

疎結合な状態で実現できるということですね。

例えば、上記コンストラクタで「_hogeService」が引数となっていますが

コンポーネント使用時は、これを意識せずに使用することが出来ます

(「出来る」であって意識しなくていいというわけではないです)。

コンポーネント間のDI

provideされたインスタンスは、下位階層のコンポーネントでも使用することが出来るようです。

その際、下位の階層では、provideプロパティを使用する必要はありません。

Angular2のDI構成は下図のようなイメージで可能かと思われます。

f:id:TakasDev:20161005134255p:plain

上記のCコンポーネントのような

Baseまでに別のコンポーネントを介する下位コンポーネントの場合でも

AコンポーネントでDIしていなくても

Cコンポーネントで上位コンポーネントでProvideされているインスタンスをDI可能なようです。

例ソース

例えば、下図の様な構成のコンポーネント郡を考えてみます。

f:id:TakasDev:20161005090256p:plain

BaseComponentにChildAComponent/ChildBComponentが存在します。

コンポーネントはそれぞれ独立したServiceを持っています。

Component郡を包括するServiceはMainServiceのみです。

上記の構成を元に、AとBそれぞれのコンポーネントから

メインに対して文字列をPushする処理を作成してみます。

結果は、下図のようになります。

背景色の異なる領域が、それぞれ別のComponentです。

f:id:TakasDev:20161005092157g:plain

上図の動作から、別々のコンポーネント

提供された同一のベースサービスを使用できていることがわかるかと思います。

上図の動作は、下記のようなソースの構成で動作しています。

BaseComponent

Component

@Component({
    selector: "base-comp",
    template: `<h2>PushResult</h2>
    <div *ngFor="let item of _baseService.baseData">
        pushComponent : {{item.pushComponent}} / pushText: {{item.pushText}}
    </div>
    <br>
    <br>
    <a-comp></a-comp>
    <b-comp></b-comp>`,
    providers: [BaseService]
})
export class BaseComponent{
    constructor(
        private _baseService: BaseService
    ){}
}

Service

@Injectable()
export class BaseService{
    baseData: PushFileds[] = [];
    pushData(component: string, pushText: string){
        var pushArray: PushFileds = {pushComponent: component, pushText: pushText};
        this.baseData.push(pushArray);
    }
    getData(component: string) : PushFileds[]{
        return this.baseData.filter((x:PushFileds) => x.pushComponent == component);
    }
}
class PushFileds{
    pushComponent: string;
    pushText: string;
}

Componentで、今回使用するモジュール全体で使用するBaseServiceをProvideしています。

Component上で使用するため、BaseComponentのConstructorでInjectしています。

BaseServiceは、子ServiceからPushされた値を、加工する機能と

加工した配列のフィルタを行い、結果配列を返却する機能を持っています。

ChildComponent

※A・B双方同様なソースのため、BComponentは割愛します。

Component

@Component({
    selector: "a-comp",
    providers:[ChildAService],
    templateUrl: "app/acomp/childa.html"
})
export class ChildAComponent{
    acompText: string;
    constructor(
        private _childAService: ChildAService
    ){}
    pushText(){
        this._childAService.pushText(this.acompText);
    }
}

Service

@Injectable()
export class ChildAService{
    acompPushData = [];
    constructor(
        private _baseService: BaseService
    ){}
    pushText(pushText: string){
        this._baseService.pushData("A", pushText);
        this.acompPushData = this._baseService.getData("A");
    }
}

html

<div style="background-color: azure;">
    This is Acomponent
    <input [(ngModel)]="acompText" />
    <button (click)="pushText()">Push</button>
    <br>
    りれき*
    <div *ngIf="_childAService.acompPushData">
        <div *ngFor="let item of _childAService.acompPushData">
            {{item.pushText}}
        </div>
    </div>
</div>

ComponentでAComponent独自のAServiceをProvideしていますが

AserviceのConstructor上で

上位のBaseComponentでProvideされたBaseServiceを注入しています。

これを、AService/Bserviceの双方で行っているため

AとBで同一のインスタンスオブジェクトを使用している状態となります。

AComponentのPushとBComponentのPush内容が

同一のbaseServiceのbaseDataにPushされ描画されます。

Angular2で和暦フォーマットDatePipeを作る。

業務で使っているだけあって、Angular2の知見が溜まってきたので放出放出。

こんな感じの表示ができるPipeを作りました。というお話です。

使用したのは「Angular2 R.C5」でございます。

f:id:TakasDev:20160812041422p:plain

Pipeってなんぞや

Angularでデータバインドする際、下記のように記述することで

Date型の値がFormatされて出力されるようになります。

{{ nowDate | date:"yyyy/MM/d" }}

↓こんな感じに表示されます。

f:id:TakasDev:20160812041012p:plain

Date型にかぎらず

例えば数値に対応する文字列

value=10000→画面上は「福沢諭吉」と表示

value=5000→画面上は「新渡戸稲造」と表示することもできるわけです。

和暦DateFormat

.Netは和暦もフォローしてくれていますが

Webは和暦をフォローしてくれません。

なので、和暦フォーマットファンクションを作成し

それを使用したDatePipeを作成してみます。

date-jp-format.ts(V1)

export class DateJpFormat {
    GetJpFormatDate(value: Date, format: string): string {
        if (!format) format = "yyyy年 MM月 DD日 aa曜日 hh:mm:ss";
        //和暦を取得
        var jpDate = value.toLocaleDateString("ja-JP-u-ca-japanese");
        var jpYear = jpDate.substring(0, jpDate.indexOf("/"));
        var jpYearArray = jpYear.match(/[0-9]+\.?[0-9]*/g);
        var jpYearNumber = "";
        //SafariなんかはArray=Nothingでくる!
        if (jpYearArray) {
            jpYearNumber = jpYearArray[0];
        }
        var regExpg = new RegExp(jpYearNumber, "g");

        var jpYearString = jpYear.replace(regExpg, "");
        var jpYearStringShort = jpYearString.slice(0, 1);

        //年変換(Full桁)
        format = format.replace("gg", jpYearString);
        format = format.replace("yyyy", ("" + (value.getFullYear())));

        //年変換(短縮)
        format = format.replace("g", jpYearStringShort);
        format = format.replace("yy", ("0" + (value.getFullYear())).slice(-2));
        format = format.replace("e", jpYearNumber);

        format = format.replace("MM", (("0" + (value.getMonth() + 1)).slice(-2)));
        format = format.replace("DD", (("0" + (value.getDay())).slice(-2)));
        format = format.replace("aa", weekDayList[value.getDay()]);
        format = format.replace("hh", (("0" + (value.getHours())).slice(-2)));
        format = format.replace("mm", (("0" + (value.getMinutes())).slice(-2)));
        format = format.replace("ss", (("0" + (value.getSeconds())).slice(-2)));
        return format;
    }
}
var weekDayList = ["日", "月", "火", "水", "木", "金", "土"];

とりあえず、DatePipeは抜きにして、どのように変換されるのかみてみます。

f:id:TakasDev:20160812041057p:plain

「ja-JP-u-ca-japanese」で元号「平成」等を表示してくれるのは

Chormeで、IEやEdgeなんかは、元号は取得してくれません(FireFoxは試してないです)。

Safariに至っては和暦の年数すら取得できません。

ベースはこれでいいかもしれませんが、元号が取れなかった場合のことを考え

自前で実装する他なさそうです。

Formatの実装

1. 和暦に関する情報を準備

和暦換算に必要な情報を作成します。 ・jp-calender-info.ts

export const jpCalendarInfo: JpCalenderType[] = [
    {
        Name: "明治",
        StartDate: new Date("1868-09-08T00:00:00"),
        EndDate: new Date("1912-07-30T00:00:00")
    },
    {
        Name: "大正",
        StartDate: new Date("1912-07-30T00:00:00"),
        EndDate: new Date("1926-12-25T00:00:00")
    },
    {
        Name: "昭和",
        StartDate: new Date("1926-12-25T00:00:00"),
        EndDate: new Date("1989-01-08T00:00:00")
    },
    {
        Name: "平成",
        StartDate: new Date("1989-01-08T00:00:00"),
        EndDate: new Date("2087-12-31T23:59:59")
    }
];

export class JpCalenderType{
    Name: string;
    StartDate: Date;
    EndDate: Date;
}

2. 和暦算出ファンクションの作成

1で作成した構造体を、算出した日付でフィルタを掛け

和暦の元号と年数の算出を行うファンクションを作成します。

    private FormatJpYearString(formatDate: Date): string {
        var jpInfo = jpCalendarInfo
            .filter((x:JpCalenderType) => x.StartDate <= formatDate && formatDate < x.EndDate);
        return jpInfo[0].Name;
    }
    private FormatJpYearNumber(formatDate: Date): string {
        var jpInfo = jpCalendarInfo
            .filter((x:JpCalenderType) => x.StartDate <= formatDate && formatDate < x.EndDate);
        var nowYear = formatDate.getFullYear();
        var baseYear = jpInfo[0].StartDate.getFullYear() - 1;
        return String(nowYear - baseYear);
    }

3.date-jp-format.tsの修正

上記のファンクションを合わせると、date-jp-format.tsは下記のようになります。

今回作成したファンクションは応急処置的な作りになっていますので

基本的にはブラウザ側で取得できたら、その値を使用する感じになっています。

ここで問題なのが「jpYearString 」にIEの場合ナニが入っているのか?ということです。

Emptyに見えますが実は「」という文字が入ってます。

ハイ。見えませんね。

ここにはUTF-16のU+200E文字コードの文字が入っているようです。

今回、かなり力技で出力された見えない文字をコピって貼り付けてますが

本来であれば文字コード変換等を行って、U+200E文字を潰すなりしなければならないはずです。

date-jp-format.ts

import { jpCalendarInfo, JpCalenderType } from "../function/jp-calender-info";

export class DateJpFormat {
    GetFormatDate(getValue: any, format: string): string {
        //取得した値を日付変換
        var value = new Date(getValue);

        //Format未指定の場合は西暦表示
        if (!format) format = "yyyy年 MM月 DD日 aa曜日 hh:mm:ss";
        //和暦を取得
        var jpYearNumber = this.FormatJpYearNumber(value);
        var jpYearString = this.FormatJpYearString(value);
        var jpYearStringShort = jpYearString.slice(0, 1);

        //年変換(Full桁)
        format = format.replace("gg", jpYearString);
        format = format.replace("yyyy", ("" + (value.getFullYear())));

        //年変換(短縮)
        format = format.replace("g", jpYearStringShort);
        format = format.replace("yy", ("0" + (value.getFullYear())).slice(-2));
        format = format.replace("e", jpYearNumber);

        format = format.replace("MM", (("0" + (value.getMonth() + 1)).slice(-2)));
        format = format.replace("DD", (("0" + (value.getDate())).slice(-2)));
        format = format.replace("aa", weekDayList[value.getDay()]);
        format = format.replace("hh", (("0" + (value.getHours())).slice(-2)));
        format = format.replace("mm", (("0" + (value.getMinutes())).slice(-2)));
        format = format.replace("ss", (("0" + (value.getSeconds())).slice(-2)));
        return format;
    }
    private FormatJpYearString(formatDate: Date): string {
        var jpInfo = jpCalendarInfo
            .filter((x: JpCalenderType) => x.StartDate <= formatDate && formatDate < x.EndDate);
        return jpInfo[0].Name;
    }
    private FormatJpYearNumber(formatDate: Date): string {
        var jpInfo = jpCalendarInfo
            .filter((x: JpCalenderType) => x.StartDate <= formatDate && formatDate < x.EndDate);
        var nowYear = formatDate.getFullYear();
        var baseYear = jpInfo[0].StartDate.getFullYear() - 1;
        return String(nowYear - baseYear);
    }
}

var weekDayList = ["日", "月", "火", "水", "木", "金", "土"];

結果、Edgeでも下図のように表示されるようになりました。

f:id:TakasDev:20160812041231p:plain

4.DatePipeを作る

「@angular/core」の「Pipe」「PipeTransform 」を使用します。

@Pipe以降の「name」でHTML上に記述する、Pipeの名称を設定します。

transform()内部は表示するデータのFormatを行っている部分です。

今回は日付のデータと文字列を使用したフォーマットを行います。

結果、下記のようなソースコードとなります。

jp-date-pipe.ts

import { Pipe, PipeTransform } from "@angular/core";
import { DateJpFormat } from "../function/date-jp-format"

@Pipe({ name: "JpDatePipe" })
export class JpDatePipe implements PipeTransform {
    dateJpFormat: DateJpFormat;
    transform(value: Date, args: any[]) {
        if (value) {
            this.dateJpFormat = new DateJpFormat();
            var formatString = String(args);
            return this.dateJpFormat.GetJpFormatDate(value, formatString);
        }
    }
}

5.DatePipeを使う

今回作成したPipeを使用したいコンポーネント

「独自設定したPipeを使いますよ」と設定しなければ当然使えません。

{pipes: [JpDatePipe]}で使用するPipeを宣言することで使えるようになります。

下記のような記述で使用します。 {{nowDate | JpDatePipe:[Formatする文字列]}}

最終的に下記のようなソースコードになりました。 date-format-page.component.ts

import { Component, Pipe } from "@angular/core";
import { DateJpFormat } from "../../function/date-jp-format";
import { JpDatePipe } from "../../custom-pipe/jp-date-pipe";
@Component({
  pipes: [JpDatePipe],
  template: `
    <h2>↓変換日付↓</h2> 
    {{nowDate}}
   <h2>↓DatePipe変換↓</h2>
    <div *ngFor="let outputFormat of formats">
      {{nowDate | JpDatePipe:[outputFormat]}}
    </div>
  `
})
export class DateFormatPageComponent {
    nowDate: Date;
    formats: string[];
    _dateJpFormat: DateJpFormat;
    constructor(){
        this.nowDate = new Date();
        this.formats= [
            "gg",
            "yy",
            "yyyy",
            "gge",
            "ge",
            "gge年MM月",
            "yyyy年MM月",
            "gge年MM月DD日",
            "yyyy年MM月DD日",
            "gge年MM月DD日aa曜日",
            "yyyy年MM月DD日aa曜日",
            "gge年MM月DD日aa曜日(hh)",
            "yyyy年MM月DD日aa曜日(hh)",
            "gge年MM月DD日aa曜日(hh)",
            "yyyy年MM月DD日aa曜日(hh)",
            "gge年MM月DD日aa曜日(hh:mm)",
            "yyyy年MM月DD日aa曜日(hh:mm)",
            "gge年MM月DD日aa曜日(hh:mm:ss)",
            "yyyy年MM月DD日aa曜日(hh:mm:ss)",
            "本日、gge年MM月DD日aa曜日です!!",
        ];
    }   
}

やたらと長いですが、結構な数のFormatを試しているので長くなっています。

肝の部分は下記のHTMLですね。

<h2>↓DatePipe変換↓</h2>
<div *ngFor="let outputFormat of formats">
    {{nowDate | JpDatePipe:[outputFormat]}}
</div>

Tsで宣言したFormatをすべて表示するHTMLとなります。

JpDatePipeという一つの変数から、ありとあらゆる表示の仕方が可能となっています。

結果、Webページ上には下図のように表示されました。

f:id:TakasDev:20160812041422p:plain

まとめ

上記のとおり、日付情報自体は同じ変数を使いまわしてるので

Pipeを使えばView用の変数と登録用の変数を分ける…

なんてことはしなくても良くなるということですね。

便利。

Xamarinでもくもくしたお話し

今日、昨日と両日もくもく会に参加してました。

JXUG東京もくもく会と、業務系システム開発勉強会でもくもくしていました。

どっちもXamarinでもくもくしました。一応それらの進展を。

ListViewにViewModelをバインドしようというお話。

まずは参考

ListView Data Sources - Xamarin

ytabuchi.hatenablog.com

ソース

まぁなんてことはない、普通のListViewバインディングです。

ViewModelとListViewのバインディングはかなり簡単に行えます。

//今回は初期ViewModelのデータをつっこんでおく
var searchInfo = new ObservableCollection<SearchInfo>
{
    new SearchInfo { Detail = "高橋 慶太郎", SearchType = ComicTokutenEnum.SearchType.AuthorSearch},
    new SearchInfo { Detail = "弐瓶 勉", SearchType = ComicTokutenEnum.SearchType.AuthorSearch},
    new SearchInfo { Detail = "Pumpkin Scissors", SearchType = ComicTokutenEnum.SearchType.TitleSearch},
    new SearchInfo { Detail = "メイドインアビス", SearchType = ComicTokutenEnum.SearchType.TitleSearch}
};

//データバインド
this.BindingContext = searchInfo;

実行結果

トグルの画面が崩れるのはエミュレータゆえなのかなぁ? f:id:TakasDev:20160612233854p:plain

ModernHttpClient + HtmlAgilityPack

XamarinでWebページを解析しよう!というお話。

こちらも比較的簡単に実現できました。

まずは参考

blog.ch3cooh.jp

Xamarin のプロジェクトで NuGet を使用する : XLsoft エクセルソフト

ModernHttpClient

Nativeに依存しないWeb通信が可能となる。

NugetでGet可能。 f:id:TakasDev:20160612233112p:plain

HtmlAgilityPack

.NetのHTMLパーサーの定番。

NuGetの取得だけでも使用できるようになっているようです。

f:id:TakasDev:20160612233217p:plain

処理。

"@CH3COOHさんの処理内容にModernHttpClientの使用を追加した感じです。

ほぼ同内容の処理は下コードな感じになりました。 (パース部分はちょっと手抜きしてますけど)

・ソース

//画面EntryからCode値を取得
var code = CompanyId.Text;
var urlstring = string.Format("http://stocks.finance.yahoo.co.jp/stocks/detail/?code={0}", code);

var httpClient = new HttpClient(new NativeMessageHandler());
var response = await httpClient.GetAsync(urlstring);

//html取得
var html = await response.Content.ReadAsStringAsync();

//AgilityPackにぶっこみ
var doc = new HtmlAgilityPack.HtmlDocument();
doc.LoadHtml(html);

//解析解析
var priceNode = doc.DocumentNode.Descendants("td")
    .Single(node => node.GetAttributeValue("class", "") == "stoksPrice");

var companyNode = doc.DocumentNode.Descendants("th")
    .Single(node => node.GetAttributeValue("class", "") == "symbol");

//表示
this.Outputlabel.Text = string.Format("{0}({1})の株価: {2}円", companyNode.InnerText, CompanyId.Text, priceNode.InnerHtml);

実行結果

f:id:TakasDev:20160612233803p:plain

後回しにしたこと

本当はAmazonの商品検索APIを使用して

XMLの解析をしてみたかったのだけど、Amazonが提供しているサンプル

SignedRequestHelperがSystem.Webをバリバリ使っているんで

結構書き直さなきゃいけない印章。

参考: Product Advertising API Signed Requests Sample Code - C# REST/QUERY : Sample Code & Libraries : Amazon Web Services

blog.nakajix.jp

もくもく会中にViewに表示できるなにかを作りたかったので

後に回しました(時間が余って作業したけど結局Amazonのは終わらなかった…)

最後に

もくもく会楽しかったのでまた参加したいです。

Angular2Tutorial + VisualStudio2015

Angular2のページで提供されているチュートリアル

MVC4でやっちゃおうというものです(準備編)

基本的に前回作ったソリューションで

新しいものに入れ替えていくようなイメージとなります。

TutorialSite

angular.io

package.jsonを改造

前回作成したAngularの情報はちと古めだったみたいです。

チュートリアルのは、最新の動向にあわせている(はず)なので

そちらの流行によせておきます。

Tutorialで増えているファイルを追加していく。

上記それぞれが増えているので、ソリューションに追加します。

BundleConfigを変更

    bundles.Add(New ScriptBundle("~/bundles/angular2").Include(
        "~/node_modules/core-js/client/shim.min.js",
        "~/node_modules/zone.js/dist/zone.js",
        "~/node_modules/reflect-metadata/Reflect.js",
        "~/node_modules/systemjs/dist/system.src.js",
        "~/systemjs.config.js"))

app.tsを変更

Tutorialと同一ソースに変更します。

はまりやすいポイント

基本的には、チュートリアルのソースと見比べ

変更部分を反映させる方法でうまくいきました。

package.jsonを入替えて、パッケージのインストールをすると

下記のサイトで報告されているようなエラーが複数発生します。

stackoverflow.com

ただ、GitHubで報告された結果、解決方法が反映されているようですので

typings.json等の新しいJsonファイルを追加することで、解消されます。

エラーが出てもそのまま立ち止まらず、進めていくのがいいかもしれません。

github.com

結果Tutorialをすすめることが出来ました

f:id:TakasDev:20160611002933p:plain

構成は下図のような感じです。

f:id:TakasDev:20160611005803p:plain

Angular2 + VisualStudio(ASP.NET MVC"4") + VisualBasic.net

風邪ひいてたりなんだりで全然ブログ更新してませんでしたー。っと。

Angular2とASP.NET MVC"4"をお仕事でいじることになりました。

Angular2 + VisualStudio(ASP.NET MVC"4") + VisualBasic.net

ニッチすぎて参考文献が少なく、VS上で動かすまでに色々と面倒くさかったのでメモ残します。

使用環境

  • VisualStudio2015(Update2)
  • TypeScript(1.8.29.0)

環境構築

1.ひとまずプロジェクトを作成

f:id:TakasDev:20160609221724p:plain
業務の都合上VB.netでプロジェクト作成していますが

今回記述ソースは.net側は、ほとんど何もありません。

VB.netなのでMVC5はテンプレート上存在しないので、MVC4を選択しました。

2.npmのpackage.jsonファイルを追加

rootにpackage.jsonを追加します。

記述するソースは下記のとおり。

{
  "name": "angular2-quickstart",
  "version": "1.0.0",
  "scripts": {
    "tsc": "tsc",
    "tsc:w": "tsc -w",
    "lite": "lite-server",
    "start": "concurrent \"npm run tsc:w\" \"npm run lite\" "
  },
   "license": "ISC",
  "dependencies": {
    "angular2": "2.0.0-beta.1",
    "systemjs": "0.19.6",
    "es6-promise": "^3.0.2",
    "es6-shim": "^0.33.3",
    "reflect-metadata": "0.1.2",
    "rxjs": "5.0.0-beta.0",
    "zone.js": "0.5.10"
  },
  "devDependencies": {
    "concurrently": "^1.0.0",
    "lite-server": "^1.3.2",
    "typescript": "^1.7.5" 
  }
} 

4.パッケージインストール

f:id:TakasDev:20160609222757p:plain

5.Rootディレクトリにtsconfig.jsonを作成

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": false,
    "outDir": "app/"
  },
  "exclude": [
    "node_modules"
  ]
}

outDirに.tsファイルから生成した.jsファイルが出力されます。

6.BundleConfigにAngular2読み込みを追加

        bundles.Add(New ScriptBundle("~/bundles/angular2").Include(
            "~/node_modules/es6-shim/es6-shim.js",
            "~/node_modules/systemjs/dist/system-polyfills.js",
            "~/node_modules/angular2/bundles/angular2-polyfills.js",
            "~/node_modules/systemjs/dist/system.src.js",
            "~/node_modules/rxjs/bundles/rx.js",
            "~/node_modules/angular2/bundles/angular2.dev.js",
            "~/node_modules/angular2/bundles/router.dev.js",
            "~/node_modules/angular2/bundles/http.dev.js"
            ))

7.app.tsを作成

scriptsディレクトリにappディレクトリを作成。
appディレクトリにapp.tsファイルを作成。

import {Component} from 'angular2/core';
@Component({
    selector: 'my-app',
    template: '<h1>Angular 2 Sample Application</h1>'
})
export class AppComponent { }

8.エラー発生

app.tsに下記のエラーが発生
f:id:TakasDev:20160609224013p:plain

まずひとつ。TypeScript古いですよー。ってメッセージが出る。

これ、Resharperが入っている場合にでるエラーだそう。

ReSharperの設定をいじることで解決。

参考:
stackoverflow.com


残りのエラー
f:id:TakasDev:20160609224205p:plain

これは、promise.d.tsに

declare var Promise: PromiseConstructor;

を加えることで解決できるらしい。

参考:
github.com

最後 _Layout.vbhtmlを改造

HEADに下記

    @Scripts.Render("~/bundles/angular2")
    <script>
      System.config({
        transpiler: 'typescript',
        typescriptOptions: { emitDecoratorMetadata: true },
        packages: {'app': {defaultExtension: 'js'}}
      });
      System.import('app/boot')
            .then(null, console.error.bind(console));
    </script>

BODYに下記を記述

<my-app>Loading...</my-app>

結果

f:id:TakasDev:20160609224654p:plain
表示できました。

f:id:TakasDev:20160609225634p:plain
最終出力時点のディレクトリ構成は上図のとおりです。

というわけで、Angular2の開発実験場が整ったわけです。

長かった…。

Xamarinやろーっと。