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

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

エンプラ脳でAngularのReactiveFormsを使ってみる

AngularのReactiveFormsは素敵なFormModuleだと思います。

自分も実際の作業の際には非常にお世話になりました。

ReactiveFormsについて

https://angular.io/docs/ts/latest/guide/reactive-forms.html

https://angular.io/docs/ts/latest/cookbook/form-validation.html

上記のAngularの公式サイトが大変参考になります。

基本のキは、上記サイトで学習できるので、エンプラ脳でReactiveFormsの利用方法を考えてみようと思います。

記事作成当時のAngularはV4です。

あと、これベストプラクティスかどうかはかなり怪しいので、色々ご意見いただけるとありがたいです。

Formでほしい機能を考える

さて、エンプラな頭で考えたとき、WebApplicationは同じような機能の複数の入力Formを持つかとおもいます。

入力Formは複数あれど、それぞれの画面で共通してほしい機能は存在するはずです。

さくっと考えて下記な感じでしょうか。

  1. Validarionエラーチェック
  2. エラーメッセージ
  3. ページ遷移時の挙動
  4. データ確定処理

同一の機能であれば、これらを共通のロジックでまとめて作りたい!

で、複数のComponentでまとめて使いたい!

ということで、作ってみようと思います。

結果、下図のような動きになります。色々サボってるので動きがちょいアレですが…

f:id:TakasDev:20170614152820g:plain

Validartionエラーチェック

これは、Angularの公式リファレンスにかかれているとおりの機能ですね。

ただ、各Componentで共通した機能として作りたいので、ReactiveFormsの諸々の処理を実装したBaseClassを作成します。

各ComponentでBaseClassをExtendsしてValidationの処理を書かなくていい感じに仕上げます。

Angular v4 ReactiveFormのやつ

また、Validationの設定を全部ComponentClassの中に書いていくのも邪魔くさいので

ValidationのSetting部分を切り出します。

Angularv4ReactiveFormのやつ

作成するComponentでBaseComponentをExtendsして、上記で作成したValidarionのセッティングを読み込みます。

Angularv4ReactiveFormのやつ

BaseComponentで、ValidationCheckに必要な作業をほぼ行っているので

実際のComponentに記述する量はガクッと減るかと思います。

getterでValidationSettingのコントロール名を返しているのは、ComponentのHtml内で楽に使用するためです。

HTMLは下記のような作りになっています。

地味に、AngularMaterialを使用して実装しています。

gistf9b185b48afceca9c50a04637a200300

formControlのNameの定義部分が[formControlName]となっているのが地味な味噌ですね。

エラーメッセージを格納しているformErros等の指定も、上記のようにしています。

ValidationSettingで設定しているCONSTのキーワード値を変更すると、関連項目のすべてに適用されるため

「DBの項目名称と一致してなかったテヘペロ」って場合も、最小限の変更で適切な箇所に変更を波及されます。

エラーメッセージ

エラーメッセージについては、

「必須ですよ」とか「文字列Overですよ」とか一般的なエラー内容は集約して管理したいので

エラーメッセージ生成Classを作成しました。

Static宣言してnewなしで使えるようにしています。

Angularv4ReactiveFormのやつ

ページ遷移時の処理

入力中にページ遷移する場合、「いいの?」ってダイアログ出したい時があります。

そんな場合は、RouterのCanDeactivateを使用するのが良いですね。

下記の感じで作成しました。

Angularv4ReactiveFormのやつ

CanDeactivateを使用する際にGenericsにComponent型を突っ込みます。

その際、BaseComponentを継承して作成されたComponentと指定することで

Activate判断処理において、Component内の処理を呼び出せるようにします。

Componentの「canDeActivateInputPage」処理では、独自のダイアログをだすなり

何かの条件のときのみ確認するなり、よしなに処理を実装します。

今回はBaseComponentに処理を記述しましたが、継承先のComponentごとに実装をバラすこともできます。

Routerでは下記のように設定し、ページ遷移時に上記で作成したGuard処理が実行されるようにします。

Angularv4ReactiveFormのやつ

データ確定時の処理

初っ端開いた状態だと、formはCleanな状態です。

Submitが走るとエラー状態にはなるものの

ValueChangeイベントが走らないため、FormsErrorsにエラー内容が格納されません。

なので、内部の全項目をDirty状態にして、ValidationCheckをあえて走らせる処理を実装します。

上記BaseComponentの↓の実装部分ですね。

    allControlReCheck() {
        for (const field in this.formErrors) {
            const control = this.inputForm.controls[field];
            control.markAsDirty();
            this.onValueChange();
        }
    }

