[超初心者が行く]HTTP編 Angular5 Tutorialしてみた8

Angular5 Tutorial HTTP!

Angular – Tutorial: Tour of Heroes を翻訳(意訳多め)しています。

[超初心者が行く]ルーティング編 Angular5 Tutorialしてみた7の続き

前回は、各コンポーネントに役割を分散し、ルーティングによって、

ページ間の遷移ができるようになりました。

さて、今回はHTTPを使ってヒーローの追加や削除などできるようにして、アプリとしてもっと実用的にしましょう!

ここからが翻訳になります。

ビックリマークの部分は個人的にわからない用語の意味を載せています。
本家Angularのサイトだけではわからない部分が多いため、本家以外の説明も載せているため、どこからどこまでが本家の情報なのか線引ができなくなっています。ご了承ください。

では、チュートリアルやっていきましょう!

HTTP

このチュートリアルでは、AngularのHttpClientのヘルプを使用して、次の機能を追加し、データを永続的に保持するようにします。

  • HeroServiceはヒーローデータをHTTPリクエストで取得
  • ユーザーはヒーローを追加、編集、削除し、これらの変更をHTTP経由で保存
  • ユーザーはヒーローを名前で検索できる

このページが終わったら、アプリはこのライブサンプル/ダウンロードの例のように見えるはずです。

まずは、完成しているライブサンプルにアクセスして、完成したサイトでどんなことができるのか試しましょう。その方が、このチュートリアルの理解度が高まるでしょう。

HTTP

Hypertext Transfer Protocol(ハイパーテキスト・トランスファー・プロトコル、略称 HTTP)とは、HTMLなどのコンテンツの送受信に用いられる通信プロトコルである。主としてWorld Wide Webにおいて、WebブラウザとWebサーバとの間での転送に用いられる。ハイパーテキスト転送プロトコルとも呼ばれる。

Hypertext Transfer Protocol – Wikipedia

ハイパーテキスト
ハイパーテキスト (hypertext) とは、複数の文書(テキスト)を相互に関連付け、結び付ける仕組みで ある。「テキストを超える」という意味から”hyper-“(~を超えた) “text”(文書)と名付けられた。
ここに表示されているようなブラウザとして表示する文章とサーバー上で動作するテキスト、これらのテキストのように一つのテキストが複数のテキストとしての役割を担うこと。
通信プロトコル
通信プロトコル(つうしんプロトコル、Communications protocol)、あるいはネットワーク・プロトコルは、ネットワーク上での通信に関する規約を定めたものである。「通信規約」や「通信手順」と訳す場合もある。
プロトコル

プロトコルまたはプロトコール(英語: protocol 英語発音: [ˈproutəˌkɔːl], [ˈproutəˌkɔl]、フランス語: protocole フランス語発音: [prɔtɔkɔl])とは、複数の者が対象となる事項を確実に実行するための手順について定めたもの。

もともとは「人間どうしのやりとり」だけに関する用語であった。戦間期の学術的批判を経て、情報工学分野でマシンやソフトウェアどうしのやりとりに関する取り決め(通信規約)を指すためにも用いられるようになった。

日本語に意訳した語として、「規定」、「議定書」、「儀典」などがある。

プロトコル – Wikipedia

World Wide Web
World Wide Web(ワールド・ワイド・ウェブ、略名:WWW)とは、インターネット上で提供されるハイパーテキストシステム。Web、ウェブ、W3(ダブリュー スリー)とも呼ばれる。俗には「インターネット」という表現がワールド・ワイド・ウェブを指す場合もある。

HTTPサービスを有効にする

HttpClientは、HTTP経由でリモートサーバーと通信するためのAngularのメカニズムです。

アプリケーションのどこでもHttpClientを利用できるようにするには、root AppModuleの@NgModule.imports配列に@angular/common/httpからHttpClientModuleシンボルをインポートします。

その方法を紹介します。

物理的に異なる場所に存在しており、インターネットなどを利用することによって手元のマシン(クライアント)と接続可能な状態になっているサーバーマシンは、リモートサーバーなどと呼ばれる。
データサーバーをシミュレートする

このチュートリアルのサンプルでは、In-memory Web API モジュールを使用して、リモートデータサーバーとの通信を模倣します。

