ラベル Angular の投稿を表示しています。 すべての投稿を表示
ラベル Angular の投稿を表示しています。 すべての投稿を表示

2025/06/20

Angularの*ng~ディレクティブについて

従来

 Angularでは状況に応じてHTMLの内容を変更する際に以下のディレクティブを使用します。

  • *ngIf
  • *ngSwitch, *ngSwitchCase, *ngSwitchDefault
  • *ngFor

 これらのディレクティブは以下のタグと組み合わせて使用することもあります。

  • ng-container
  • ng-content
  • ng-template

 使い方を説明した記事はネット上に既にたくさんあるので詳細は説明しませんが、以下のような感じになります。

<div *nfIf="isProcessing; then processingMess; else doneMess"></div>

<ng-template #processingMess>
    <span>処理中...</span>
</ng-template>

<ng-template #doneMess>
    <span>完了!</span>
</ng-template>
<div [ngSwitch]="processState">
    <span *ngSwitchCase="PREPARING">準備中.</span>
    <span *ngSwitchCase="PROCESSING">処理中...</span>
    <span *ngSwitchCase="DONE">完了!</span>
    <span *ngSwitchDefault> (?o?) </span>
</div>
<table>
    <tr *ngFor="let item of itemList">
        <td>{{item}}</td>
    </tr>
</table>

 ngSwitchやngForの例はまだ何とかわかりますが、ngIfのようにng-templateなどとの組み合わせになると直感的でなくて、わかりにくいと大変不評です。


Angular17以降

 Angular17で、以下の新しい構文が追加されました。
  • @if, @else, @else if
  • @switch, @case, @default
  • @for, @empty

 これらは直感的に書けるように改善されています。

<span>
    @if (isProcessing) {
        処理中...
    } @else {
        完了!
    }
</span>
<span>
    @switch (processState) {
        @case ("PREPARING") {準備中.}
        @case ("PROCESSING") {処理中...}
        @case ("DONE") {完了!}
        @default { (?o?) }
    }
</span>
<table>
    @for (item of itemList; track item) {
        <tr>
            <td>{{item}}</td>
        </tr>
    }
    @empty {
        <tr>
            <td>空っぽです</td>
        </tr>
    }
</table>

 @emptyに対応する*ng~ディレクティブはなさそうです。探してみたのですが見つかりません。


 一度使ってしまうと、もう*ng~ディレクティブには戻れません。


2022/06/04

vsReversiアップデート

2022/05/17に続いて、 GCPのデモとして用意していたvsReversiをアップデートしました。


アップデート内容は以下のとおりです。

  • マテリアルデザイン対応
  • モバイル対応

マテリアルデザイン対応

Angularのマテリアルデザインに対応しました。上記のキャプチャにはマテリアルデザインの個所はありませんが、全体的に見た目がちょっとAndroidアプリぽく変化しています。


モバイル対応

過去のパーションと比較して大きな違いが出るのは、上記キャプチャのゲーム画面です。モバイル対応は、スマートフォンでしか確認できていません。手元にスマートフォンしかないので、実機での確認が出来ているのはスマートフォンだけです。タブレットや機種によっては表示がおかしくなるかもしれません。

2022/05/17

vsReversiアップデート

GCPのデモとして用意していた vsReversiをアップデートしました。


アップデート内容は以下のとおりです。

  • ユーザのログインと新規登録を別に
  • SPA対応
  • デザイン変更

ユーザのログインと新規登録を別に

ユーザ認証にはGCPのIdentity Platform(Firebase Authentication)を使用していますが、これには出来合いのUIとして提供されるもの(Firebase UI)も、単体のAPIも、ログインと新規登録の区別がありません。それらが一緒になったサインインがあるのみです。

しかし日本では、ユーザ登録と登録済みユーザのログインが別になっているのをよく見るので、それらしく分けてみました。やってみて出来なくはないのですが、かえって実装は面倒になってしまった印象です。

またEメールで登録する場合は、OAuth(Google/Facebook/Twitter/etc.)に比べて、追加で実装しなければならないのものが多いので、余計面倒です。具体的には、

  • 入力されたEメールアドレスを確認する
  • 入力されたEメールアドレスが間違っていた場合にユーザ登録を解除
  • ユーザがパスワードを忘れた場合のパスワードリセット
  • ユーザがEメールアドレスの変更を希望した場合の対処 (vsReversiでは割愛)
  • さらにこれらのUIのカスタマイズ (vsReversiでは割愛)

Firebase UIではこれらの一部が対応済みなので、簡単に実装したいならそれを使うのが楽です。ただしUIのデザインは簡素なものなので、カスタマイズしたくなります。


SPA対応

Angularを使って一部のページをSingle Page Applicationに対応させました。ログイン/新規登録後のページがそれです。マテリアルデザインは採用していないので それっぽい見た目にはなっていないのですが、ページ遷移が速くなりました。