あとは複製…

ValidationSettingとHtmlとComponentについては

画面に設定する項目に関するものだけを変更すれば、新しい画面が比較的ラクに作れるようになります。

最後に

今回作成したソースは下記リポジトリで管理しています。

github.com

AngularでCSVをAPIからDLするときに色々したお話

業務用のアプリケーション作ってるときに大概あるのがCSV出力ですが

WEBでCSV出力するときに、ちょいとはまったのでメモがてらに記事投稿します。

記事投稿時のAngularはV4です。

IEで」下図のような感じで動くのを作ります

f:id:TakasDev:20170615123619g:plain

エクスポートしたCSVどうやってWEBで受け取るの?

一番単純な方法はAPIのURLを叩くのみですね。

window.location.href = "CSVを出力するAPIのURL";

CSVのレスポンスがあれば勝手にDLが始まります。

ただ、Chromeとかだと問題ないのですが

IEとかEdgeとかだと、utf-8のFormatでDLしようとしやがるので

場合によっては(shift-jisのファイルとか…)文字化けします。

こんな感じで…

f:id:TakasDev:20170615122428p:plain

あと、上記の方法だとビジネスロジックのエラー発生時にハンドリングしづらいです。

対策

受け取ったCSVデータからBLOBオブジェクトを生成

そいつに名前をつけてやる形で日本語名称の文字化けを解消します。

TypeScriptのAPIの受け口は下記のように作ります。

参考

github.com

webapi.ts - CSVImportするやつ

readyStateが完了したとき、どのような状態で返却されたかによってresponseTypeを決定します。

(BLOBのままだとresponseTextでエラー吐いて使えないですからね)

WebAPI側では、ビジネスロジックエラーが発生した場合、InternalServerErrorのステータスで返却しています。

BLOB化したあとはCSVファイルとしてDLさせます。

ただ、IE系列とそれ以外とでロジックがちがうので下記のような感じなります。

component - CSVをエクスポートするやつ

最後に

今回作成したソースは下記リポジトリで管理しています。

github.com

Azureを使ってサクッと動画共有サービスを作る

YouTube等の外部Serviceを使用してもいいですが

Privateな環境で動画のアップロードをしたい場合があります

(業務利用ぐらいしか思いつきませんが…)

AzureのMediaService等を使っても良いかもしれませんが

同じくAzureのBlobStorageServiceを使用し、AzureのSDKを利用すれば

手っ取り早くプライベートな環境でビデオのアップロード&視聴が可能となります。

Angular+ASP.netで動画ファイルのアップロードと

視聴ページの作成を行ってみたいと思います

Azureの設定

CORSの設定

CORS(クロスオリジンリソース共有)を設定します。

Storageコンテナにアクセスするため、クロスドメインでアクセスできるように設定を行います。

参考にしたサイトでは、スクリプトで設定の変更を行っていますが

AzureのPortal上からも変更できるようです。

tech-blog.cloud-config.jp

docs.microsoft.com

ポータルでCORSの設定を行う

1.リソースグルプでストレージアカウントを選択

f:id:TakasDev:20170422012256j:plain

2.ストレージアカウントの設定から「CORS」を選択

f:id:TakasDev:20170422012321j:plain

3.CORSの設定がないと思うので、「追加」を選択

f:id:TakasDev:20170422012349j:plain

4.上記の参考URLの内容をもとに、CORSの設定を行う

f:id:TakasDev:20170422012433j:plain

オブジェクトのAPIへのアップロード

前準備

ASP.Netはアップロードファイルの上限がデフォルト4MBとなっているので

受付可能なファイルサイズを拡張して上げる必要があります。

tarcvf.blogspot.jp

web.config

  <system.web>
    <authentication mode="None" />
    <compilation debug="true" targetFramework="4.5.2" />
    <!--ファイルサイズの上限を変更しておく -->
    <httpRuntime targetFramework="4.5.2" maxRequestLength="102400" />
  </system.web>

Webページからのファイルのアップロード

WebAPIへのアップロード