アプリケーションは、In-memory Web API モジュールをインストールすると、In-memory Web APIがこれらの要求を傍受し、in-memoryデータストアに適用し、シミュレートされた応答内容を知ることなく、HttpClientに要求を行い、応答を受信します。
これはチュートリアルの便利な機能です。 HttpClientについてサーバーを設定する方法を学ぶ必要はありません。

これは、サーバーのWeb APIが正しく定義されていないか、まだ実装されていような、アプリケーション開発の初期段階で便利かもしれません。

重要:In-memory Web APIモジュールは、AngularのHTTPとは何の関係もありません。

このチュートリアルを読んでHttpClientについて学んでいるなら、このステップをスキップすることができます。 このチュートリアルでコーディングする場合は、In-memory Web APIを追加してください。

コーディング
コーディングとは、プログラミング言語を用いてプログラムのソースコードを記述する作業のことである。

npmからIn-memory Web APIパッケージをインストールします。

コマンド npm install angular-in-memory-web-api –save

HttpClientModule、InMemoryWebApiModuleとInMemoryDataServiceクラスをインポートします。

import { HttpClientModule } from ‘@angular/common/http’;
import { HttpClientInMemoryWebApiModule } from ‘angular-in-memory-web-api’;
import { InMemoryDataService } from ‘./in-memory-data.service’;

そして、InMemoryDataServiceを使用してHttpClientをインポートした後、InMemoryWebApiModuleを@NgModule.imports配列に追加します。

HttpClientをインポートした後、@NgModule.imports配列にInMemoryWebApiModuleとその中にInMemoryDataServiceを追加します。

HttpClientModule,

// The HttpClientInMemoryWebApiModule module intercepts HTTP requests
// and returns simulated server responses.
// Remove it when a real server is ready to receive requests.
HttpClientInMemoryWebApiModule.forRoot(
InMemoryDataService, { dataEncapsulation: false }
)

src/app/app.module.ts (In-memory Web API imports)

forRoot()設定メソッドは、メモリ内のデータベースを準備するInMemoryDataServiceクラスを使います。

The Tour of Heroesサンプルでは、次のような内容のsrc/app/in-memory-data.service.tsを作成し、使用します。ここでは、CLIを使わず手動で作成します。

src/app/in-memory-data.service.ts

このファイルはmock-heroes.tsの代わりになります。

サーバが準備完了したら、In-memory Web APIをデタッチし、アプリのリクエストをサーバに送ります。

今、HttpClientの話に戻ります。

HeroeサービスとHTTP

hero.servic.tsに必要なHTTPシンボルをインポートします。

import { HttpClient, HttpHeaders } from ‘@angular/common/http’;

httpというプライベートプロパティでHttpClientをコンストラクタに挿入します。

constructor(
private http: HttpClient,
private messageService: MessageService) { }

MessageServiceの注入し続け、頻繁に呼び出すので、それをプライベートログメソッドでラップします。

/** Log a HeroService message with the MessageService */
private log(message: string) {
this.messageService.add(‘HeroService: ‘ + message);
}

heroesUrlをサーバー上のヒーローリソースのアドレスで定義します。

private heroesUrl = ‘api/heroes’; // URL to web api

HttpClientでヒーローデータを入手

現在、HeroService.getHeroes()はRxJSのof()関数を使用して、Observable<Hero[]>を観測してサンプルヒーロー配列を返しています。

getHeroes(): Observable<Hero[]> { return of(HEROES); }

メソッドをHttpClientを使用するように変換します。

/** GET heroes from the server */
getHeroes (): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl)
}

ブラウザを更新します。ヒーローデータが疑似サーバーから正常に読み込まれるはずです。

ofからhttp.getに入れ替えましたが、どちらの関数でもObservable を返すので、アプリケーションは他の変更なしで動作し続けます。

src/app/hero.service.ts

Httpメソッドは1つの値を返します

すべてのHttpClientメソッドはいずれかRxJS Observableを返します。

HTTPは要求/応答プロトコルです。要求の単一応答を返します。一般的にObservableは時間とともに複数の値を返すことができます。 HttpClientから受け取ったObservableは、常に単一の値を出力して完了します。決して再び出力しません。

この特定のHttpClient.get呼び出しは、Observable<Hero[]>を返します。実際には、単一のヒーロー配列しか返しません。

HttpClient.getは応答データを返します

HttpClient.getは、デフォルトで型のないJSONオブジェクトとして本文を返します。オプションの型指定子<Hero[]>を適用すると、型付きの結果オブジェクトが得られます。

