LoginSignup
5
3

【Angular】Vueユーザー向けAngularチュートリアル

Last updated at Posted at 2023-05-07

フレームワークを学習する際、「Vueで言うところのあの機能は、このフレームワークではどうやって実装するのだろう...」という風な学習の進め方をします。

最近Angularを学習しているのですが、その際に整理した代表的な「Vueで言うところの...」を、記事にしてみました。Vueとの比較をしながら、Angularの基礎も紹介しています。Angularの学習を始められた同志のお力になれると幸いです!(^。^)

こちらの記事ではReactの「Vueで言うところの...」を紹介しています。是非併せてご覧ください!✨

Angularとは

angular-logo-1.png
Angularは、Googleによって開発されたオープンソースのJavaScriptフレームワークです。2012年6月に初版がリリースされ、2022年6月に、最新版となるバージョン14がリリースされました(2023年5月現在)。TypeScripをベースに作成されている為、TypeScriptを利用する為に特別設定が必要ないのも特徴です。React、Vue、Svelteに並ぶ人気者です。
公式ドキュメントはこちら

Angularアプリケーションの起動方法

以下コマンドを実行して、Angularアプリケーションの作成を実行してください。

npm init @angular {アプリケーション名}

実行すると対話形式のCLIが起動するので、ルーティングの導入と、スタイリングに使用する言語の選択を要求されます。それぞれ作成したいアプリケーションに合わせて選択すると、アプリケーションの作成が開始します。