inputタグの[type=file accept=‘video/*’]であれば、取得対象が動画ファイルとなります。

(change)でファイル変更時のイベントを補足し、WebAPIにファイルを送る処理を実装します。

今回は横着かまして、Change即WebAPIへって感じの処理ですが

もちろん(change)でオブジェクト退避させ

Submit等のイベントを起点にAPIへおくるのが定石ですね。

<input type='file' accept='video/*' (change)="onChangeInput($event)" />
// Component
private onChangeInput(el: any) {
    // イベントで変更されたValue=ファイルオブジェクトを取得
    let file = el.target.files[0];
    let formData = new FormData();
    formData.append('uploadFile', file, file.name);
    this._webapi.putUploadFile(formData).subscribe(data => {
        console.log(data);
    });
}
//----------------------------------------------------------
//webapiに送る処理
putUploadFile(formData: FormData): Observable<any> {
    return this.postFileData<any>("api/FileOperation", formData);
}

WebApiではFormデータを送信する

//httpのPostリクエストでファイルを送信
private postFileData<T>(url: string, sendData: FormData) {
    return this.http
        .post(url, sendData, <headersContent>)
        .map(res => res.json())
        .catch(error => {
            alert(error);
            return Observable.throw(error)
        });
}

WebAPIからAzureにBlobデータを登録する

POSTデータからファイルオブジェクトの取得

WebAPIではPOSTされたデータからファイル情報等を抜き出します。

public async Task<HttpResponseMessage> Post()
{
    var response = new HttpResponseMessage();
    var httpRequest = HttpContext.Current.Request;
    var mediaType = Request.Content.Headers.ContentType.MediaType;
    if (httpRequest.Files.Count > 0)
    {
        foreach (string file in httpRequest.Files)
        {
            var postedFile = httpRequest.Files[file];
            /*→Azureの登録処理へ→*/
        }
    }
    return response;
}
コンテナにオブジェクトの登録

まずはAzureSDKを使用して、登録対象ストレージを選択(なければ作成)します。

var credentials = new StorageCredentials(<ACCOUNT_NAME>, <ACCOUNT_KEY>);
var storageAccount = new CloudStorageAccount(credentials, true);
var blobClient = storageAccount.CreateCloudBlobClient();
container = blobClient.GetContainerReference(<CONTAINER_NAME>);
container.CreateIfNotExists();

gooner.hateblo.jp

AzureのAPIはファイルはByte引数となりますので

httpRequestのFileのStream型をByte型に変換してAzureのAPIに渡します。

[GetBlockBlobReference]でAzureにUpload出来る形にした後

[UploadFromByteArrayAsync]でAzureにアップロードします。

var sendByteData = //file.InputStreamをByte[]変換;
//Blobにアップロードする対象のファイルを決定
var blob = container.GetBlockBlobReference(FILE_NAME);
//メディアのタイプを決定
blob.Properties.ContentType = mediaType;
//Blobにアップロード
await blob.UploadFromByteArrayAsync(sendByteData, 0, sendByteData.Length);

SASを付与したURLの生成

BlobへのアクセスはShared Access Signature (SAS) で作成したURLを使用してアクセスします。

その為、作成したデータにアクセスするためのアドレスを生成します。

stackoverflow.com

//コンテナ内のファイルを指定
var blob = container.GetBlockBlobReference(FILE_NAME);
//SASアドレスの作成
var sas = blob.GetSharedAccessSignature(new SharedAccessBlobPolicy()
{
    //読取りのみ
    Permissions = SharedAccessBlobPermissions.Read,
    //1時間だけアクセス可能
    SharedAccessExpiryTime = DateTime.UtcNow.AddHours(1)
});
//BlobのURIとマージしてURLを生成
var Url = string.Format("{0}{1}", blob.Uri, sas);

VideoのSrcにバインドしVideoを見れるようにする

JSONでURLを返却したら、VideoのsourceにURLをバインドします。

stackoverflow.com

コストについて

日和って短い時間の動画しか上げていませんが

1週間ほど放置してもTOTALで1円以下ですんでいます。

Webサービスの一環として、かんたんでPrivateな動画共有サービスとしては導入しやすいのかなと思います

f:id:TakasDev:20170422014501p:plain

jQueyプラグインをAngular v2で使いたい(FullCalendar)

Angularを使用するようになって、jQuery自体は殆ど使用しなくなりました。

Componentベースでアプリを作成したときに

jQueryの処理が全体に波及するので使いにくかったんですね。

(特に同じ部品のComonentを親Componentで何度も使用しているときとか。)

ただ。jQueryの覇権時代が長いだけに

色々と使えるjQueryプラグインが存在するわけで

それを使いたいなー。という思いもあるわけです。

なので、今回は、jQueryプラグインをAngularで使用してみたいと思います。

How to include JQuery plugins in Angular 2 running via webpack

といっても、上記の記事の焼き直しみたいな感じになっていますが…

今回は手っ取り早くangular-cliを使用して、作ってみようと思います。

angular-cliを使用することで

比較的手軽にangularの開発&Build環境を構築することが可能です。

angular-cliの使い方は今回の本筋ではないので下記を参考に。