JSONデータの形状は、サーバーのデータAPIによって決まります。 Tour of HeroesデータAPIは、ヒーローデータを配列として返します。

他のAPIは、オブジェクト内に必要なデータを埋め込むことがあります。 Observable結果をRxJSのmap演算子で処理して、そのデータを取り出す必要があるかもしれません。

ここでは説明しませんが、サンプルソースコードに含まれるgetHeroNo404()メソッドにmapの例があります。

エラー処理

特に、リモートサーバーからデータを取得しているときに問題が起こります。 HeroService.getHeroes()メソッドでエラーをキャッチし、適切な処理を行う必要があります。

エラーを捕捉するには、http.get()からのobservable結果を​​RxJSのcatchError()演算子を通してパイプ処理します。

rxjs/operatorsからcatchErrorシンボルをインポートし、後で必要となる他の演算子もインポートします。

import { catchError, map, tap } from ‘rxjs/operators’;

observable結果を​​.pipe()メソッドで拡張し、catchError()演算子を与えます。

getHeroes (): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl)
.pipe(
catchError(this.handleError(‘getHeroes’, []))
);
}

catchError()演算子は、失敗したObservableをインターセプトします。それは、エラーで必要なことをすることができるエラーハンドラをエラーに渡します。

次のhandleError()メソッドはエラーを報告し、アプリケーションが動作し続けるように無害な結果を返します。

handleError

次のerrorHandler()は多くのHeroServiceメソッドで共有されるため、異なるニーズに合わせて一般化されています。

直接エラーを処理する代わりに、失敗した操作の名前と安全な戻り値の両方を設定したcatchErrorにエラーハンドラ関数を返します。

このコードがなければ先程の.pipeを組込んだコードはエラーになります。

/**
* Handle Http operation that failed.
* Let the app continue.
* @param operation – name of the operation that failed
* @param result – optional value to return as the observable result
*/
private handleError<T> (operation = ‘operation’, result?: T) {
return (error: any): Observable<T> => {// TODO: send the error to remote logging infrastructure
console.error(error); // log to console instead// TODO: better job of transforming error for user consumption
this.log(`${operation} failed: ${error.message}`);// Let the app keep running by returning an empty result.
return of(result as T);
};
}

エラーをコンソールに報告した後、ハンドラーはユーザーフレンドリなメッセージを作成し、アプリケーションに安全な値を返して、作業を継続できるようにします。

各サービスメソッドは異なる種類のObservable結果を返すので、errorHandler()はタイプパラメータを取り、アプリケーションが期待する型として安全な値を返すことができます。

tapを使ったObservable値

HeroServiceメソッドは、Observable値の流れを参照し、ページの下部にあるメッセージ領域に(log()経由で)メッセージを送信します。
Observableを見て、それらの値で、それらを渡すRxJSのtap演算子で値にあったデータを受け渡します。tapコールバックは値自体には触れません。

操作を記録するタップを持つgetHeroesの最終版は次のようになります。

ヒーローのidを取得する

ほとんどのWeb APIは、api/hero/:id(api/hero/11など)の形式でget by idリクエストをサポートしています。 HeroService.getHero()メソッドを追加してリクエストを作成します。


getHeroes()との大きな違いは3つあります。

  • 要求されたヒーローのIDを持つリクエストURLを構築します。
  • サーバはヒーローの配列ではなく、単一のヒーローで応答する必要があります。
  • したがって、getHeroはヒーロー配列のobservableではなく、Observable<Hero>(「HeroオブジェクトのObservable」)を返します。
ヒーローを更新する

ヒーローの詳細ビューでヒーローの名前を編集すると、ページ上部、見出しのヒーロー名が更新されます。しかし、「go back」ボタンをクリックすると、変更は失われます。

変更を保持したい場合は、それらをサーバーに書き戻す必要があります。

ヒーローディテールテンプレートの最後に、clickイベントバインディングを含み、クリックのたびにsave()という名前の新しいコンポーネントメソッドを呼び出す保存ボタンを追加します。

src/app/hero-detail/hero-detail.component.ts (save)

ヒーローサービスのupdateHero()メソッドを使用してヒーロー名の変更を維持し、前のビューに戻るように移動する次のsave()メソッドを追加します。

src/app/hero-detail/hero-detail.component.ts (save)