> ? Would you like to add Angular routing? (y/N)
> ? Which stylesheet format would you like to use? (Use arrow keys)
❯ CSS 
  SCSS   [ https://sass-lang.com/documentation/syntax#scss                ] 
  Sass   [ https://sass-lang.com/documentation/syntax#the-indented-syntax ] 
  Less   [ http://lesscss.org

以下のように出力されれば、アプリケーションの作成が正常終了しています。command not found: npmというエラーが出た場合は、実行環境にNode.jsとnpmがインストールされていません。こちらの記事を参考にインストールした後に再度以下コマンドを実行してください。

...
CREATE my-first-app/src/app/app.module.ts (393 bytes)
CREATE my-first-app/src/app/app.component.css (0 bytes)
CREATE my-first-app/src/app/app.component.html (23115 bytes)
CREATE my-first-app/src/app/app.component.spec.ts (1091 bytes)
CREATE my-first-app/src/app/app.component.ts (216 bytes)
✔ Packages installed successfully.
    Successfully initialized git.

インストールが完了したら、作成されたアプリケーションのディレクトリに移動してください。

cd {アプリケーション名}

以下コマンドを実行すると、Angularの開発サーバーがlocalhost:4200起動します(4200が使用されている場合は別ポートで起動します)。

npm start
> my-first-app@0.0.0 start
> ng serve

✔ Browser application bundle generation complete.

Initial Chunk Files   | Names         |  Raw Size
vendor.js             | vendor        |   2.04 MB | 
polyfills.js          | polyfills     | 314.28 kB | 
styles.css, styles.js | styles        | 209.41 kB | 
main.js               | main          |  48.10 kB | 
runtime.js            | runtime       |   6.52 kB | 

                      | Initial Total |   2.60 MB

Build at: 2023-05-03T05:01:09.540Z - Hash: d570b7afea270a79 - Time: 4150ms

** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ **


✔ Compiled successfully.

以下のような画面が表示されれば、Angularアプリケーションの作成が完了しました!👏
Screen Shot 2023-05-03 at 14.07.48.png

また、AngularCLIを使用することでAngularアプリケーション開発がより快適になります。インストールをおすすめします。AngularCLIの細かい説明はこちらの公式ドキュメントをご覧ください。

npm install -g @angular/cli

コンポーネント基礎

AngularではTypeScriptファイル, HTMLファイル, CSSファイルを1セットとしてコンポーネントを構成します。Vueのtemplateタグ,scriptタグ,styleタグがそれぞれ別のファイルになっているイメージです。そう聞くと少し面倒な感じがしますが、AngularCLIを使用することで簡単にコンポーネントを作成することができます。
アプリケーションのディレクトリで、以下コマンドを実行してください。

ng generate component {コンポーネント名}

以下が出力されれば、コンポーネントの自動生成が完了しています。コンポーネント作成時はapp.module.tsでコンポーネントの登録を行う必要がありますが、これも自動で修正をしてくれます。便利です。

CREATE src/app/hello/hello.component.css (0 bytes)
CREATE src/app/hello/hello.component.html (30 bytes)
CREATE src/app/hello/hello.component.spec.ts (656 bytes)
CREATE src/app/hello/hello.component.ts (237 bytes)
UPDATE src/app/app.module.ts (509 bytes)

また、上記のコマンドではspecファイルも生成されます。こちらはテスト実装用のファイルなので必要に応じて生成してください。--skip-testsオプションを付与することでspecファイルを生成しないようにすることができます。

ng generate component {コンポーネント名} --skip-tests

AngularではTypeScriptファイル, HTMLファイル, CSSファイルを1セットとしてコンポーネントを構成しますと書きましたが、TypeScriptファイル内でテンプレートを表現することも可能です。本記事は基本的にファイルを分ける方法で記載していますので、ご了承ください。

import { Component } from '@angular/core';

@Component({
  selector: 'app-hello',
  template: `
    <h1>Hello Component</h1>
  `,
  styleUrls: ['./hello.component.css'],
})
export class HelloComponent {
}

Vueで言うところのdata

TypeScriptファイルのコンポーネントクラスで、メンバー変数として定義することで、dataと同様の動きをします。

TypeScriptファイル
import { Component } from '@angular/core';

@Component({
  selector: 'app-hello',
  templateUrl: './hello.component.html',
  styleUrls: ['./hello.component.css'],
})
export class HelloComponent {
  message = 'Hello World!';
  flag = false;
}

コンポーネントから使用する際は、以下のように使用します。

HTMLファイル
<p *ngIf="flag">{{ message }}</p>

Vueで言うところのmethods

TypeScriptファイル内コンポーネントクラスで、メソッドを定義することで、methodsと同様の動きをします。
メソッド内でメンバー変数(data)にアクセスする際は、Vue同様にthis.{アクセスしたいデータ}という風に書きます。

TypeScriptファイル
import { Component } from '@angular/core';

@Component({
  selector: 'app-hello',
  templateUrl: './hello.component.html',
  styleUrls: ['./hello.component.css'],
})
export class HelloComponent {
  message = 'Hello World!';

  helloWorld(): void {
    alert(this.message);
  }
  saySomething(something: string): void {
    alert(something);
  }
}

HTMLファイルからメソッドを使用する際は、以下のように記述します。

HTMLファイル
<button (click)="helloWorld()">HelloWorld!</button>
<button (click)="saySomething('Something!!')">SaySomething!</button>

Vueで言うところのcomputed

VueのcomputedプロパティをAngularで再現する方法として、getキーワードを付与してゲッターメソッドを定義する方法がよく紹介されています。
ですが、以下の場合、コンソールには「numberToDisplay called.」がnumberToDisplayが複数回出力されます。返却する値が同じものでも、何度も再計算を行なっています。
Vueのcomputedプロパティは、計算結果のキャッシュ機能が特徴となっています。Vueで以下のような実装をした場合は一度だけコンソールに出力され、依存するデータが変化するまで再計算が行われることはありません。(こちらの記事computedプロパティのキャッシュ機能が分かりやすく解説されています!)
その為、computedプロパティを再現する方法としてget`キーワードを使用したゲッターメソッドを定義するのは適切とは言えません。キャッシュする挙動まで再現する方法は調べた限り無さそうだったので、方法が分かる方は是非コメントお願いします。

import { Component } from '@angular/core';

@Component({
  selector: 'app-user',
  template: `
    <p>{{ numberToDisplay }}</p>
    <p>{{ numberToDisplay }}</p>
    <p>{{ numberToDisplay }}</p>
    <p>{{ numberToDisplay }}</p>
  `,
  styleUrls: ['./user.component.css'],
})
export class UserComponent {
  number: number = 0;

  get numberToDisplay(): number {
    console.log('numberToDisplay called.');
    return this.number + 1;
  }
}

Vueで言うところのprops

@Inputデコレーターを使用することで親から子にデータを渡すことができるようになります。以下では、子コンポーネントにプロパティnameを定義して、親から「Test props」という値を渡しています。

TypeScriptファイル(子)
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.css']
})
export class ChildComponent {
  @Input() name: string = '';
}
HTMLファイル(子)
<p>{{ language }}</p> <!-- Test props -->
HTMLファイル(親)
<app-child name="Test props"></app-child>

また、子コンポーネントに渡す値に変数をバインディングする場合は、以下のようにプロパティ名を[]で囲う必要があります。

HTMLファイル(親)
<app-child [language]="propToChild"></app-child>
TypeScriptファイル(親)
import { Component } from '@angular/core';

@Component({
  selector: 'app-hello',
  templateUrl: './hello.component.html',
  styleUrls: ['./hello.component.css'],
})
export class HelloComponent {
  propToChild = 'Hello';
}

Vueで言うところのemit

@Outputデコレーターと、EventEmitterクラスを使用することでカスタムイベント(emit)を定義をすることができます。以下では、closeというカスタムイベントを定義し、子コンポーネントのボタンが押下されたタイミングでイベントを発火しています。this.{イベント名}.emit()で定義したイベントを発火することができます。

HTMLファイル(子)
<button (click)="onClickClose()">Close</button>
TypeScriptファイル(子)
import {
  Component,
  EventEmitter,
  Output
} from '@angular/core';

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.css'],
})
export class ChildComponent {
  @Output() close = new EventEmitter<any>();

  onClickClose(): void {
    this.close.emit();
  }
}
HTMLファイル(親)
<app-child language="propToChild" (close)="onEmitClose()"></app-child>
TypeScriptファイル(親)
import { Component } from '@angular/core';

@Component({
  selector: 'app-hello',
  templateUrl: './hello.component.html',
  styleUrls: ['./hello.component.css'],
})
export class HelloComponent {
  onEmitClose(): void {
    alert('Emitted!!!');
  }
}

イベント発火時に引数として値を渡す場合は以下のように実装します。

HTMLファイル(子)
<button (click)="onClickClose(args)">Close</button>
TypeScriptファイル(子)
import {
  Component,
  EventEmitter,
  Output
} from '@angular/core';

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.css'],
})
export class ChildComponent {
  @Output() close = new EventEmitter<string>();

  onClickClose(): void {
    this.close.emit('Hello');
  }
}
HTMLファイル(親)
<app-child language="propToChild" (close)="onEmitClose($event)"></app-child>
TypeScriptファイル(親)
import { Component } from '@angular/core';

@Component({
  selector: 'app-hello',
  templateUrl: './hello.component.html',
  styleUrls: ['./hello.component.css'],
})
export class HelloComponent {
  onEmitClose(args): void {
    alert(args);
  }
}

Vueで言うところのwatch

Angularではメンバ変数として定義した値(Vueで言うところのdata)の変更を検知する方法は無さそうです(何か方法がありましたら是非教えていただきたいです)。ですが、ngOnChanges()メソッドを使用して@Input()を使用して親から受け取った値(Vueで言うところのprops)の変更を検知することは可能です。
ngOnChanges()メソッドを使用するには、OnChangeインターフェースをimplementsする必要があります。

TypeScriptファイル(子)
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';

@Component({
  selector: 'app-hello',
  templateUrl: './hello.component.html',
  styleUrls: ['./hello.component.css'],
})
export class HelloComponent implements OnChanges {
  @Input() name = '';

  ngOnChanges(changes: SimpleChanges): void {
    console.error('Changed:', changes);
  }
}
HTMLファイル(子)
<p>Name: {{ name }}</p>
TypeScriptファイル(親)
import { Component } from '@angular/core';

@Component({
  selector: 'app-user',
  templateUrl: './user.component.html',
  styleUrls: ['./user.component.css']
})
export class UserComponent {
  parentName: string = '';
}
HTMLファイル(親)
Name: <input type="text" [(ngModel)]="parentName"/>
<app-hello [name]="parentName"></app-hello>

ngOnChanges()メソッドは引数として以下のようなSimpleChangesオブジェクトを受け取ります。このオブジェクトからプロパティ毎の変更前の値(previousValue)と変更後の値(currentValue)を取得することができます。ngOnChanges()メソッドは@Input()プロパティ初期化時にも実行される為、コンポーネント初期化時に必ず一度実行されます。その際はfirstChangetrueになります。

{
  "name": {
    "previousValue": "a",
    "currentValue": "aa",
    "firstChange": false
  }
}

Vueで言うところのcomponents

Vueでは、コンポーネント毎にcomponentsに使用する子コンポーネントを追加するのば一般的ですが、Angularではapp.module.tsで使用するコンポーネントを指定して、全てのコンポーネントから全てのコンポーネントを使用できるようにするのが一般的なようです。
@NgModule()デコレーションのdeclarationsにインポートしたクラスを追加することでコンポーネントの登録が完了します。

src/app/app.component.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HelloComponent } from './hello/hello.component';
import { RouterModule } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { UserComponent } from './user/user.component';
import { ChildComponent } from './child/child.component';
import { ParentComponent } from './parent/parent.component';
import { TestComponent } from './test/test.component';

@NgModule({
  declarations: [
    AppComponent,
    HelloComponent,
    UserComponent,
    ChildComponent,
    ParentComponent,
    TestComponent,
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule,
    RouterModule.forRoot([]),
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

ディレクティブ

Vueで言うところの@click

clickを使用することで要素がクリックされた際のイベントを定義することができます。(click)="クリック時に発火するイベント"というような形で使用します。以下では、ボタンをクリックしたらメソッドhelloを実行しています。

<button (click)="hello()">
  Button
</button>

Vueでは@click="hello"という風に書くと、メソッドの引数にはイベントオブジェクトが渡されますが、Angularでは、以下のようにイベントオブジェクトの$eventを明示的に引数として渡す必要があります。

<button (click)="hello($event)">
  Button
</button>

Vueで言うところのv-model

ngModelを使用することでフォームの入力に変数をバインディングすることができます。[(ngModel)]="バインディングする変数"というような形で使用します。以下では、変数nameをテキストフォームにバインディングしています。

<input type="text" [(ngModel)]="name" />

注意点として、ngModelを使用する際にはapp.module.tsで以下のようにFormsModuleの定義をする必要があります。定義しない場合、Can't bind to 'ngModel' since it isn't a known property of 'input'というエラーが出力されます。

src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { HelloComponent } from './hello/hello.component';
import { FormsModule } from '@angular/forms';

@NgModule({
  declarations: [
    AppComponent,
    HelloComponent,
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Vueで言うところのv-for

ngForを使用することでループを表現します。*ngFor="let 要素 of ループ対象"というような形で使用します。以下では、someArrの要素数回分、ngForが付与された要素以下を表示します。

<div *ngFor="let item of someArr">
  {{ item }}
</div>

Vueで言うところのv-if

ngIfを使用することで要素の表示に条件を付与することができます。*ngIf="条件"というような形で使用します。以下では、isAngulartrueの場合にngIfが付与された要素以下を表示します

<div *ngIf="isAngular">
  <p>Hello Angular!</p>
</div>

Vueで言うところのcreated/mounted

ngOnInit()を使用することで、コンポーネント初期化時に一度だけ処理をフックすることができます。
ngOnInit()を使用するには、コンポーネントクラスでOnInitインターフェースをimplementsする必要があります。implementsすることにより、ngOnInit()メソッドを使用することができるようになります。

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

@Component({
  selector: 'app-hello',
  templateUrl: './hello.component.html',
  styleUrls: ['./hello.component.css'],
})
export class HelloComponent implements OnInit {
  users = [];

  ngOnInit(): void {
    this.getUsers();
  }

  async getUsers(): Promise<void> {
    try {
      const { data } = await axios.get('/api/users');
      console.log(data);
    } catch (e) {
      console.error(e);
    }
  }
}

他、ライフサイクルフックに関しては公式ドキュメントを参照してください。

ルーティング(Vueで言うところのVueRouter)

初期設定

Vueでは、VueRouterをインストールしてルーティングを行ないますが、Angularでは追加パッケージなどインストールすることなくルーティングを行うことができます。ここではルーティング導入の代表例を紹介します。
app.module.ts@NgModule()デコレーターのimportsに、RouterModule.forRoot()を追加してください。

src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { RouterModule } from '@angular/router';

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    RouterModule.forRoot([])
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

次にsrc/app/app.component.htmlrouter-outlet(VueRotuerで言うところのrouter-view)タグを追加します。

src/app/app.component.html
<router-outlet></router-outlet>

あとは、RouterModule.forRoot()の引数に配列形式でルート情報を追加すればルーティングの設定が完了です。ルート情報は以下のようにパス(path)と表示するコンポーネント(component)を持つオブジェクトを指定してください。

src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { RouterModule } from '@angular/router';
import { HelloComponent } from './hello/hello.component';

@NgModule({
  declarations: [
    AppComponent,
    HelloComponent
  ],
  imports: [
    BrowserModule,
    RouterModule.forRoot([
      {
        path: 'hello',
        component: HelloComponent
      }
    ])
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

また、ルーターにパスパラメータを指定する場合には、pathの中で:paramNameのように指定します。

{
  path: 'users/:userId',
  component: UserComponent
}

遷移

Vueで言うところの<router-link/>

AngularにはVueRouterの<router-link>のようなタグは用意されていません。その代わり、aタグにルーター用のディレクティブが追加されます。遷移先を指定するには、routerLinkを使用します。このrouterLinkの値として、定義したパス(ルート定義時にpathに指定した値)を指定することで、対象の画面に遷移することができます。

<a routerLink="/users">Users</a>

パスパラメータを指定する場合には、リンクパラメータ配列を使用します。routerLink[]で囲うことで、値に配列を指定することができるようになります。配列の1つ目の要素に遷移先のpath、2つ目の要素にパスパラメータを指定します。以下の場合、遷移先のURLは/users/1となります。

<a [routerLink]="['/users', 1]">Users</a>

Vueで言うところのthis.$router.push()

TypeScriptで画面遷移する際には、navigate()メソッドを使用します。naviagate()の引数には、routerLinkで指定したものと同様のものを渡します。

import { Component } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'app-hello',
  templateUrl: './hello.component.html',
  styleUrls: ['./hello.component.css'],
})
export class HelloComponent {
  constructor(private _router: Router) {}

  jumpToUserPage(): void {
    this._router.navigate(['/users', 1]);
  }
}
5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3