今までGAE/Java8メインで作っていたのですが、「GAE/Java8でSPA対応に苦戦」に記載したように、これは複数のURIに1つのページを割り当てることができないので、実装の変更が大掛かりになりました。具体的には...

  • HTMLなどスタティックコンテンツをFirebase Hostingに移行してURI割り当てを解決。
  • ajaxリクエストエンドポイントは過去のGAEの実装を流用。ただしCORS(Cross Origin Resource Sharing)に対応。
  • 管理者用のページはCloud Runで管理者の認証を実装。


GCPの初期からあるサービスだけに、GAEは単体でいろいろ必要なものが用意されていることを実感できました。今回は対応できないものが見つかったので、他にいろいろと物色することになりました。それぞれが別のサービスとして名前を持っているので、利用したサービスの数が増えています。


デザイン変更

SPA対応のついでですが、見た目だけの変更です。


CPUの思考ルーチンなどその他は以前のままで、変りありません。


2022/05/03

AngularのSPAサイトで 表示ページに応じて ページのタイトルを更新する

経緯

Webサイトを作る際、現在表示しているページのタイトルを、ブラウザのタイトルバーに表示するのがアクセシビリティの面から望まれます。 Single Page Application(以下SPAと表記)では、URI的には複数のページが、実体としては1つにということになります。SPAでは表示しているURIに応じてタイトルバーの表示を変更する必要が出てきます。

SPAサイトをAngularで実装していたのですが、単にページへのリンクを押して遷移するわけではなく、スクリプトが特定の条件を満たした場合に遷移するので、Routerを使ってページ遷移を実現することになります。このパターンの実装方法はネットで探せばまあまあ出てくるのですが、実装してみると動かないことが多々ありました。特定のAngularバージョンでしか動作しないか、記載の間違いと思われます。思いのほか難航してしまったので、解決できた実装内容などを残しておきます。


実装方法 : Angular 13まで

こちらの記事「Setting Page Titles Natively With The Angular Router」で紹介されていました。著者はGDE(Google Developers Experts)の方なので確実な実装だと思います。この記事は前半が現在の最新版であるAngular 13までの実装方法です。

StackOverflowの記事「How to change page title with routing in Angular application?」の、Ankurさんの回答も多少の違いはありますが、ほぼ同じです。


まずは各ページのデータを用意します。ここで任意のデータを渡せる dataプロパティを使ってページタイトルを含めます。

app.module.ts
@NgModule({ imports: [ RouterModule.forRoot([ { path: 'page-path', component: PageComponent, data:{ title: 'タイトル' }, }, ]), ... ], ... })


ページ表示時にタイトルを反映させる処理がこちら。ループで最もチェインの先端のRouteを取得していますが、ページが入れ子になっているケースに対応するためです。

app.components.ts
import { Title } from '@angular/platform-browser'; import { Router, ActivatedRoute, NavigationEnd } from '@angular/router'; import { filter, map } from 'rxjs'; @Component({ selector: 'app-root', templateUrl: './app.component.html', providers: [ Title ], }) export class AppComponent { constructor( private router: Router, private pageTitle: Title, ) { } ngOnInit() { this.router.events .pipe( filter(event => event instanceof NavigationEnd), // NavigationEndのタイミングで map(() => { let route: ActivatedRoute = this.router.routerState.root; while (route.firstChild) { route = route.firstChild; // 最も下位のRouteを取得して } if (route.snapshot.data['title']) { return route.snapshot.data['title']; // そのRouteのdataからタイトルを取得 } return null; }) ) .subscribe((title: string) => { if (title) { this.title.setTitle(title); // 取得したタイトルを反映 } }); } }


実装方法 : Angular 14から

上記の記事の後半「Using the built-in TitleStrategy」からは、Angular 14での実装について書かれています(Angular 14はまだプレリリース版のようです)。TitleStrategyクラスを使用することで、より簡単に実装できるようです。

以下のスニペットは記事をもとに書いてみただけです。まだAngular 14の開発環境を用意していないので、動作確認していません。こんな感じになるくらいに認識してください。


各ページのタイトルのために、Routeクラスに専用のプロパティtitleが追加されています。もうdataプロパティは使わなくても構いません。

app.module.ts
@NgModule({ imports: [ RouterModule.forRoot([ { path: 'page-path', component: PageComponent, title: 'タイトル' }, ]), ... ], ... })


ページ表示時にタイトルを反映させる処理は、AppComponentではなくTitleStrategyクラスから派生したサブクラスでupdateTitle()メソッドをオーバーライドして実装します。下記のbuildTitle()メソッドはTitleStrategyクラスのものなのか、サブクラスに自分で実装するのかは、説明がないので不明です。

app.components.ts
@Injectable() export class TemplatePageTitleStrategy extends TitleStrategy { constructor(private readonly title: Title) { super(); } override updateTitle(routerState: RouterStateSnapshot) { const title = this.buildTitle(routerState); if (title) { this.title.setTitle(title); } } }


ソースファイルは増えますが、この実装の方がより直感的で魅力的になったように感じます。


公式痛恨(?)のフライング...

Angularの日本語サイトを彷徨っていたら「Setting the page title」を見つけました。参照したのはAngular 13のstable(v13.3.3)なのですが、この章の記載内容は上記のAngular 14そのものです。フライングで公開してしまったようです。