HeroService.updateHero()を追加します

updateHero()メソッドの全体的な構造はgetHeroes()と似ていますが、http.put()を使用して、変更されたヒーローをサーバー上に保持します。

src/app/hero.service.ts (update)

HttpClient.put()メソッドは、3つのパラメータがあります。

  • URL
  • 更新するデータ(この場合変更されたヒーロー)
  • options

URLは変更しません。ヒーローズのWeb APIは、ヒーローのIDをキーとしてヒーロー名を更新します。ヒーローズのWeb APIは、HTTP保存リクエストには特別なヘッダが必要です。そのヘッダーをHeroServiceのヘッダーでhttpOption定数を使い定義します。

ブラウザを更新し、ヒーロー名を変更して変更を保存し、「戻る」ボタンをクリックします。ヒーローは変更された名前でリストに表示されます。

新しいヒーローを追加

ヒーローを追加するには、このアプリではヒーローの名前だけが必要です。追加ボタンとペアになった入力要素を使用します。

HeroesComponentテンプレートの見出しの直後に、以下を挿入します。

src/app/heroes/heroes.component.html (add)

クリックイベントに応答して、コンポーネントのクリックハンドラを呼び出し、入力フィールドをクリアして、別の名前の準備が整うようにします。

src/app/heroes/heroes.component.ts (add)

指定された名前が空でない場合、ハンドラは名前からヒーローのようなオブジェクト(IDだけが欠落している)を作成し、サービスのaddHero()メソッドに渡します。

addHeroが正常に保存されると、subscribeコールバックは新しいヒーローを受け取り、それをヒーローリストにプッシュして表示します。

HeroService.addHero()を追加

HeroService.addHerohは次のようになります。

src/app/hero.service.ts (addHero)

HeroService.addHero()はupdateHeroと2つの点で異なります。

  • put()の代わりにHttpClient.post()を呼び出します。
  • サーバが新しいヒーローのIDを生成することを期待しています。Observable の中でそれを呼び出し元に返します。

ブラウザを更新してヒーローを追加してみましょう。

ヒーローを削除する

ヒーローリストの各ヒーローには削除ボタンが必要です。

HeroesComponentテンプレートに、繰り返される<li>要素のヒーロー名の後に次のボタン要素を追加します。

<button class=”delete” title=”delete hero” (click)=”delete(hero)”>x</button>

ヒーローのリストのHTMLは、次のようになります。

src/app/heroes/heroes.component.html (list of heroes)

コンポーネントにdelete()ハンドラを追加します。

src/app/heroes/heroes.component.ts (delete)

コンポーネントは、HeroServiceにヒーローの削除を委任しますが、ヒーローの独自のリストを更新する責任があります。コンポーネントのdelete()メソッドは、HeroServiceがサーバー上で成功することを予期して、そのリストから削除するヒーローをコンポーネントですぐに削除します。 heroService.delete()によって返されるObservableでコンポーネントが行うことは本当に何もありません。ですが、常にsubscribeしなければなりません。

subscribe()を怠った場合、サービスは削除要求をサーバーに送信しません。原則として、Observableは何かをsubscribeするまで何もしません!

ためしにsubscribe()を一時的に削除し、 ‘Dashboard’をクリックしてから ‘Heroes’をクリックして、確認してみてください。ヒーローの未編集の完全なリストを見ることになるでしょう。

HeroService.deleteHero()を追加

このようにHeroServiceにdeleteHero()メソッドを追加します。

src/app/hero.service.ts (delete)

注意してください

  • HttpClient.deleteを呼び出します。
  • URLはヒーローリソースのURLと削除するヒーローのIDです
  • putとpostのようにデータを送信しません
  • まだhttpOptionsを送信します

ヒーローエントリの右端に削除ボタンを配置するには、heroes.component.cssにCSSを追加します。

heroes.component.css

ブラウザを更新して、新しい削除機能を試してください。

名前で検索

この最後の演習では、Observable演算子を連動させて、類似したHTTPリクエストの数を最小限に抑え、経済的にネットワーク帯域幅を消費する方法を学びます。

ヒーローズ検索機能をダッシュボードに追加します。 ユーザーが検索ボックスに名前を入力すると、その名前でフィルタリングされたヒーローのHTTPリクエストが繰り返されます。 あなたの目標は、必要なだけ多くのリクエストを発行することです。

HeroService.searchHeroes

