noxi雑記

.NET、Angularまわりの小ネタブログ

AngularのHTMLテンプレート上に {} だけを書いてはいけない

最近 Angular の HTML テンプレートまわりで [xxx]="{}" を書いていてハマったのでメモしておきます。



環境

Angular CLI: 8.3.24
Node: 12.14.1
OS: win32 x64
Angular: 8.2.14
... animations, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.803.24
@angular-devkit/build-angular     0.803.24
@angular-devkit/build-optimizer   0.803.24
@angular-devkit/build-webpack     0.803.24
@angular-devkit/core              8.3.24
@angular-devkit/schematics        8.3.24
@angular/cli                      8.3.24
@ngtools/webpack                  8.3.24
@schematics/angular               8.3.24
@schematics/update                0.803.24
rxjs                              6.4.0
typescript                        3.5.3
webpack                           4.39.2

何が起こるのか

適当に空のオブジェクトを設定しておきたいだけなのに、 全てに同じオブジェクトインスタンス が設定されます。違う空のオブジェクトを設定したい場合は必ずクラス上のプロパティをバインドする様にしましょう。

サンプル

// sample.component.ts

import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core';

@Component({
  selector: 'app-sample',
  templateUrl: './sample.component.html',
  styleUrls: ['./sample.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class SampleComponent implements OnInit {

  @Input() data?: { [key: string]: string };

  constructor() { }

  ngOnInit() {
  }

}

こんな適当なコンポーネントを作成して、とても適当に配置してみます。

<!-- app.component.html -->

<app-sample [data]="{}"></app-sample>
<app-sample [data]="{}"></app-sample>

これを AOT コンパイルすると、データバインド部分はこんな感じになっています。

// main-es2015.js

function(_ck, _v) {
  var currVal_0 =
    _angular_core__WEBPACK_IMPORTED_MODULE_1__["ɵEMPTY_MAP"];
  _ck(_v, 1, 0, currVal_0);
  var currVal_1 =
    _angular_core__WEBPACK_IMPORTED_MODULE_1__["ɵEMPTY_MAP"];
  _ck(_v, 3, 0, currVal_1);
},

見た感じ @angular/coreɵEMPTY_MAP を参照しています。こいつは何者かというと、どう見ても固定の空オブジェクトです。

// vendor-es2015.js

"ɵEMPTY_MAP", function() { return EMPTY_MAP; })

...

const EMPTY_MAP = {};

そんなわけで、意図しない挙動を防ぐためにも、 HTML テンプレート上に空のオブジェクトを書くのはやめましょう。

Angular 9 Ivy 環境

Angular 9 の Ivy 環境でビルドすると、ビルドされた結果はこんな感じです。

// main-es2015.js

const _c0 = function() {
  return {};
};

...

_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵproperty"](
  "data",
  _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵpureFunction0"](
    2,
    _c0
  )
);
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵadvance"](1);
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵproperty"](
  "data",
  _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵpureFunction0"](
    3,
    _c0
  )
);

Angular 9 環境下では渡されるのは Function なので、インスタンス共用は無さそうです。