ある程度の文法のVue.js/Blazor/Angularにおいて同様の動作を実現するための方法についてまとめました。分離元
VueVsBlazorにソースコードをまとめています。
ちなみにVue.jsではTypeScriptを採用しています。
動作サンプルページ
Vue.js Sample
Blazor Sample
Angular Sample
文法の比較
ここでのサンプルは全てルーター上の1ページとして表現しています。
土台となるルーターの実装例から順に比較していきます。
0. 最小のコンポーネント
コンポーネントの内容はただのhtmlから可能です。
Vue.js
templateタグで囲みます。
1つのブロックのみとする必要があります。
<template>
<div>
<h1>Hello Vue.js!</h1>
</div>
</template>
Blazor
特別考慮する点はありません。
書いた分だけhtmlとして動きます。
通常、scriptタグは使用できません。
@page "/"
<div>
<h1>Hello Blazor!</h1>
</div>
Angulae
TypeScript内にhtmlをテンプレートとして挿入します。
htmlやcssはファイルを分ける方が主流のようですが、ここでは同一ファイルとして扱います。
const template=`
<div>
<h1>Hello Angular!</h1>
</div>
`;
import {Component} from "@angular/core";
@Component({
selector: "Index",
template: template
})
export class IndexComponent{}
1. スタイルシート
Vue.js
styleタグで囲むことでスタイルを記述できます。
style scopedを使うことでコンポーネント内に「スコープ」を作ることができます。
<template>
<div id=index>
<h1>Hello Vue.js!</h1>
</div>
</template>
<style scoped>
div#index{
color: #0000FF;
}
</style>
Blazor
CSSに関するBlazor特有の仕組みはありません。
諦めてbodyにstyleタグを置いています。
まぁ、大体のブラウザでは動きます。
html的には良いのか悪いのかわかりませんが。
@page "/StyleBlock"
<div id=index>
<h1>Hello Blazor!</h1>
</div>
<style>
div#index{
color: #0000FF;
}
</style>
Angular
htmlの中にstyleを記述することが認められています。
ここでは示しませんが別ファイルからインポートすることも可能です。
const template=`
<div id=index>
<h1>Hello Angular!</h1>
</div>
<style>
div#index{
color: #0000FF;
}
</style>
`;
import {Component} from "@angular/core";
@Component({
selector: "StyleBlock",
template: template
})
export class StyleBlockComponent{}
2. コードブロック/スクリプトの埋め込み
コードブロックで定義している変数はhtml内に埋め込むことができます。
(Vue.js/Angularは{{hoge}}(thisは常に付加される)、C#は@hogeで呼び出し)
Vue.js
scriptタグ内(TypeScriptを使う場合はlang=ts要)に記述します。
<template>
<div>
<h1>{{title}}</h1>
</div>
</template>
<script lang=ts>
import {Component,Vue} from "vue-property-decorator";
@Component
export default class ScriptBlock extends Vue{
title="Hello Vue.js!";
}
</script>
Blazor
@codeブロック内にスクリプト処理を記述します。
@page "/ScriptBlock"
<div>
<h1>@title</h1>
</div>
@code{
string title="Hello Blazor!";
}
Angular
そもそもTypeScriptベースなので特筆よることはあまり無いでしょうか。
○○.component.tsのクラス内に処理を記述します。
const template=`
<div>
<h1>{{title}}</h1>
</div>
`;
import {Component} from "@angular/core";
@Component({
selector: "ScriptBlock",
template: template
})
export class ScriptBlockComponent{
title="Hello Vue.js!";
}
3. html中への数式埋め込み
html中に埋め込めるのは変数のみではありません。
数式を埋め込むこともできます。
Vue.js
{{}}内ではグローバルオブジェクトを扱えないことについて注意します。
<template>
<div>
<h1>10!={{10*9*8*7*6*5*4*3*2*1}}</h1>
</div>
</template>
Blazor
@のエスケープは@@と記述します。
@page "/Formula"
<div>
<h1>10!=@(10*9*8*7*6*5*4*3*2*1)</h1>
</div>
Angular
Vue.jsと同様に{{}}内でグローバルオブジェクトを扱うことはできません。
const template=`
<div>
<h1>10!={{10*9*8*7*6*5*4*3*2*1}}</h1>
</div>
`;
import {Component} from "@angular/core";
@Component({
selector: "Formula",
template: template
})
export class FormulaComponent{}
4. ライフサイクルメソッド
Vue.js/Blazor/Angularにはhtmlのonload/unonloadのように
コンポーネントの状態でフックされるライフサイクルメソッドというものがあります。
項目 | Vue.js | Blazor | Angular |
---|---|---|---|
初期化前 | beforeCreate | ― | constructor※3 |
初期化後 | created | OnInitialized OnInitializedAsync |
― |
レンダリング前 | beforeMount | OnParametersSet OnParametersSetAsync |
― |
レンダリング後 | mounted | OnAfterRender OnAfterRenderAsync※1 |
ngOnInit |
変更前 | beforeUpdate | ― | ngDoCheck ngAfterViewInit |
変更後 | updated | OnAfterRender OnAfterRenderAsync※1 |
ngAfterViewChecked |
アクティブ化時 | activated | ― | ― |
非アクティブ化時 | deactivated | ― | ― |
破棄前 | beforeUpdate | ― | ― |
破棄時 | beforeDestroy | Dispose※2 | ngOnDestroy |
- ※1: firstRender引数で初回かどうか判別。
- ※2: ページリロード時には動作しない。
- ※3: サービスの登録にも使用される。
- ※4: Angularは効果がわかりにくいものが多いのでわかるものだけ。
Vue.jsのライフサイクル
Blazorのライフサイクル
Angularのライフサイクル
初期化処理は大体レンダリング後に該当する処理で間に合う気がします。
Blazorはデストラクタがページ更新時に動作しないので注意が必要です。
(その場合、現状としてはjsのunonloadをなんとか使うしかない?)
Vue.js
<template>
<div>
<h1>{{title}}</h1>
</div>
</template>
<script lang=ts>
import {Component,Vue} from "vue-property-decorator";
@Component
export default class LifeCycle extends Vue{
title="Hello Vue.js!";
async mounted(){
await new Promise(res=>setTimeout(res,5000));
this.title+=" 5s passed!";
}
}
</script>
Blazor
@page "/LifeCycle"
<div>
<h1>@title</h1>
</div>
@code{
string title="Hello Blazor!";
protected override async Task OnAfterRenderAsync(bool firstRender){
if(!firstRender) return;
await Task.Delay(5000);
title+=" 5s passed!";
StateHasChanged();
}
}
Angular
const template=`
<div>
<h1>{{title}}</h1>
</div>
`;
import {Component,OnInit} from "@angular/core";
@Component({
selector: "LifeCycle",
template: template
})
export class LifeCycleComponent implements OnInit{
title="Hello Angular!";
async ngOnInit(){
await new Promise(res=>setTimeout(res,5000));
this.title+=" 5s passed!";
}
}
5. DOM API
言語にJavaScript/TypeScriptを選択しているフレームワークでは
ブラウザ依存のDOM APIをそのまま使用することができます。
Vue.js
<template>
<div>
<h1>Alert</h1>
</div>
</template>
<script lang=ts>
import {Component,Vue} from "vue-property-decorator";
@Component
export default class UseDOMAPI extends Vue{
async mounted(){
var title=document.title;
alert(title);
}
}
</script>
Blazor
BlazorではJavaScriptに頼らずDOM APIを扱うことは不可能なので
JavaScriptのメソッドを呼び出す必要があります。
JavaScriptを使うにはIJSRuntimeをinjectして
IJSRuntime.InvoveAsync・IJSRuntime.InvokeVoidAsyncメソッドを呼び出します。
プロパティの類には一切取得できないのでその用途にはevalを使うか
別にjsファイルを用意して関数として呼び出す必要があります。
@page "/UseDOMAPI"
<div>
<h1>Alert</h1>
</div>
@inject IJSRuntime js
@code{
protected override async Task OnAfterRenderAsync(bool firstRender){
if(!firstRender) return;
var title=await js.InvokeAsync<string>("eval","document.title");
await js.InvokeVoidAsync("alert",title);
}
}
Angular
const template=`
<div>
<h1>Alert</h1>
</div>
`;
import {Component,OnInit} from "@angular/core";
@Component({
selector: "UseDOMAPI",
template: template
})
export class UseDOMAPIComponent implements OnInit{
ngOnInit(){
var title=document.title;
alert(title);
}
}
6. 双方向バインディング
Vue.js/Blazor/Angularではdocument.element.valueを直接操作する代わりに変数と要素の値をバインド(同期)します。
Vue.js
v-model属性を使用します。
<template>
<div>
<h1>{{title}}</h1>
<input v-model="title">
</div>
</template>
<script lang=ts>
import {Component,Vue} from "vue-property-decorator";
@Component
export default class BindingInput extends Vue{
title="Hello Vue.js!";
}
</script>
Blazor
@bind属性を使用します。
テキストの更新にリアルタイム性を求める場合は@bind-value/@bind-value:event属性を使用します(第20項参照)。
@page "/BindingInput"
<div>
<h1>@title</h1>
<input @bind="title">
</div>
@code{
string title="Hello Blazor!";
}
Angular
[(ngModel)]属性を使用します。
[]はスクリプト処理によって値が変更され、
()は人為的な操作により値に変更を与えられることを示します。
const template=`
<div>
<h1>{{title}}</h1>
<input [(ngModel)]="title">
</div>
`;
import {Component} from "@angular/core";
@Component({
selector: "BindingInput",
template: template
})
export class BindingInputComponent{
title="Hello Angular!";
}
7. 片方向バインディング
変数からdocument.element.valueを一方的に更新することもできます。
Vue.js
v-bind:valueを属性に指定します。
v-bind:はスクリプト処理によって値が変更されることを示します。
v-bind:は:のように省略することができるので:valueと書けます。
<template>
<div>
<h1>{{title}}</h1>
<input :value="title">
</div>
</template>
<script lang=ts>
import {Component,Vue} from "vue-property-decorator";
@Component
export default class BindingInputOneWay extends Vue{
title="Hello Vue.js!";
async mounted(){
for(;;){
await new Promise(res=>setTimeout(res,2000));
this.title+=">";
}
}
}
</script>
Blazor
valueへ直接@で変数として割り当てることができます。
@page "/BindingInputOneWay"
<div>
<h1>@title</h1>
<input value=@title>
</div>
@code{
string title="Hello Blazor!";
protected override async Task OnAfterRenderAsync(bool firstRender){
if(!firstRender) return;
for(;;){
await Task.Delay(2000);
title+=">";
StateHasChanged();
}
}
}
Angular
[ngModel]属性を使用します。
前項の[(ngModel)]から()(人為的な変更)を外したものとなります。
const template=`
<div>
<h1>{{title}}</h1>
<input [ngModel]="title">
</div>
`;
import {Component,OnInit} from "@angular/core";
@Component({
selector: "BindingInputOneWay",
template: template
})
export class BindingInputOneWayComponent implements OnInit{
title="Hello Angular!";
async ngOnInit() {
for(;;){
await new Promise(res=>setTimeout(res,2000));
this.title+=">";
}
}
}
8.イベントハンドラ
イベントハンドラはそれぞれちょいちょい表記が異なるので通常のhtmlについても併記します。
Vue.js/Blazorでは指定するものはメソッド名のみでメソッド呼び出しを意味する"()"は不要です。
HTML
<html>
<body>
<button onclick="openDialog()">Click Me!</button>
<script>
var title="Hello HTML!";
function openDialog(){
alert(title);
}
</script>
</body>
</html>
Vue.js
onclick属性の代わりにv-on:click属性を使用します。
v-on:は人為的な操作により値に変更を与えられることを示します。
v-on:は@と表記することもできるので@clickと書くことが出ます。
<template>
<div>
<button @click="openDialog">Click Me!</button>
</div>
</template>
<script lang=ts>
import {Component,Vue} from "vue-property-decorator";
@Component
export default class EventHandler extends Vue{
title="Hello Vue.js!";
openDialog(){
alert(this.title);
}
}
</script>
Blazor
通常のイベント名に@を付けたものがイベント属性となります。
つまりこの例では@onclick属性です。
@page "/EventHandler"
<div>
<button @onclick="openDialog">Click Me!</button>
</div>
@inject IJSRuntime js
@code{
string title="Hello Blazor!";
async void openDialog(){
await js.InvokeVoidAsync("alert",title);
}
}
Angular
onclick属性の代わりに(click)属性を使用します。
const template=`
<div>
<button (click)="openDialog()">Click Me!</button>
</div>
`;
import {Component} from "@angular/core";
@Component({
selector: "EventHandler",
template: template
})
export class EventHandlerComponent{
title="Hello Angular!";
openDialog(){
alert(this.title);
}
}
9. onchangeイベント
Vue.js/Angularでは@chenge/(change)イベントとv-model/[(ngModel)]を同時に使うことができますが、
Blazorでは@onchangeイベントと@bindを同時に使うことはできません。
Changeイベントは双方向バインディングの操作で内部的に使用されているため、
プロパティ(get/set)を咬ませることでイベントの発火を受け取ることができます。
Vue.js
<template>
<div>
<h1>Check: {{isChecked}}</h1>
<input id=chk type=checkbox v-model="chkChange">
<label for=chk>CheckBox</label>
</div>
</template>
<script lang=ts>
import {Component,Vue} from "vue-property-decorator";
@Component
export default class OnChangeEvent extends Vue{
isChecked=false;
get chkChange(){return this.isChecked;}
set chkChange(value:boolean){
this.isChecked=value;
alert(`Check: ${this.isChecked}`);
}
}
</script>
Blazor
@page "/OnChangeEvent"
<div>
<h1>Check: @isChecked</h1>
<input id=chk type=checkbox @bind="chkChange">@(""
)<label for=chk>CheckBox</label>
</div>
@inject IJSRuntime js;
@code{
bool isChecked=false;
bool chkChange{
get{return isChecked;}
set{
isChecked=value;
_=js.InvokeVoidAsync("alert",$"Check: {isChecked}");
}
}
}
Angular
const template=`
<div>
<h1>Check: {{isChecked}}</h1>
<input id=chk type=checkbox [(ngModel)]="chkChange">
<label for=chk>CheckBox</label>
</div>
`;
import {Component} from "@angular/core";
@Component({
selector: "OnChangeEvent",
template: template
})
export class OnChangeEventComponent{
isChecked=false;
get chkChange(){return this.isChecked;}
set chkChange(value:boolean){
this.isChecked=value;
alert(`Check: ${this.isChecked}`);
}
}
10. スタイルバインディング
スクリプトによるスタイルの変更はhtmlではdocument.element.styleの変更によって行われていました。
Vue.js/Blazor/Angularではその代わり属性に直接値をバインドさせることで変更を行います。
Vue.js
v-bind:style属性にJSON形式の文字列で渡します。
変更したいスタイルのキーに対してスタイルの文字列を返す処理を書き込むか
スタイルの含まれる文字列変数を割り当てます。
<template>
<div>
<h1>Check: {{isChecked}}</h1>
<input id=chk type=checkbox v-model="isChecked">
<label for=chk>CheckBox</label>
<div :style="{color: isChecked? 'blue': 'red'}">
Change Style!
</div>
</div>
</template>
<script lang=ts>
import {Component,Vue} from "vue-property-decorator";
@Component
export default class BindingStyle extends Vue{
isChecked=false;
}
</script>
Blazor
style属性に渡す文字列を変更することでスタイルの変更を行います。
(CSSそのものの書式で文字列として与える必要があります)
@page "/BindingStyle"
<div>
<h1>Check: @isChecked</h1>
<input id=chk type=checkbox @bind="isChecked">@(""
)<label for=chk>CheckBox</label>
<div style=@("color:"+(isChecked? "blue": "red"))>
Change Style!
</div>
</div>
@code{
bool isChecked=false;
}
Angular
[style.○○]属性に渡す文字列を変更することでスタイルの変更を行います。
○○には変更したスタイルを記述します。
const template=`
<div>
<h1>Check: {{isChecked}}</h1>
<input id=chk type=checkbox [(ngModel)]="isChecked">
<label for=chk>CheckBox</label>
<div [style.color]="isChecked? 'blue': 'red'">
Change Style!
</div>
</div>
`;
import {Component} from "@angular/core";
@Component({
selector: "BindingStyle",
template: template
})
export class BindingStyleComponent{
isChecked=false;
}
11. クラスバインディング
クラスについてもスタイルと同様にバインドできます。
Vue.js
v-bind:class属性にJSON形式の文字列で渡します。
変更したいクラス名のキーに対してboolean値を割り当てて行います。
<template>
<div>
<h1>Check: {{isChecked}}</h1>
<input id=chk type=checkbox v-model="isChecked">
<label for=chk>CheckBox</label>
<div :class="{clsA: isChecked, clsB: !isChecked}">
Change Style!
</div>
</div>
</template>
<style scoped>
.clsA{
color: blue;
font-size: 1.5em;
text-decoration: underline solid;
}
.clsB{
color: red;
font-size: 1.0em;
font-style: italic;
}
</style>
<script lang=ts>
import {Component,Vue} from "vue-property-decorator";
@Component
export default class BindingClass extends Vue{
isChecked=false;
}
</script>
Blazor
スタイルの変更と同様にclass属性に渡す文字列を直接編集します。
@page "/BindingClass"
<div>
<h1>Check: @isChecked</h1>
<input id=chk type=checkbox @bind="isChecked">@(""
)<label for=chk>CheckBox</label>
<div class=@(isChecked? "clsA": "clsB")>
Change Style!
</div>
</div>
<style>
.clsA{
color: blue;
font-size: 1.5em;
text-decoration: underline solid;
}
.clsB{
color: red;
font-size: 1.0em;
font-style: italic;
}
</style>
@code{
bool isChecked=false;
}
Angular
[class.○○]属性にboolean値を割り当てて適用/不適用を切り替えます。
○○には対応するクラス名を記述します。
const template=`
<div>
<h1>Check: {{isChecked}}</h1>
<input id=chk type=checkbox [(ngModel)]="isChecked">
<label for=chk>CheckBox</label>
<div [class.clsA]="isChecked" [class.clsB]="!isChecked">
Change Style!
</div>
</div>
<style>
.clsA{
color: blue;
font-size: 1.5em;
text-decoration: underline solid;
}
.clsB{
color: red;
font-size: 1.0em;
font-style: italic;
}
</style>
`;
import {Component} from "@angular/core";
@Component({
selector: "BindingClass",
template: template
})
export class BindingClassComponent{
isChecked=false;
}
12. if(場合分け)
Vue.js/Blazor/Angularでは場合分けで表示状態を変更できます。
Vue.js
v-if属性を対象の要素に含めると表示状態を変更できます。
v-ifで表示の切り替えを行うとライフサイクルが働くこととなります。
コンポーネントの状態を保ったまま表示切替を行いたい場合はv-showを使います。
(内部的にdisplay: none;を使っています)
<template>
<div>
<h1>Check: {{isChecked}}</h1>
<input id=chk type=checkbox v-model="isChecked">
<label for=chk>CheckBox</label>
<div v-if="isChecked">
<input>
</div>
<div v-show="isChecked">
<input>
</div>
</div>
</template>
<script lang=ts>
import {Component,Vue} from "vue-property-decorator";
@Component
export default class IfAndShow extends Vue{
isChecked=false;
}
</script>
Blazor
@ifを使います。
動作としてはVue.jsにおけるv-ifと同様です。
コンポーネントの状態を保つ場合はstyle/class属性で直接隠すようにします。
@page "/IfAndShow"
<div>
<h1>Check: @isChecked</h1>
<input id=chk type=checkbox @bind="isChecked">@(""
)<label for=chk>CheckBox</label>
@if(isChecked){
<div>
<input>
</div>
}
<div style=@("display:"+(isChecked? "": "none"))>
<input>
</div>
</div>
@code{
bool isChecked=false;
}
Angular
*ngIf属性を使用します。
コンポーネントの状態を保って非表示にする場合は[style.display]属性に"none"を与えます。
const template=`
<div>
<h1>Check: {{isChecked}}</h1>
<input id=chk type=checkbox [(ngModel)]="isChecked">
<label for=chk>CheckBox</label>
<div *ngIf="isChecked">
<input>
</div>
<div [style.display]="isChecked? '': 'none'">
<input>
</div>
</div>
`;
import {Component} from "@angular/core";
@Component({
selector: "IfAndShow",
template: template
})
export class IfAndShowComponent{
isChecked=false;
}
13. foreach(繰り返し)
Vue.js/Blazor/Angularでは同じ構成のタグであれば繰り返して表示させることができます。
v-bind:key/@keyについて
ループで生成されるリストにはコンポーネントの同一性などを担保するためにkey属性の追加が推奨されます。
(Blazorでは推奨されてない?)
動作が高速になるとも言われます。
v-bind:keyはVue.js、@keyはBlazorにおける表記方法です。
Vue.js
Blazor
Vue.js
v-for属性を繰り返したい要素に含めます。
これはtemplateタグを無名のタグとして使い、囲んでループさせても問題ありません。
大体の主流なブラウザではオブジェクトの順序は一定となりますが、仕様上で保障されていないので注意してください。
(Mapも一応使えますが情報量が少なくて少し怪しいです。
書き方はv-for="[key,value] of list]"と通常のfor文っぽく書くと使えるようです)
<template>
<div>
<div v-for="(isChecked,key) in dict">
<input :id="key" type=checkbox v-model="dict[key]" :key="key">
<label :for="key">{{key}}</label>
</div>
</div>
</template>
<script lang=ts>
import {Component,Vue} from "vue-property-decorator";
@Component
export default class ForEachLoop extends Vue{
dict:{[s:string]:boolean}={
A:true,
B:true,
C:true,
D:false,
E:false
};
}
</script>
Blazor
@for/@foreachを使います。
@page "/ForEachLoop"
<div>
@foreach(var (key,isChecked) in dict){
<div>
<input id=@key type=checkbox @bind="dict[key]" @key="key">@(""
)<label for=@key>@key</label>
</div>
}
</div>
@code{
Dictionary<string,bool> dict=new Dictionary<string,bool>{
{"A",true},
{"B",true},
{"C",true},
{"D",false},
{"E",false}
};
}
Angular
*ngFor属性を繰り返したい要素に含めます。
Vue.jsのtemplateと同様にng-containerを~要素として使用できます。
const template=`
<div>
<div *ngFor="let v of dict | keyvalue">
<input [id]="v.key" type=checkbox [(ngModel)]="dict[v.key]">
<label [for]="v.key">{{v.key}}</label>
</div>
</div>
`;
import {Component} from "@angular/core";
@Component({
selector: "ForEachLoop",
template: template
})
export class ForEachLoopComponent{
dict:{[s:string]:boolean}={
A:true,
B:true,
C:true,
D:false,
E:false
};
}
14. コンポーネントの追加
Vue.js/Blazor/Angularではhtmlタグ中に自作の要素(コンポーネント)を埋め込むことをできます。
Blazorでは自動で全てのコンポーネントを読み込みますが、
Vue.jsではimport文で読み込むコンポーネントを指定する必要があります。
Angularではapp.modile.tsに記載します。
Vue.js
<template>
<div>
<ComponentA />
<ComponentB />
</div>
</template>
<script lang=ts>
import {Component,Vue} from "vue-property-decorator";
import ComponentA from "@/components/ComponentA.vue";
import ComponentB from "@/components/ComponentB.vue";
@Component({
components:{
ComponentA,
ComponentB
}
})
export default class AddComponent extends Vue{}
</script>
<template>
<div>
<h3>ComponentA</h3>
<textarea></textarea>
</div>
</template>
<template>
<div>
<input id=chk type=checkbox>
<label for=chk>ComponentB</label>
</div>
</template>
Blazor
@page "/AddComponent"
<div>
<ComponentA />
<ComponentB />
</div>
<div>
<h3>ComponentA</h3>
<textarea></textarea>
</div>
<div>
<input id=chk type=checkbox>
<label for=chk>ComponentB</label>
</div>
Angular
const template=`
<div>
<ComponentA></ComponentA>
<ComponentB></ComponentB>
</div>
`;
import {Component} from "@angular/core";
@Component({
selector: "AddComponent",
templateUrl: template
})
export class AddComponentComponent{}
const template=`
<div>
<h3>ComponentA</h3>
<textarea></textarea>
</div>
`;
import {Component} from "@angular/core";
@Component({
selector: "ComponentA",
template: template
})
export class ComponentAComponent{}
const template=`
<div>
<input id=chk type=checkbox>
<label for=chk>ComponentB</label>
</div>
`;
import {Component} from "@angular/core";
@Component({
selector: "ComponentB",
templateUrl: template
})
export class ComponentBComponent{}
15. コンポーネントの属性
Vue.js/Blazor/Angularでは子コンポーネントに属性を与え、
与えられた子コンポーネント中でプロパティとして使用することができます。
Vue.jsでは@Prop、Blazorでは[Parameter]、Angularでは@Inputで属性名を指定します。
Vue.js
<template>
<div>
<ComponentC msg="View Message" color="#FF00FF" />
</div>
</template>
<script lang=ts>
import {Component,Vue} from "vue-property-decorator";
import ComponentC from "@/components/ComponentC.vue";
@Component({
components:{
ComponentC
}
})
export default class ComponentAttribute extends Vue{}
</script>
<template>
<div :style="{color: color}">
Input Attribute={{msg}}
</div>
</template>
<script lang=ts>
import {Component,Prop,Vue} from "vue-property-decorator";
@Component
export default class ComponentC extends Vue{
@Prop()
private msg:string;
@Prop()
private color:string;
}
</script>
Blazor
@page "/ComponentAttribute"
<div>
<ComponentC msg="View Message" color="#FF00FF" />
</div>
<div style=@($"color: {color}")>
Input Attribute=@msg
</div>
@code{
[Parameter]
public string msg{get;set;}
[Parameter]
public string color{get;set;}
}
Angular
const template=`
<div>
<ComponentC msg="View Message" color="#FF00FF"></ComponentC>
</div>
`;
import {Component} from "@angular/core";
@Component({
selector: "ComponentAttribute",
template: template
})
export class ComponentAttributeComponent{}
const template=`
<div [style.color]="color">
Input Attribute={{msg}}
</div>
`;
import {Component,Input} from "@angular/core";
@Component({
selector: "ComponentC",
template: template
})
export class ComponentCComponent{
@Input()
private msg: string;
@Input()
private color: string;
}
16. コンポーネントのメソッド呼び出し
Vue.js/Blazor/Angularでは子コンポーネント中のメンバーを呼び出すことができます。
Vue.js
ref属性でバインドする変数名を指定します。
クラス中でも使用するために宣言が必要です。
<template>
<div>
<Toast ref="toast" />
<button @click="viewToast">Click Me!</button>
</div>
</template>
<script lang=ts>
import {Component,Vue} from "vue-property-decorator";
import Toast from "@/components/Toast.vue";
@Component({
components:{
Toast
}
})
export default class ComponentMethod extends Vue{
$refs!:{toast: Toast};
async viewToast(){
await this.$refs.toast.show("View Torst!");
}
}
</script>
<template>
<dialog :open="isShow">
{{msg}}
</dialog>
</template>
<script lang=ts>
import {Component,Vue,Prop} from "vue-property-decorator";
@Component
export default class Toast extends Vue{
isShow=false;
msg="";
public async show(msg:string){
this.msg=msg;
this.isShow=true;
await new Promise(res=>setTimeout(res,1500));
this.isShow=false;
}
}
</script>
Blazor
@ref属性でバインドする変数名を指定します。
クラス中でも使用するために宣言が必要です。
@page "/ComponentMethod"
<div>
<Toast @ref="toast" />
<button @onclick="viewToast">Click Me!</button>
</div>
@code{
Toast toast;
async Task viewToast(){
await toast.show("View Toast!");
}
}
<dialog open=@isShow>
@msg
</dialog>
@code{
bool isShow=false;
string msg="";
public async Task show(string msg){
this.msg=msg;
isShow=true;
StateHasChanged();
await Task.Delay(2500);
isShow=false;
StateHasChanged();
}
}
Angular
コンポーネントをインポートし、クラス内で@ViewChildデコレータを割り当てることで使用します。
const template=`
<div>
<Toast></Toast>
<button (click)="viewToast()">Click Me!</button>
</div>
`;
import {Component,ViewChild} from "@angular/core";
import {ToastComponent} from "../components/Toast.component";
@Component({
selector: "ComponentMethod",
template: template
})
export class ComponentMethodComponent{
@ViewChild(ToastComponent,{static: false})
private toast:ToastComponent;
async viewToast(){
await this.toast.show("View Torst!");
}
}
const template=`
<dialog [open]="isShow">
{{msg}}
</dialog>
`;
import {Component} from "@angular/core";
@Component({
selector: "Toast",
template: template
})
export class ToastComponent{
isShow=false;
msg="";
public async show(msg:string){
this.msg=msg;
this.isShow=true;
await new Promise(res=>setTimeout(res,1500));
this.isShow=false;
}
}
17. 状態管理コンテナ
どのコンポーネントからでも参照できるグローバル変数のようなもの。
ここでは変数の読み書き程度の極々簡単のみ行っています。
Vue.jsでは公式なライブラリとしてVuexが存在します。
Blazorでは特にそのようなものは存在しませんが、Blazorの基本機能のみで同じようにコンテナを扱う方法が存在します。
参考1 参考2
Angularではサービスをコンテナとして使用することができます。
詳細については割愛し、動作例のみ記載します。
Vue.js
<template>
<div>
<BooksInput />
<button @click="getBooks">Get Books!</button>
<h3>BookLists ({{date}})</h3>
<ul>
<li v-for="book in books" :key="book">{{book}}</li>
</ul>
</div>
</template>
<script lang=ts>
import {Component,Vue} from "vue-property-decorator";
import BooksInput from "@/components/BooksInput.vue";
import store from "@/store";
@Component({
components:{
BooksInput
}
})
export default class StateContainer extends Vue{
books:string[]=[];
date:Date=null;
getBooks(){
this.books=store.state.books;
this.date=store.state.date;
}
}
</script>
<template>
<div>
<div><textarea v-model="bookList" id="bookList"></textarea></div>
<button @click="setBooks">Set Books!</button>
</div>
</template>
<style scoped>
#bookList{
height: 300px;
width: 300px;
}
</style>
<script lang=ts>
import {Component,Vue,Prop} from "vue-property-decorator";
import store from "@/store";
@Component
export default class BooksInput extends Vue{
bookList="";
public setBooks(){
store.commit("setBooks",this.bookList.split(/\r|\n|\r\n/).filter(s=>s!=""));
store.commit("setDate",new Date());
alert("setBooks!");
}
}
</script>
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state:{
books: [] as string[],
date: null as Date
},
mutations:{
setBooks(state,books:string[]){
state.books=books;
},
setDate(state,date:Date){
state.date=date;
}
}
});
Blazor
Program.csにサービスとしてコンテナを登録する必要があります。
@page "/StateContainer"
<div>
<BooksInput />
<button @onclick="getBooks">Get Books!</button>
<h3>BookLists (@date)</h3>
<ul>
@foreach(var book in books){<li @key="book">@book</li>}
</ul>
</div>
@inject AppState state;
@implements IDisposable;
@code{
protected override void OnInitialized(){state.OnChange+=StateHasChanged;}
public void Dispose(){state.OnChange-=StateHasChanged;}
string[] books={};
DateTime? date=null;
void getBooks(){
books=state.books;
date=state.date;
}
}
<div >
<div><textarea @bind="bookList" id="bookList"></textarea></div>
<button @onclick="setBooks">Set Books!</button>
</div>
<style>
#bookList{
height: 300px;
width: 300px;
}
</style>
@inject IJSRuntime js;
@inject AppState state;
@code{
string bookList="";
public void setBooks(){
state.setBooks(Array.FindAll(bookList.Replace("\r\n","\n").Split(new[]{'\n','\r'}),s=>s!=""));
state.setDate(DateTime.Now);
js.InvokeVoidAsync("alert","setBooks!");
}
}
using System;
public class AppState{
public string[] books{get;private set;}=new string[]{};
public DateTime? date{get;private set;}=null;
public void setBooks(string[] books){
this.books=books;
NotifyStateChanged();
}
public void setDate(DateTime date){
this.date=date;
NotifyStateChanged();
}
public event Action OnChange;
private void NotifyStateChanged()=>OnChange?.Invoke();
}
Angular
const template=`
<div>
<BooksInput></BooksInput>
<button (click)="getBooks()">Get Books!</button>
<h3>BookLists ({{date}})</h3>
<ul>
<li *ngFor="let book of books">{{book}}</li>
</ul>
</div>
`;
import {Component} from "@angular/core";
import {StoreService} from "../../store.service";
@Component({
selector: "StateContainer",
template: template
})
export class StateContainerComponent{
constructor(
private store:StoreService
){};
books:string[]=[];
date:Date=null;
getBooks(){
this.books=this.store.books;
this.date=this.store.date;
}
}
const template=`
<div>
<div><textarea [(ngModel)]="bookList" id="bookList"></textarea></div>
<button (click)="setBooks()">Set Books!</button>
</div>
<style>
#bookList{
height: 300px;
width: 300px;
}
</style>
`;
import {Component} from "@angular/core";
import {StoreService} from "../../store.service";
@Component({
selector: "BooksInput",
template: template
})
export class BooksInputComponent{
constructor(
private store:StoreService
){}
bookList=`たのしいさんすう
たのしいこくご
たのしいどうとく
かぐやひめ
シンデレラ
うらしまたろう
かちかちやま`;
public setBooks(){
this.store.books=this.bookList.split(/\r|\n|\r\n/).filter(s=>s!="");
this.store.date=new Date();
alert("setBooks!");
}
}
import {Injectable} from "@angular/core";
@Injectable({
providedIn: "root"
})
export class StoreService{
books:string[]=[];
date:Date=null;
}
18. JSONの読み込み
Vue.js/Blazor/AngularではJSONファイルを読み込んで表示することができます。
(クライアントサイドなので書き込みはできません)
ここでは次のJSONファイルを読み込みします。
[
"大剣",
"太刀",
"片手剣",
"双剣",
"ハンマー",
"狩猟笛",
"ランス",
"ガンランス",
"スラッシュアックス",
"チャージアックス",
"操虫棍",
"ライトボウガン",
"ヘビィボウガン",
"弓"
]
Vue.js
require関数を使用し、JSONファイルを/src/assets/以下に配置します。
<template>
<div>
<h3>Read JSON</h3>
<ul>
<li v-for="value in list" :key="value">{{value}}</li>
</ul>
</div>
</template>
<script lang=ts>
import {Component,Vue} from "vue-property-decorator";
@Component
export default class ReadJSON extends Vue{
list:string[]=[];
mounted(){
this.list=require("@/assets/weapons.json");
}
}
</script>
Blazor
HttpClient.GetJsonAsyncを使用し、JSONファイルを/wwwroot/以下に配置します。
@page "/ReadJSON"
<div>
<h3>Read JSON</h3>
<ul>
@foreach(var value in list){<li @key="value">@value</li>}
</ul>
</div>
@inject HttpClient http;
@code{
string[] list={};
protected override async Task OnAfterRenderAsync(bool firstRender){
if(!firstRender) return;
list=await http.GetJsonAsync<string[]>("Assets/weapons.json?0");
StateHasChanged();
}
}
Angular
HttpClientモジュールのgetメソッドを使用します。
使用するにはadd.module.tsにサービスを追加する必要があります。
JSONファイルの配置パスは/src/assets/以下とする必要があります。
const template=`
<div>
<h3>Read JSON</h3>
<ul>
<li *ngFor="let value of list">{{value}}</li>
</ul>
</div>`;
import {Component,OnInit} from "@angular/core";
import {HttpClient} from "@angular/common/http";
@Component({
selector: "ReadJSON",
template: template
})
export class ReadJSONComponent implements OnInit{
constructor(
private http:HttpClient
){}
list:string[]=[];
async ngOnInit(){
this.list=await new Promise(res=>this.http.get("./assets/weapons.json?0").subscribe(res));
}
}
19. テキストファイルの読み込み
Vue.js
Vue.jsのみではプレーンなテキストファイルを読み込むことはできません。
読み込む方法としてVue.jsの公式でaxiosを使うことを提案しています。
プロジェクトにはyarnやnpmで追加が可能です。
また、この方法でもJSONを読み込めますが、少々ファイルの扱いが異なるので注意が必要です。
メソッド | パス | 備考 |
---|---|---|
axios.get | /public/ | 個別の静的ファイルとして扱う |
require | /src/assets | ビルド時jsファイルとともに結合される |
<template>
<div>
<h3>Read Text</h3>
<pre>{{text}}</pre>
</div>
</template>
<script lang=ts>
import {Component,Vue} from "vue-property-decorator";
import axios from "axios";
@Component
export default class ReadText extends Vue{
text="";
async mounted(){
this.text=await axios.get("./kimigayo.txt?0").then(v=>v.data);
}
}
</script>
Blazor
JSONと同様にHttpClient.GetStringAsyncを用いて読み込みます。
@page "/ReadText"
<div>
<h3>Read Text</h3>
<pre>@text</pre>
</div>
@inject HttpClient http;
@code{
string text="";
protected override async Task OnAfterRenderAsync(bool firstRender){
if(!firstRender) return;
text=await http.GetStringAsync("./kimigayo.txt?0");
StateHasChanged();
}
}
Angular
JSONと同様にHttpClient.getを使用できます。
テキストファイルを読み込むには引数に{responseType:"text"}を加える必要があります。
const template=`
<div>
<h3>Read Text</h3>
<pre>{{text}}</pre>
</div>
`;
import {Component,OnInit} from "@angular/core";
import {HttpClient} from "@angular/common/http";
@Component({
selector: "ReadText",
template: template
})
export class ReadTextComponent implements OnInit {
constructor(
private http:HttpClient
){}
text="";
async ngOnInit(){
this.text=await new Promise(res=>this.http.get("./assets/kimigayo.txt?0",{responseType:"text"}).subscribe(res));
}
}
20. HTMLの書き出し
Vue.js/Blazor/Angularではhtmlにおけるelement.innerHTMLと同様にHTMLを書き出すことができます。
Vue.js
v-html属性に値をバインドさせます。
script要素は使用できません。
<template>
<div>
<h3>Inner HTML</h3>
<textarea v-model="txt" style="height:300px;width:300px;"></textarea>
<div v-html="txt"></div>
</div>
</template>
<script lang=ts>
import {Component,Vue} from "vue-property-decorator";
@Component
export default class InnerHTML extends Vue{
txt=
`<h1>TEST TITLE</h1>
<span style=color:#009900>
<u>Say</u> <i>Hello!</i>
</span>`;
}
</script>
Blazor
文字列をMarkupString型にキャストして任意の場所に書き出すことでHTMLを出力できます。
script要素は使用できません。
@page "/InnerHTML"
<div>
<h3>Inner HTML</h3>
<textarea @bind-value="txt" @bind-value:event="oninput" style="height:300px;width:300px;"></textarea>
<div>@((MarkupString)txt)</div>
</div>
@code{
string txt=
@"<h1>TEST TITLE</h1>
<span style=color:#990099>
<u>Say</u> <i>Hello!</i>
</span>";
}
Angular
[innerHTML]属性に値をバインドさせます。
script要素に加えてstyle要素も使用できません。
const template=`
<div>
<h3>Inner HTML</h3>
<textarea [(ngModel)]="txt" style="height:300px;width:300px;"></textarea>
<div [innerHTML]="txt"></div>
</div>
`;
import {Component} from "@angular/core";
@Component({
selector: "InnerHTML",
template: template
})
export class InnerHTMLComponent{
txt=
`<h1>TEST TITLE</h1>
<font color="#990000">
<u>Say</u> <i>Hello!</i>
</font>`;
}
R. ルーター
最後に今回使ったルーターの例を示します。
Vue.js
/src/router.index.tsをページが増えるたびに追加する必要があります。
import Vue from "vue";
import VueRouter from "vue-router";
import Index from "@/views/Index.vue";
import PageA from "@/views/PageA.vue";
import PageB from "@/views/PageB.vue";
Vue.use(VueRouter);
const routes=[
{path: "/", name: "Index", component: Index},
{path: "/PageA", name: "PageA", component: PageA},
{path: "/PageB", name: "PageB", component: PageB}
];
const router=new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes
});
export default router;
<template>
<main style=display:flex>
<NavMenu />
<div class=v-hr></div>
<router-view/>
</main>
</template>
<style scoped>
.v-hr{
margin: 0 10px;
border-right: 5px solid #CCC;
height: 100vh;
}
</style>
<script lang:ts>
import {Component,Vue} from "vue-property-decorator";
import NavMenu from "@/components/NavMenu.vue";
@Component({
components:{
NavMenu
}
})
export default class App extends Vue{}
</script>
<template>
<nav>
<ol type="1" start="0">
<li><router-link to="/">index</router-link></li>
<li><router-link to="/PageA">PageA</router-link></li>
<li><router-link to="/PageB">PageB</router-link></li>
</ol>
</nav>
</template>
<style scoped>
.router-link-exact-active{
color: #FF0000;
font-weight: bold;
}
</style>
Blazor
@inherits LayoutComponentBase
<main style=display:flex>
<NavMenu />
<div class=v-hr></div>
@Body
</main>
<style>
.v-hr{
margin: 0 10px;
border-right: 5px solid #CCC;
height: 100vh;
}
</style>
<div>
<ol type="1" start="0">
<li><NavLink Match="NavLinkMatch.All" href="./">index</NavLink></li>
<li><NavLink Match="NavLinkMatch.All" href="./PageA">PageA</NavLink></li>
<li><NavLink Match="NavLinkMatch.All" href="./PageB">PageB</NavLink></li>
</ol>
</div>
<style>
nav .active{
color: #FF0000;
font-weight: bold;
}
</style>
Angular
/src/app/app-routing.module.tsをページが増えるたびに追加する必要があります。
import {NgModule} from "@angular/core";
import {Routes, RouterModule} from "@angular/router";
import {IndexComponent} from "./pages/Index/Index.component";
import {PageAComponent} from "./pages/PageA/PageA.component";
import {PageBComponent} from "./pages/PageB/PageB.component";
const routes: Routes=[
{path: "", component: IndexComponent},
{path: "PageA", component: PageAComponent},
{path: "PageB", component: PageBComponent},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
const template=`
<main style=display:flex>
<NavMenu></NavMenu>
<div class=v-hr></div>
<router-outlet></router-outlet>
</main>
<style>
.v-hr{
margin: 0 10px;
border-right: 5px solid #CCC;
height: 98vh;
}
</style>
`;
import {Component} from "@angular/core";
@Component({
selector: "app-root",
template: template
})
export class AppComponent{}
const template=`
<nav>
<ol type="1" start="0">
<li><a routerLinkActive="active" [routerLinkActiveOptions]="{exact:true}" routerLink="/">index</a></li>
<li><a routerLinkActive="active" [routerLinkActiveOptions]="{exact:true}" routerLink="/PageA">PageA</a></li>
<li><a routerLinkActive="active" [routerLinkActiveOptions]="{exact:true}" routerLink="/PageB">PageB</a></li>
</ol>
</nav>
<style>
.active{
color: #FF0000;
font-weight: bold;
}
</style>
`;
import {Component} from "@angular/core";
@Component({
selector: "NavMenu",
templateUrl: template
})
export class NavMenuComponent{}
まとめ
以上、Vue.jsとBlazor(+Angular)の文法の比較についてまとめてみました。
styleの仕様など、どうにもならない部分はありますが、大部分はほぼ同じように書けるのではないかと思います。
まだ、速度の面で苦しいなぁと感じていますが今後のBlazorに期待したいと思います。