まず、HeroServiceにsearchHeroesメソッドを追加します。

src/app/hero.service.ts

このメソッドは、検索語がない場合は空の配列ですぐに戻ります。それ以外の部分はgetHeroes()とよく似ています。唯一の重要な違いはURLです。このURLには検索語句を含むクエリ文字列が含まれています。

クエリ文字列
クエリ文字列とは、WebブラウザなどがWebサーバに送信するデータをURLの末尾に特定の形式で表記したもの。
Webアプリケーションなどでクライアントからサーバにパラメータを渡すのに使われる表記法で、URLの末尾に「?」マークを付け、続けて「名前=値」の形式で記述する。値が複数あるときは「&」で区切り、例えば http://example.com/foo/var.php?name1=value1&name2=value2 のように記述する。
ダッシュボードに検索を追加する

DashboardComponentテンプレートを開き、ヒーロー検索要素をテンプレートの下部に追加します。

src/app/dashboard/dashboard.component.html

このヒーロー検索要素は、HeroesComponentテンプレートの*ngForリピーターによく似ています。

残念ながら、この要素を追加するとアプリケーションが壊れます。 Angularはに一致するセレクタでコンポーネントを見つけることができないからです。

HeroSearchComponentはまだ存在しません。さあ、作成しましょう。

HeroSearchComponentを作成する

CLIでHeroSearchComponentを作成します。

コマンド ng generate component hero-search

CLIは3つのHeroSearchComponentを生成し、そのコンポーネントがAppModuleの宣言に追加されました。

生成されたHeroSearchComponentテンプレートを、テキストボックスと入力に対応した検索結果のリストに置き換えます。

src/app/hero-search/hero-search.component.html

非同期パイプ(AsyncPipe)

期待どおり、*ngForはヒーローオブジェクトを繰り返します。よく見ると、*ngForがheroes$と呼ばれるリストを反復することがわかります。heroesではありません。

<li *ngFor=”let hero of heroes$ | async” >

$ は、heroes$ が、Observableであり、配列ではないことを示します。

*ngFor は、Observableでは何もすることはできませんが、しかし、パイプの文字 (|) の後には、Angularの非同期パイプを識別する async が続くことで*ngForを使えるようになります。

AsyncPipe は、自動的にObservableにsubscribesするため、コンポーネントクラスでは必要ありません。

HeroSearchComponent クラスを修正する

生成された HeroSearchComponent クラスとメタデータを次のように置き換えます。

src/app/hero-search/hero-search.component.ts

Observableとしてのheroes$の宣言に注目してください。

heroes$: Observable<Hero[]>;
これをngOnInit()で設定します。 これを行う前に、searchTermsの定義に注目してください。
The searchTerms RxJS subject

searchTerms プロパティは、RxJS サブジェクトとして宣言されます。

このサブジェクトは、Observable値のソースとObservable自身の両方です。Observableと同様にSubjectにサブスクライブすることができます。

search()メソッドのようにnext(value)メソッドを呼び出すことによって、Observableに値をプッシュすることもできます。

search()メソッドは、テキストボックスのキーストロークイベントにバインドされ、このイベントを介して呼び出されます。

<input #searchBox id=”search-box” (keyup)=”search(searchBox.value)” />
ユーザーが textbox に入力するたび、バインディングはsearch()を textbox 値 (‘検索用語’) で呼び出します。searchTermsはObservableになり、検索用語のスムーズに安定したを出力します。
RxJS演算子の連鎖

ユーザーのキーストロークが発生するたびにsearchHeroes()に新しい検索語を直接渡すと、サーバーリソースが受取、セルラーネットワークデータプランを介して焼き付けることで、過剰なHTTP要求が生み出します。

代わりに、ngOnInit()メソッドは、searchTermsをobservableなものにパイプして、searchHeroes()への呼び出し回数を減らすRxJS演算子のシーケンスをパイプさせ、最終的に時機を得たヒーロー検索結果(Hero [])をobservableに戻します。

これがコードです。

  • debounceTime(300)は、最新の文字列を渡す前に、新しい文字列イベントのフローが300ミリ秒間待機します。 300ms以上の頻度でリクエストすることはありません。
  • distinctUntilChangedは、フィルター・テキストが変更された場合にのみ要求が送信されるようにします。
  • switchMap()は、debounceおよびdistinctUntilChangedを使用して検索する各検索タームの検索サービスを呼び出します。 これは、以前の検索observableを取り消して破棄し、observableな最新の検索サービスのみを返します。

