はいさい!ちゅらデータぬオースティンやいびーん!
概要
Angularでモジュールのインポートにエイリアスを付けるTypeScript機能を使う方法を紹介します。
背景
Angularの正式ドキュメントで「ヒーローのアプリケーションを作る」のガイドをやっている時に、「AngularでTypeScriptのエイリアスをどうやってつけるのだろう」と気になったので試験的に設定してみたら案外簡単だったので、共有したくなりました。
インポートエイリアスとは
ESモジュールをインポートする時に、通常、相対的パスでインポートします。
しかし、サブダイレクトリが増えてくると、import { helperA } from "../../../../../../helpers.js"
のように、とてつもなく長くなってしまいかねないのです。
また、helpers.js
を移動させたら、大変なことに。
こういう問題を解決するために、インポートエイリアスという機能がTypeScriptにあります。
上記のhelper.js
のエイリアスを以下のようにtsconfig.json
で設定します。
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@helpers": ["helper.js"]
}
}
}
すると先ほどのインポート構文が以下のように短くなります。
import { helperA } from "@helpers";
これがインポートエイリアスなのです。
Angularでインポートエリアスを使う
Angularでインポートエイリアスを使うのは簡単です。上記の例と同じようにtsconfig.json
にpaths
の設定を追加すればできます。
Webpackと違って、Angularはtsconfig.json
を見てくれますので特別な設定は不要です。
ガイドのヒーローアプリケーションを作った前提で進めます!
types
のフォルダーにHero
の型を入れます。
上記のガイドではsrc/app/hero.ts
にHero
の型定義をエクスポートしていたのだと思いますが、今回は、src/types
というフォルダーを作り、そこにhero.ts
を移動させます。
export interface Hero {
id: number;
name: string;
}
tsconfigを変更する
次、上記で作ったフォルダーのエイリアスをtsconfig.json
で指定します。
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
// ここです
"paths": {
"@custom-types/*": ["src/types/*"]
},
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "es2020",
"module": "es2020",
"lib": ["es2020", "dom"]
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}
このエイリアス定義だと、types
の中に入るすべてのファイルが使えるのでおすすめです。
なお、@types/*
じゃなくて@custom-types/*
と指定していることに気づかれた読者もいるかと思いますが、これには理由があります。
- 明示的にこのプロジェクトで追加した型なのだというニュアンスを伝えたいから。
-
TypeScriptの
node_modules
の@types
とコンフリクトする から
@types/
で指定すると以下のようなエラーがTSインタプリターから吐かれますのでご注意を。
Cannot import type declaration files. Consider importing 'hero' instead of '@types/hero'
エイリアスでインポートする
上記の変更を保存すれば、最後に、Hero
がインポートされているファイルをすべて以下のようにエイリアスを使うように修正する必要があります。
import { Component, OnInit } from '@angular/core';
// こうして普通のエイリアス構文でインポートすれば後はAngularがやってくれます!
import { Hero } from '@custom-types/hero';
import { HeroService } from '../hero.service';
import { MessageService } from '../message.service';
@Component({
selector: 'app-heroes',
templateUrl: './heroes.component.html',
styleUrls: ['./heroes.component.css'],
})
export class HeroesComponent implements OnInit {
public heroes: Hero[] = [];
public selectedHero?: Hero;
protected heroesSubscription!: ReturnType<
ReturnType<InstanceType<typeof HeroService>['getHeroes']>['subscribe']
>;
constructor(
private heroService: HeroService,
private messageService: MessageService
) {}
ngOnInit(): void {
this.getHeroes();
}
protected getHeroes() {
this.heroesSubscription = this.heroService
.getHeroes()
.subscribe((heroes) => {
this.heroes = heroes;
});
}
public handleSelection(hero: Hero): void {
if (hero.id === this.selectedHero?.id) {
this.messageService.add('HeroesComponent: Hero unselected.');
return (this.selectedHero = undefined);
}
this.messageService.add(`HeroesComponent: Selected ${hero.name}.`);
this.selectedHero = hero;
}
}
全てを変更し、保存してみると、エラーが消えてちゃんとコンパイルされるはずです!
まとめ
ここまで、AngularでTypeScriptのインポートエイリアスを使う方法を紹介しましたがいかがでしょうか?
tsconfig.json
を変更するだけであとは何もしなくていいというところがとても好きです。
Webpackでは、最初にエイリアスを設定する時にかなり苦労した記憶があります。
ViteとAstroも同じような仕組みで、tsconfig.json
で一度設定すれば、他のややこしい設定はないのです。
おまけ:Angularについての感想
Angularはかなり過小評価されているライブラリだと思いました。
なぜこれまでにアンチされているのか理解ができません。
Vueのように学習ハードルが低いけれど、Vueよりもツールが豊富で、全体的にオブジェクト指向のアプリケーション設計ができるようになっていてコードを整理しやすいと思います。
また、フレームワークでできる書き方に制限をかけていて、よく言えばジュニアのエンジニアでも変な書き方ができないようになっています。
悪く言えば書き方に融通が効かない、硬い感じがするのでしょうか?
クラス構文を採用しているのと、明確なライフサイクルがあるので、その中でできないことはないと筆者は思いますが、なんでヘイトされているのだろうか。
筆者は、全体的にモダンなWeb開発はDOMとブラウザ機能からかけ離れているのが良くないと思っており、Vue、Angular、LitといったライブラリのようにJavaScriptのバニラ機能を活かしているフレームワークが魅力的に感じます。