www.npmjs.com

developers.eure.jp

npmでパッケージをダウンロード

まずは、モノがないと始まらないので

jQueryと今回使用したいFullCalendarをnpmでダウンロードします。

npm install jquery

npm install fullcalendar

型定義ファイル+αをダウンロード

jQueryとFullCalendarは型定義ファイルが提供されているので

下記の2つの型定義ファイルをダウンロードします。

npm install @types/jquery

npm install @types/fullcalendar

また、FullCalendarの型定義ファイル上で

日付等の指定にmoment.jsの型が指定されているので

moment.jsと、その型定義ファイル @types/momentもダウンロードします。

npm install moment

npm install @types/moment

angular-cli.json

Angular-cliを使用している場合

外部コンポーネント等のCSSJavascriptを使用しやすいです。

angular-cli.jsonに外部JSやCSSを読み込むオプションが存在するためです。

今回、FullCalendarを使用するため、FullCalendarのCSSを別に読み込みます。

"styles": [
    "../node_modules/fullcalendar/dist/fullcalendar.css"
]

FullCalendarの実装

参考サイトの通り、FullCalendarを使用するComponentを分離して作成します。

Htmlにng-contentを指定し、そこにjQueryでFullCalendarを注入します。

HTML

<ng-content></ng-content>

Component上のHTMLにはng-contentしかないので

this.elementRef.nativeElementのみで指定できますね。

型定義ファイルを使用することで型検証されるようになるため

どのような値をつっこめば良いのかわかりやすくなります。ビバ型。

TypeScript

this.calendarElement = jQuery(this.elementRef.nativeElement);
this.calendarElement.fullCalendar({
    editable: true,
    defaultView: 'agendaWeek',
    selectable: true,
    selectHelper: true
});

仕上げ

あとは上記で作成したComponentを使用し、[ng serve]で実行して動作確認です。

表示するだけでは味気ないので、下のように書き換えてみました。

this.calendarElement = jQuery(this.elementRef.nativeElement);
this.calendarElement.fullCalendar({
    allDayText: '終日',
    editable: true,
    defaultView: 'agendaWeek',
    selectable: true,
    selectHelper: true,
    slotDuration: moment.duration(15, 'minutes'),
    slotLabelFormat : 'H:mm',
    buttonText: {
        today: '今日'
    },
    monthNames: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
    dayNames: ['日曜日', '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日'],
    dayNamesShort: ['日', '月', '火', '水', '木', '金', '土'],
    views: {
        month: {
            columnFormat: 'ddd'
        },
        week: {
            columnFormat: 'D[(]ddd[)]'
        },
        day: {
            columnFormat: 'D[(]ddd[)]'
        }
    },
    select: this.onSelectFunction,
    eventDragStop: this.onDropFunction,
    eventResize: this.onReSizeFunction,
    eventRender: this.addEventRender
});
/*FullCalendarで定義されていないイベントを付与する(ダブルクリック)*/
private addEventRender = function(event: FC.EventObject, element: any){
    element.bind('dblclick', () => {
        alert('onDoubleClickEbent');
        console.log(event);
    });
}
/*カレンダーセレクト時*/
private onSelectFunction = function(start, end){
    let eventTitle = prompt('予定の名称を入力せよ!!');
    let eventData: FC.EventObject = {
        start: start,
        end: end,
        title: eventTitle
    };
    this.calendarElement.fullCalendar('renderEvent', eventData, true);
    this.calendarElement.fullCalendar('unselect');
}.bind(this);
/*イベントオブジェクト移動時*/
private onDropFunction = function(event: FC.EventObject){
    console.log('Drop!');
    console.log(event);
}.bind(this);
/*イベントオブジェクトサイズ変更時*/
private onReSizeFunction = function(event: FC.EventObject){
    console.log('ReSize!');
    console.log(event);
}.bind(this);

日付の表示Formatを変更し、各イベントの処理を実装します。

イベントの引数はFunctionを指定しますが

その場合、calendarElementのobjectが[this]となります。

Angularを使用するうえで色々と使いづらいので、Functionにthisをbindして

FullCalendarを宣言しているClassのオブジェクトを[this]としています。

そうすることで、Angularの機能の諸々を使用しやすいようにしています。

動作させてみる

f:id:TakasDev:20170306212222g:plain

Console内部にイベントオブジェクトを表記するようにしています。

moment型のデータ等が問題なく取得できていることが確認できるかと思います。

最後に

一応今回のソースは下記に上げてあります。

Angular2Study/UseFullCalender at master · Takas0522/Angular2Study · GitHub

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され描画されます。