switchMap演算子を使用すると、すべてのキーイベントがHttpClient.get()メソッドの呼び出しをトリガーできます。 300msのリクエスト休止時間では、その間に複数のHTTPリクエストを持つことができ、送信された順序で返されないことがあります。

switchMap()は、最新のHTTPメソッド呼び出しからのobservableのみを返しながら、元のリクエストの順序を保持します。 以前のコールの結果はキャンセルされ、破棄されます。

以前のsearchHeroes()Observableをキャンセルしても、保留中のHTTPリクエストは実際には中止されません。 望ましくない結果は、アプリケーションコードに達する前に単に破棄されます。

コンポーネントクラスはheroes$ observableをsubscribeしていないことに注意してください。 これがテンプレート内のAsyncPipeの機能です。

下記の最終的なコードレビューに記載されているように、hero-search.component.cssにプライベートCSSスタイルを追加します。ユーザーが検索ボックスに入力すると、keyupイベントバインディングによって、コンポーネントのsearch()メソッドが新しい検索ボックスの値で呼び出されます。

hero-search.component.css

試してみよう!

アプリを再度実行します。ダッシュボードで、検索ボックスにテキストを入力します。既存のヒーロー名に一致する文字を入力すると、このようなものが表示されます。

Hero Search Component

最終コードレビュー

あなたのアプリはこのライブサンプル/ダウンロードの例のように見えるはずです。

このページで解説されているコードファイルです。

HeroService, InMemoryDataService, AppModule
hero.service.ts

in-memory-data.service.ts

app.module.ts

HeroesComponent
heroes/heroes.component.html

heroes/heroes.component.ts

heroes/heroes.component.css

HeroDetailComponent
hero-detail/hero-detail.component.html

hero-detail/hero-detail.component.ts

HeroSearchComponent
hero-search/hero-search.component.html

hero-search/hero-search.component.ts

hero-search/hero-search.component.css

まとめ

Angularのtutorialの旅はこれで終わりました。あなたはたくさんのことを成し遂げました。

  • アプリケーションでHTTPを使用するために必要な依存関係を追加しました
  • HeroServiceをリファクタリングして、Web APIからヒーローをロードしました
  • post()、put()、delete()メソッドをサポートするようにHeroServiceを拡張しました
  • ヒーローの追加、編集、削除ができるようにコンポーネントを更新しました
  • in-memory Web APIを設定しました
  • Observablesの使い方を学びました

これで、「Tour of Heroes」チュートリアルは終わりです。 Angularの開発の詳細については、Architectureのガイドから始める基本セクションをご覧ください。

チュートリアル終了!

やっとチュートリアルが終わりました。

ウェブ開発に関する知識がない状態での翻訳は正直、しんどかったです(泣)

ただ、チュートリアル次の「fundamentals:基本」という項目をちらっと見たところ・・・

「こっちの方がわかりやすくね?!」と思いました。

まあしっかり見てないからわからないけどね。

次回は、おさらいも含めて、チュートリアルの全てを理解していこうと思います!

おまけ コンパイル

Angularで作ったけど・・・

これって自分のサイトで動くの?!

ということで、ビルドして、実際に自分のサイトで動かしてみました。

AngularTourOfHeroes

サイト開いて更新するとエラーになるようにまだ機能としては不完全ですが、動くことを確認できただけでOKでしょう(^_^

では、その方法も紹介します。参考:Angular – The Ahead-of-Time (AOT) Compiler

ビルドコマンドを入力

コマンド ng build –aot

これを実行すると、/angular-tour-of-heroes/dist/ にコンパイルされたファイルたちが格納されます。

これをサーバーに持っていけば動作すると思いきや・・・・・・・動きません(泣)

どうやらindex.htmlファイルの編集が必要なようです。

なぜか、<base href=”/”>になっています。次のように変更します。

<base href=”./”>
編集したファイルをサーバーに移すことでローカルの時と同様に動作するようになります。

長い道のりでしたが、やっと自分のサイトでAngularを使ってページを作成できました。

自分が作りたいサイトを作れるようになるのはいつのことでしょうか(笑)

気長にコツコツ作っていこうと思いまーす!

シェアする

  • このエントリーをはてなブックマークに追加

フォローする