AstroでAngularを使う

この記事は [Angular Advent Calendar 2022](https://qiita.com/advent-calendar/2022/angular) の16日目の記事です。


# Astroとは？

Astroは、コンテンツにフォーカスした高速なWebサイトを構築するためのオールインワンWebフレームワークです。SSG (Static Site Generator)の1つです。

[公式ドキュメント](https://docs.astro.build/ja/getting-started/)がわかりやすいので、ぜひご一読ください。日本語版もあります。

Astroは、[コアコンセプト](https://docs.astro.build/ja/concepts/why-astro/)にもあるように、**自分好みのUIフレームワークを持ち込むことができる(BYOD: Bring Your Own UI Framework)** 設計になっています。

本記事は、AstroにAngularを持ち込む方法を紹介したいと思います。


# AstroでAngularを使う方法

残念ながら[Astroが公式でサポートしているUIフレームワーク](https://docs.astro.build/ja/guides/integrations-guide/)にAngularは含まれていません :cry:

[Analog](https://analogjs.org/)というAngularのメタフレームワークを導入することで、AstroでもAngularを使うことができます。ありがとうBrandon!

[Analog](https://analogjs.org/)ついては、[@komura_c](https://qiita.com/komura_c)さんが[AngularのメタフレームワークAnalogを試す](https://zenn.dev/komura_c/articles/455b4a79c6d534)で紹介しているので、気になった方はご参照ください。

# 実際に試してみる

## Astroのセットアップ

まずは、Astroのプロジェクトを生成します。

```zsh
$ npm create astro@latest
```

`✔ How would you like to setup your new project?`は、`a few best practices(recommended)`を選択します。

プロジェクトルートへ移動して、起動してみます。

```zsh
npm run dev
```

`http://localhost:3000/`にアクセスして、`Welcome to Astro.`のページが表示されたらOKです。

## `@analogjs/astro-angular`をインストールする

次に、[Analog](https://analogjs.org/)のAstro用プラグインをインストールして、Angularが使えるようにします。

```zsh
npx astro add @analogjs/astro-angular 
```

コマンドを実行すると、astroの設定ファイルにanalogの設定が追加されます。

```diff_javascript:astro.config.mjs
import { defineConfig } from 'astro/config';

// https://astro.build/config
- export default defineConfig({});
+ import analogjsangular from "@analogjs/astro-angular";
+ 
+ // https://astro.build/config
+ export default defineConfig({
+   integrations: [analogjsangular()]
+ });
```

Angularは2022年12月12日現在、`v15.0.3`がインストールされます。

```diff_json:package.json
"dependencies": {
+     "@analogjs/astro-angular": "^0.1.0-beta.5",
+     "@angular-devkit/build-angular": "^15.0.3",
+     "@angular/animations": "^15.0.3",
+     "@angular/common": "^15.0.3",
+     "@angular/compiler": "^15.0.3",
+     "@angular/compiler-cli": "^15.0.3",
+     "@angular/core": "^15.0.3",
+     "@angular/language-service": "^15.0.3",
+     "@angular/platform-browser": "^15.0.3",
+     "@angular/platform-browser-dynamic": "^15.0.3",
+     "@angular/platform-server": "^15.0.3",
+     "rxjs": "^7.6.0",
+     "tslib": "^2.4.1",
+     "zone.js": "^0.11.8"
  }
```

:::note warn
`@analogjs/astro-angular`はまだβ版で`@Output`が使えない、などの制限事項があります。
:::

## `tsconfig.app.json`を追加する

コンパイルするために、プロジェクトルートに`tsconfig.app.json`を追加する必要があります。

```json:tsconfig.app.json
{
  "extends": "./tsconfig.json",
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "sourceMap": true,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "importHelpers": true,
    "noEmit": false,
    "target": "es2020",
    "module": "es2020",
    "lib": ["es2020", "dom"]
  },
  "angularCompilerOptions": {
    "enableI18nLegacyMessageIdFormat": false,
    "strictInjectionParameters": true,
    "strictInputAccessModifiers": true,
    "strictTemplates": true
  },
  "files": [],
  "include": ["src/**/*.ts"]
}
```

## Angularコンポーネントを追加する

`components`フォルダにファイルを追加して、コンポーネントを定義します。

今回は、`counter.component.ts`を追加します。
ボタンを押すたびに、表示されている数字が増減します。

```javascript:counter.component.ts
import { Component, Input } from "@angular/core";

@Component({
  selector: "app-counter",
  standalone: true,
  template: `
    <h2>{{ title }}</h2>
    <div>
      <div>{{ count }}</div>
      <button (click)="onIncrement()">increment</button>
      <button (click)="onDecrement()">decrement</button>
    </div>
  `,
})
export class CounterComponent {
  @Input() title: string;

  count = 0;

  onIncrement() {
    this.count++;
  }

  onDecrement() {
    this.count--;
  }
}
```
:::note warn
Angular v14から追加された機能である、`Standalone Components`を使用する必要があります。
:::



## Angularコンポーネントを使用する

まず、追加したAngularコンポーネントを表示するためのページを追加します。

Astroはファイルベースルーティングなので、`pages`フォルダにファイルを追加するだけで、ルーティングが自動的に設定されます。

今回は、`angular.astro`を追加します。
初期構築時に自動生成された`Layout.astro`と、先ほど追加した`counter.component.ts`をimportして使用します。

```html:angular.astro
---
import Layout from "../layouts/Layout.astro";
import { CounterComponent } from "../components/counter.component";
---

<Layout title="Angular">
  <h1>Angular Sample</h1>
  <CounterComponent title="Counter component" client:load />
</Layout>
```

JavaScriptの動作が必要なAngularコンポーネントには`client:load`ディレクティブを設定します。

Astroはビルド時に全てのJavaScriptを排除します。インタラクティブなUIを実現するためにJavaScriptが必要になる場合は、開発者が明示的に許可する必要があります。

これは**Partial Hydration**と呼ばれる、[Astroのコアコンセプト](https://docs.astro.build/ja/concepts/why-astro/)の1つです。

:::note info
`client:load`が指定されたコンポーネントのJavaScriptは、ページ読み込み時にインポートされます。これを`client:visible`に変えて、表示されるまで遅延ロードさせることもできます。
:::

## 動作確認

`http://localhost:3000/angular`にアクセスして、動作確認します。

![astro-angular.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187787/309f253b-9a7e-5813-fafa-1d7b8e6ebcf9.gif)

:ok_hand:

# ユースケースを考える

長らく、AngularでSSGをやりたい場合は[Scully](https://scully.io/)ぐらいしかありませんでしたが、Astro + Angular(Analog)という選択肢が増えました。それぞれコンセプトが違うので難しいですが、既存のAngularアプリケーションをSSG化したい場合はScully、新規で構築する場合はAstro + Angular(Analog)がいいような気がします。

Astroと他のSSGの比較は公式でも言及されているため、よければそちらもご参照ください。

https://docs.astro.build/ja/comparing-astro-vs-other-tools/


# おわりに

個人的にAstroの設計思想はかなり良いと思っていて、今からSSGを構築するならぜひ検討したいフレームワークの1つです。[Analog](https://analogjs.org/)と合わせて引き続きキャッチアップしていきたいです。

明日は、[@ic_lifewood](https://qiita.com/ic_lifewood)さんです！


# 参考記事
- [Bringing Angular Components to Astro Islands](https://dev.to/brandontroberts/bringing-angular-components-to-astro-islands-52jp)
- [Astro + Angular](https://medium.com/ngconf/astro-angular-abe926dac097)

https://astro.build/

https://analogjs.org/