Angularを勉強した時の個人用メモです。随時追記しています。

Javascript の基本

let foo1 = bar1 || bar2;   // bar1 が false なら bar2 を設定
let foo2 = bar1 && bar2 && bar3;     // bar1とbar2がtrueならbar3を設定

Install

$ npm init
$ npm install @angular/cli
$ npx ng new <app-name>
$ npx ng add @angular/material
  - Theme: Indigo

# Component追加
$ npx ng generate component <component-name>
$ npx ng g c <component-name>

# 開発用Webサーバー起動しブラウザを開く
$ npx ng serve -o

Template

// 変数展開
<p>My name is: {{ myName }}</p>

// テンプレート内変数
// "myForm"変数にHTMLFormElementオブジェクトを設定
<form #myForm (ngSubmit)="onClick(myForm)"></form>

// "myForm"変数に NgForm ディレクティブ(オブジェクト)を設定
<form #myForm="ngForm"></form>

Binding

Property Binding

<img [src]="imageSource" />
// "src"は img オブジェクトのプロパティ。右側は式として評価。

<img src="imageSource" />
// "src"は img HTMLエレメントの属性。右側は文字列。

<img src="{{ imageSource }}" />
// "src"は img HTMLエレメントの属性。右側は変数展開された文字列。

Attribute, Class, Style Binding

// attribute
// 属性バインディングは "[attr.<attribute-name>]" と書く
<button [attr.aria-label]="ariaLabel">{{ ariaLabel }}</button>

// class
<p [class.my-class]="isMyClass"></p>
// ↑ isMyClassがtrueの時 "my-class" CSS クラスが設定される。

<p [class]="<expression>"</p>
// ↑ <expression> は以下のいずれかを取れる
// string: "myclass1 myclass2"
// object: {myclass1: true, myclass2: false}
// array: ['myclass1', 'myclass2']

// style
<p [style.width]="100px"></p>
<p [style.width.px]="100"></p>
<p [style]="<expression>"></p>
// ↑ <expression> は以下のいずれかを取れる
// string: "width:100px;height:100px"
// object: {width: "100px", height: "100px"}
// array: ['width', '100px']

Event Binding

<button (click)="onClick()"></button>

Custom Event

// child.component.ts
@Output() clickEvent = new EventEmitter<Item>();
onClick() {
  this.clickEvent.emit();
}

// parent.component.html
<app-child (clickEvent)="onChildClick"></app-child

Two-way Binding

<input [(ngModel)]="myBook.title">

// これは以下と同じ
<input [value]="myBook.title" (input)="myBook.title=$event.target.value">
<input [ngModel]="myBook.title" (ngModelChange)="myBook.title=$event">

Component

子コンポーネントにデータを渡す

// parent.component.html
<app-child [name]="Parent Name" [title]="Parent Title"></app-child>

// child.component.ts
@Component({
  selector: 'app-child',
  ...
})
export class ChildComponent {
  @input() name: string;
  @input('title') titleName: string;  // tslint:disable-line: no-input-rename
}

Routing

Router定義

// app-routing.module.ts
const routes: Routes = [
  {path: '', component: Page1Component},
  {path: 'page1', component: Page1Component},
  {path: 'page2', component: Page2Component}
];

// app.component.html
<ul>
  <li><a routerLink="/page1">Page 1</a></li>
  <li><a routerLink="/page2">Page 2</a></li>
</ul>
<router-outlet></router-outlet>

Animation

基本

// component.ts
import { trigger, state, style, transition, animate } from 
'@angular/animations';

@Component({
  selector: '...',
  templateUrl: '...',
  styleUrls: ['...'],
  animations: [
    trigger('<trigger-name>', [
      state('<state-name1>', style({
        // css style.
        // property name should be in camelCase.
      }),
      state('<state-name2>', style({
        // css style.
      }),
      // transition direction can be either '<=', '<=>', or '=>'
      transition('<state-name>' <=> '<state-name>', [
        animate('0.2s') // '0.2s' == 200 == '200ms' 
      ])
    ])
  ]
})
export class TestComponent implements OnInit {
// ...
}


// component.html
<div [@<trigger-name>]="<state-name>">
  <!-- 
  ex). <div [@toggleState]="this.isOpen ? 'open' : 'close'" ...
  -->
</div>

画面遷移のアニメーション

現ページ、次ページのコンポーネントは “:leave” と “:enter” 疑似セレクターでクエリできる (= 指定できる)。

わかりにくい関数:

// app-routing.module.ts
const routes: Routes = [
  { path: '...', component: ..., data: {animation: '<state-name>'} }
];

// app.component.html (or where <router-outlet> is placed)
<div [@routeAnimations]="prepareRoute(outlet)">
  <router-outlet #outlet="outlet"></router-outlet>
</div>

// app.component.ts (or where <router-outlet> is placed)
prepareRoute(outlet: RouterOutlet) {
  // return animation state name defined in Routes.
  return outlet && outlet.activatedRouteData && outlet.activatedRouteData.animation;
}

// animation.ts
export const slideInAnimation = trigger('routeAnimations', [
  transition('state1 <=> state2', [
    // initialize css styles
    style({ position: 'relative' }),
    query(':enter, :leave', [
      style({ position: 'absolute', top: 0, left: 0, width: '100%' })
    ]),
    query(':enter', [
      style({ left: '-100%' })
    ]),

    // animation definition
    query(':leave', animateChild()),
    group([
      query(':leave', [
        animate(500, style({ left: '100%' }))
      ]),
      query(':enter', [
        animate(500, style({ left: '0%' }))
      ])
    ]),
    query(':enter', animateChild())
  ])
]);