JavaScript
dom
Angular2

Angular 2を触る人に知っておいて欲しい"Attribute"と"Property"の違い

結構Angular2関連の投稿も増えてきてるけど、意外と誰もこの話書いてないっぽいので1


AngularJS 1.x とAngular 2におけるTemplate

AngularJS 1.x と Angular 2は色々違うものの、template中での内挿(interpolation)の書き方は {{...}} が使える。

それぞれ、以下のようになる。


AngularJS

class AwesomeComponent {

title: string;
contents: string;
}

angular.module('myApp').component('awesomeComponent', {
template: `<span title="{{ctrl.title}}">{{ctrl.contents}}</span>`,
controller: AwesomeComponent,
controllerAs: 'ctrl'
});



Angular2

@Component({

selector: 'awesome-component',
template: `<span title="{{title}}">{{contents}}</span>`
// template: `<span [title]="title">{{contents}}</span>` でもよい
})
export class AwesomeComponent {
title: string;
contents: string;
}

上記2つのコードはComponentの定義方法の違いはあるもののtemplateの中身は基本的に一緒だし、ブラウザで動作させた場合も同じ結果を返す。

だけどすこし待って欲しい。実は上記のtemplateは本質的に全く異なることをしている。<span title="{{...}}"> の部分だ。

AngularJS 1.xはAttribute Binding2、一方Angular 2ではProperty Binding3が採用されている。

すなわち、前者は要素のAttributeに値を内挿するが、後者は要素のPropertyに値を内挿する。

もしこれを読んでいる貴方がAttributeとPropertyの違いを理解しているのなら、ここから先の話は読む必要がない。TEMPLATE SYNTAXを読んで、いくつかの特殊記法を覚えれば充分だ。


AttributeとPropertyの違いについて

ものすごくざっくり説明すると、


  • Attribute: 要素をマークアップするときに付与する属性。<span title="hoge" />title はAttribute.

  • Property: 要素のDOMオブジェクトが保持している値。

JavaScriptのコードで違いを説明すると下記のようになる:


  • Attribute: spanElem.setAttribute('title', 'hoge');

  • Property: spanElem.title = 'hoge';

大概の属性はsetAttributeでセットしても、propertyを直接セットしてもブラウザ上での挙動に違いはない。殆どのケースでPropertyがAttributeの値と一致するようになっているからだ。

実際、例として挙げたtitle属性の仕様を確認すると下記のように記載されている。


The title IDL attribute must reflect the title content attribute.


"IDL attribute" とかややこしい言い方をしているが、要素のPropertyはAttributeの値を反映しておけよ、と言っているわけだ。


AttributeとPropertyが一致しないケース

PropertyとAttributeの対応関係は要素種別/属性毎に規定されているため、たまにPropertyとAttributeが一致しない属性が出てくる。


Boolean Attribute

おそらく一番有名な例がこれだろう。例えば<input type="checkbox" checked="checked" />checkedが該当する。その他、disabledとかreadonly等、幾つか存在する。

Boolean Attributeは、Attributeの値にどんな文字を書こうとPropertyはtrueになる。<input type="checkbox" checked /> の時点で、このHTMLInputElementのchecked Propertyは true だ。

AngularJS 1.xや、そのベースであるjQueryだと(1.6を除き) ($(':checkbox').attr('checked')'checked' という文字列ではなく、booleanの値を返すように吸収されていたので、開発者はこの違いをあまり意識しなかったはずだ4

一方、Angular2はProperty Bindingなので、素直にtrue, falseが扱えるのでやはりあまり問題にはならないだろう。


SVGLength

もう少しマニアックなところで、ハマりそうなのがSVG5

Angular2で下記のように書いても動作しない(AngularJS 1.xでもエラー吐くんだけど、それは別の理由):

<circle cx="{{x}}" cy="{{y}}" r="{{radius}}"></circle>

なぜかと言うと、SVGCircleElement を見れば分かるが、cx Propertyはそもそもreadonlyである。その上、その型はStringでもNumberでもなく、SVGAnimatedLengthという型である。

let circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');

circle.cx = 1000; // cxの値は設定されない.
circle.cx = '1000px'; // これも意味がない

// 正しくは下記:
circle.cx.baseVal.value = 1000;
circle.cx.baseVal.valueAsString = '1000px';


Angular2にはAttribute Bindingはないの?

あります。以下のように書くことでPropertyではなくAttributeとして扱われるようになる。

先ほどのSVGをAttribute Bindingで書き直すと次のように書くことができる。

<circle [attr.cx]="x" [attr.cy]="y" [attr.r]="radius"></circle>

ただし、TEMPLATE SYNTAX#attribute に記載されている通り、バインド対象のPropertyが存在しない場合のみ使うこと。


We must use attribute binding when there is no element property to bind.


ちなみに上記のリンク先には、対応するPropertyが存在しないAttributeの例として<td>タグのcolspan属性の例が挙げられているが、この例は上記のルールと照らし合わせると若干微妙だとおもう。なぜなら、HTMLTableCellElementcolSpan というPropertyがあり、これはcolspan Attributeと一致するように仕様に記載されているのだから。


おわりに

AngularJS 1.xだろうとAngular 2だろうとDOMをJavaScriptで操作する以上、DOMの基本知識としてAttributeとPropertyが別物という認識をもっておいて損はないと思う。その上でProperty/Attributeの違いで何かエラーに出くわしたらW3Cの仕様に目を通そう。





  1. ホントのこというと、attributeとpropertyの違いは、Webフロントエンジニアを名乗る人全員に知っておいて欲しいと思ってる 



  2. Developer Guide Interpolation and data-binding より. 



  3. BASICSの TEMPLATE SYNTAX より. 



  4. jQuery 1.6.0のみ$.attrの挙動が厳密になり大混乱を招いたことがある。 http://api.jquery.com/attr/ 



  5. SVGの話持ち出すと、本来はxmlnsやvalidationの話もしないといけないんだけど、面倒なので割愛