npm install -g @ionic/cli
ionic start client-app blank --type react
ionic start
後は画面上からいろいろ選択するだけでokです。
フレームワークは react , angular , vue.js から選択できます。
protected myFunction(): boolean {
return true;
}
↓ Observable を返すようにします
import { forkJoin, timer, of, Observable } from 'rxjs';
/**
* @returns Observable
*/
protected myFunction(): Observable<boolean> {
return of(true);
}
this.myFunction.subscribe(
data => ..., // Promise.then
err => ..., // Promise.catch
() => ... // 終了時常に実行
);
of(123456) で 値: 123456 を返す場合
/**
* @returns Observable
*/
protected myFunction(): Observable<any> {
return of(123456).pipe(
map((res: any) => {
console.log("■ next ■")
return res;
}),
catchError(err => {
console.log('■ catchError 1 ■ caught mapping error and rethrowing', err);
return throwError(err);
}),
finalize(() => {
console.log("■ finalize ■")
}),
catchError(err => {
console.log('■ catchError 2 ■caught rethrown error, providing fallback value');
return of([]);
}),
);
}
結果(正常時)
■ next ■
■ finalize ■
ErrorObservable.create('error') でテスト的にエラーを発生させる場合
import { ErrorObservable } from 'rxjs/observable/ErrorObservable';
/**
* @returns Observable
*/
protected myFunction(): Observable<any> {
// エラーを発生させます
return ErrorObservable.create('error').pipe(
map((res: any) => {
console.log("■ next ■")
return res;
}),
catchError(err => {
console.log('■ catchError 1 ■ caught mapping error and rethrowing', err);
return throwError(err);
}),
finalize(() => {
console.log("■ finalize ■")
}),
catchError(err => {
console.log('■ catchError 2 ■caught rethrown error, providing fallback value');
return of([]);
}),
);
}
結果(エラー時)
■ catchError 1 ■ caught mapping error and rethrowing error
■ catchError 2 ■caught rethrown error, providing fallback value
■ finalize ■
let user = Users.find(user => user.id === query);
↓
let user = Users.find((user: any) => user.id === query);
型はメソッドの宣言に記述してあるのでコメント内では省略してもいいでしょう。
例:
/**
* 関数の説明
* @param arg 引数の説明
* @return 番号を返す
*/
public doSomething (arg: string) : number {
//...
}
.ts ファイル
import { Component, ViewEncapsulation } from '@angular/core';
encapsulation: ViewEncapsulation.None を使用します
.ts ファイル
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class HeaderComponent {
}
.scss ファイル
app-header {
// your styles here and any child component styles can go here
}
@Input() value を作ってるならば
@Output() valueChange = new EventEmitter(); と、同名+ChangeのEventEmitterを作るだけでよい。
そうすると、[(value)] で双方向バインディング出来ます。
引用: https://qiita.com/kohashi/items/bcbcc9cbeaee9498e4fe
補足
バインディングしている変数やクラスに「変更があったとき」 @Output を経由して 変更が適用されるので、
バインディングしている変数へ「値を再代入して初期化」した場合には @Output を経由しないので注意が必要です。
// × 変更が適用されない
this.value = new MyClass();
// 〇 初期化するメソッドを自作して、メソッドを実行する
this.value.deleteAll();
自作コンポーネント
@Inputでプロパティを定義する。
@OutputでEventEmitterを定義する。EventEmitterの型は@Inputの型と同じものとする。
@Output側のプロパティ名を@Inputのプロパティ名 + Changeにする。
例)@Inputがvalueだった場合、@OutputはvalueChange
自作コンポーネント内の処理で@Output側のEventEmitter.emit処理を実行することで、値が呼び出し側に伝搬する。
呼び出し側コンポーネント
[(双方向対象のプロパティ)]="呼び出し側プロパティ"のように呼び出す。
引用: https://kakkoyakakko2.hatenablog.com/entry/2018/08/24/003000
import { forkJoin } from 'rxjs';
forkJoin(
this.service.myService01(),
this.service.myService02()
).subscribe(
data => {
console.log( data[0] ); // myService01() の戻り値
console.log( data[1] ); // myService02() の戻り値
}
);
戻り値は要求した順に返ってくるようです。
{{ 123456 | number }}
とすると、このようにカンマが入ります。
123,456
.ts
protected console_log(val: string): void { console.log(val); }
.html
<my-component
(change)="console_log( 'テストです' )"
>
.ts
private console: any = console;
.html
<my-component
(change)="console.log( 'テストです' )"
>
ts
export class AppComponent {
@ViewChild('v1') protected countComponent: CountComponent;
@ViewChild('v2') protected countComponent2: CountComponent;
}
html
<div>
<count #v1 [c]="100"></count>
<button (click)="onClick1()">+</button>
</div>
<div>
<count #v2 [c]="1"></count>
<button (click)="onClick2()">+</button>
</div>
.ts ファイルの 'v1' と .htmlファイルの #v1 で 同じコンポーネントが複数ある場合に識別します。
引用 : https://angular.keicode.com/basics/component-interaction-viewchild.php
.ts
this.myHtml= '<script>alert("XSS TEST");</script>テストHTML<br>テストHTML<br>テストHTML<br>テストHTML';
.html
<p [innerHTML]="myHtml"></p>
これだけで、自動的にXSS対策もされています。
https://angular.io/guide/binding-syntax
日本語はこちらですが、日本語のほうがわかりにくいです。。。
https://angular.jp/guide/binding-syntax
Type | Syntax | Category |
---|---|---|
Interpolation Property Attribute Class Style |
|
One-way from data source to view target |
Event |
|
One-way from view target to data source |
Two-way |
|
Two-way |
Angular でモックを作るなら ... 次の3つが浮かびます。
・1. Angular in-memory-web-api
参考: https://bit.ly/35iW0SN
・2. json-server
・3. HttpClientTestingModule
・2. json-server を作成してみます。
npm i -D json-server
ファイル名は任意のファイル名でokですが、とりあえず db.json ファイルを新規作成します。
vi db.json
db.json を以下の内容で保存します。
{
"users": [
{
"id": 1,
"title": "お名前太郎",
},
{
"id": 2,
"title": "サンプル花子",
},
{
"id": 3,
"title": "検索太郎",
}
]
}
( package.json がない場合はこちらを実行 )
npm init
package.json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
↓
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1" ,
"json-server": "json-server --watch ./db.json --port 9999"
},
npm run json-server
http://localhost:9999/ で json-server へアクセスすることができます。
npx json-server db.json -m middleware.js --watch --host 0.0.0.0 --port 9999
ホストに 0.0.0.0 を指定することで http://localhost:9999/ と http://ローカルIPアドレス で json-server へアクセスすることができます。
http://localhost:9999/contents
http://localhost:9999/contents/1
にアクセスして戻り値を確認します。
postmanを使って次のようにPOSTします。
postすると自動で db.json のファイルが更新されます。
db.json ファイルを更新しないようにするには
db.js
const db = require('./db.json')
module.exports = () => db
package.json
"json-server": "json-server db.js --port 9999"
とすると、確かにjson-serverは更新されますが、メモリ上のみの更新なので db.json は更新されません。(再起動すると元のデータに戻ります。)
また、「ミドルウェアを使ってPOSTメソッドをGETメソッドに変換する」という技も有効ですのでそちらでもOKです。
middleware.js
module.exports = function (req, res, next) {
if (req.method === 'POST') {
req.method = 'GET'
req.query = req.body
}
next()
}
↓これを追加
import { FormControl,Validators } from '@angular/forms';
export class AppComponent {
}
↓ に変更
export class AppComponent {
public control = new FormControl('', [Validators.required]);
}
入力欄:<input type="text" [formControl]="control" required>
<div *ngIf="control.invalid && (control.dirty || control.touched)">
<span *ngIf="control.hasError('required')">必須です。</span>
</div>
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
imports: [
....
ReactiveFormsModule, // 追加
],
class Validators {
static min(min: number): ValidatorFn
static max(max: number): ValidatorFn
static required(control: AbstractControl): ValidationErrors | null
static requiredTrue(control: AbstractControl): ValidationErrors | null
static email(control: AbstractControl): ValidationErrors | null
static minLength(minLength: number): ValidatorFn
static maxLength(maxLength: number): ValidatorFn
static pattern(pattern: string | RegExp): ValidatorFn
static nullValidator(control: AbstractControl): ValidationErrors | null
static compose(validators: ValidatorFn[]): ValidatorFn | null
static composeAsync(validators: AsyncValidatorFn[]): AsyncValidatorFn | null
}
public control = new FormControl('', [Validators.required]);
↓ このように書き換える
public control = new FormControl('', {
validators: [Validators.required] ,
updateOn: 'blur'
});
次の ルーティングを指定します
/heroes
/users/home
/admin/home (lazyloading)
const routes: Routes = [
{ path: 'heroes', component: HeroesComponent } ,
{ path: 'users',
children: [
{ path: 'home', component: HomeComponent },
]
} ,
{ path: 'admin',
children: [
{ path: '', loadChildren: './user-list/user-list.module#UserListPageModule' },
{ path: ':userId', loadChildren: './user-detail/user-detail.module#UserDetailPageModule' },
]
} ,
];
src\app\app-routing.module.ts
imports: [
RouterModule.forRoot(routes)
],
↓ { enableTracing: true } を追加します。
imports: [
RouterModule.forRoot(routes, { enableTracing: true })
],
>declarationスイッチをtrueにすると、全ての.tsファイルから.jsファイルと.d.tsファイルのペアを生成します。 >index.tsをコンパイルすると、index.jsとindex.d.tsを作ってくれる。 >そしてこのできたindex.d.tsをpackage.jsonで明らかにすればよい。
tsconfig.json
{
"compilerOptions": {
"outDir": "lib",
"declaration": true,
}
}
npm install jquery --save
npm install --save jquery @types/jquery
プロジェクトフォルダのnode_modulesフォルダに@typesフォルダが作成され、jQueryの型定義ファイルがインストールされます。
types に jquery を追加
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types" : [
"jquery"
]
},
src\app\app.component.ts
toggleTitle(){
const jqobj :JQuery = $(".title");
jqobj.slideToggle().fadeOut();
}
src\app\app.component.html
<h1 class="title" style="display:none">
{{title}}
</h1>
<button (click)="toggleTitle()"> clickhere</button>
ng serve --open
子コンポーネント : ts
export class ChildEditComponent {
@Output() childCreated = new EventEmitter<MyData>();
onButtonClicked(): void {
this.childCreated.emit(this.mydata);
}
}
childCreated を受け取る
親コンポーネント : html
<my-edit-article (childCreated)="consoleShow($event)"></my-edit-article>
親コンポーネント : ts
export class ParentEditComponent {
consoleShow(mydata: Mydata) {
console.log( mydata );
}
}
一番いいのはモデルを作成してそのモデルを渡す方法ですが、次のように複数渡すこともできます。
子コンポーネント : ts
@Output() childCreated = new EventEmitter<MyData>();
↓
@Output() childCreated = new EventEmitter<{ id: number; mydata:MyData }>();
this.childCreated.emit(this.mydata);
↓
this.childCreated.emit({
id: this.myid,
mydata: this.mydata,
});
親コンポーネント : ts
consoleShow(mydata: Mydata) {
console.log( mydata );
}
↓
consoleShow(childObj: any) {
console.log( childObj.id );
console.log( childObj.mydata );
}
html
次のコンポーネントの HTML でボタンを次のようにします
<button type="button">送信する</button>
↓ ( appOnClickDisable="1" を追加 )
<button
appOnClickDisable="1"
type="button" >
送信する
</button>
カスタムディレクティブを作成します app/directives/app-on-click-disable.directive.ts
import { Directive, HostListener, Input, Renderer2 } from '@angular/core';
@Directive({
selector: '[appOnClickDisable]'
})
export class AppOnClickDisableDirective {
@Input() appOnClickDisable: number;
constructor(
private renderer: Renderer2,
) { }
@HostListener('click', ['$event']) onClick(event) {
console.log( 'AppOnClickDisableDirective clicked' );
if ( this.appOnClickDisable == 1 ){
event.target.disabled = true;
}
}
}
app.module.ts で読み込みます app/app.module.ts
import { AppOnClickDisableDirective } from './directives/app-on-click-disable.directive';
@NgModule({
declarations: [
AppComponent,
AppOnClickDisableDirective, // 追加
],
以上で一度クリックするとボタンが disabled になります。 ( disabled解除は app-on-click-disable.directive.ts ソース内に実装する必要があります。)
main.ts
if (environment.production) {
enableProdMode();
}
↓ このように追加します
if (environment.production) {
enableProdMode();
// production環境下では console.log , console.error を何もさせない
if (window) {
window.console.log = () => {};
window.console.error = () => {};
}
}
引用元 : https://bit.ly/3hTy660
https://angular.io/api/core/Renderer2
html
クリックイベントの引数に $event を渡します
<button
type="button"
(click)="sendButtonClicked($event)">
送信する
</button>
ts
event.target が DOMオブジェクトです。
private sendButtonClicked( event:any ) {
// css class を追加
this.renderer.addClass(event.target, 'now-loading');
}
my.component.ts に 以下の3つを追加
import { Location } from '@angular/common';
constructor(
private location: Location
) {
}
private backBtnClick() {
this.location.back();
}
my.component.html に戻るボタンを設置
<button (click)="backBtnClick()" type="button">戻る</button>
ng g service services/boards
CREATE src/app/services/boards.service.spec.ts (333 bytes)
CREATE src/app/services/boards.service.ts (135 bytes)
ng g コマンドでサービスを生成した場合、そのサービスは root に所属します。appモジュールではありません。
以下のように @Injectable に providedIn というのが追加になっていて、
@Injectable({
providedIn: 'root'
})
どのモジュールから追加されるのか指定できるようになってます。
ここが 'root' の場合は app/app.module.ts への登録不要で Dependency Injection できます。
なお root 所属のサービスは全てのモジュールで同じインスタンスが利用されます(つまりシングルトン)。
import { HttpClientModule } from '@angular/common/http';
imports: [
.................................
HttpClientModule,
],
environments/environment.ts
export const environment = {
production: false ,
apiUrl: 'http://localhost:5000'
};
environments/environment.prod.ts
export const environment = {
production: true ,
apiUrl: 'http://localhost:5000'
};
app/services/boards.service.ts を以下の内容で保存
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { environment } from './../../environments/environment';
@Injectable({
providedIn: 'root'
})
export class BoardsService {
constructor(private httpClient: HttpClient) { }
getIndex(): Observable<any> {
return this.httpClient.get(environment.apiUrl + '/boards/');
}
}
app/boards/boards.component.ts を以下の内容で保存
export class BoardsComponent implements OnInit {
boards_loop = [];
constructor(
private boardsService: BoardsService ,
)
ngOnInit() {
this.boardsService.getIndex().subscribe(res => {
this.boards_loop = res;
});
}
app/boards/boards.component.html に以下を追加
<ul>
<li *ngFor="let board of boards_loop">
<span>{{board.id}}</span> : {{board.name}}
</li>
</ul>
実は簡単な使い分け
declarations : ディレクティブ(含コンポーネント、パイプ)を書きます。 htmlテンプレートに書くもの、ですね。
providers : Serviceなど、DIで解決するものをここに書きます。 Angular 6 以降は各モジュールに
================
@Injectable({
providedIn: 'root'
})
================
と書きます。
imports : 外部のAngularモジュール。Httpモジュールとか、UIモジュールとか。
これだけです!
app/app.component.ts
ngOnInit() {
this.boards_loop = [
{ id: 11, name: 'Mr. Nice' },
{ id: 12, name: 'Narco' },
{ id: 13, name: 'Bombasto' },
{ id: 14, name: 'Celeritas' },
{ id: 15, name: 'Magneta' },
{ id: 16, name: 'RubberMan' },
{ id: 17, name: 'Dynama' },
{ id: 18, name: 'Dr IQ' },
{ id: 19, name: 'Magma' },
{ id: 20, name: 'Tornado' }
];
}
app/app.component.html
<li *ngFor="let board of boards_loop">
<span class="badge">{{board.id}}</span> : {{board.name}}
</li>
出力結果
<ul _ngcontent-bek-c0=""><!--bindings={
"ng-reflect-ng-for-of": "[object Object],[object Object"
}--><li _ngcontent-bek-c0=""><span _ngcontent-bek-c0="">11</span> : Mr. Nice
</li><li _ngcontent-bek-c0=""><span _ngcontent-bek-c0="">12</span> : Narco
</li><li _ngcontent-bek-c0=""><span _ngcontent-bek-c0="">13</span> : Bombasto
</li><li _ngcontent-bek-c0=""><span _ngcontent-bek-c0="">14</span> : Celeritas
</li><li _ngcontent-bek-c0=""><span _ngcontent-bek-c0="">15</span> : Magneta
</li><li _ngcontent-bek-c0=""><span _ngcontent-bek-c0="">16</span> : RubberMan
</li><li _ngcontent-bek-c0=""><span _ngcontent-bek-c0="">17</span> : Dynama
</li><li _ngcontent-bek-c0=""><span _ngcontent-bek-c0="">18</span> : Dr IQ
</li><li _ngcontent-bek-c0=""><span _ngcontent-bek-c0="">19</span> : Magma
</li><li _ngcontent-bek-c0=""><span _ngcontent-bek-c0="">20</span> : Tornado
</li></ul>
app/app.component.html ( ng-container を使うやり方 )
<ng-container *ngFor="let board of boards_loop">
<li><span>{{board.id}}</span> : {{board.name}}</li>
</ng-container>
出力結果 ( ng-container を使うやり方 )
<ul _ngcontent-nve-c1="">
<!--bindings={
"ng-reflect-ng-for-of": "[object Object],[object Object"
}-->
<!---->
<li _ngcontent-nve-c1=""><span _ngcontent-nve-c1="">11</span> : Mr. Nice</li>
<!---->
<li _ngcontent-nve-c1=""><span _ngcontent-nve-c1="">12</span> : Narco</li>
<!---->
<li _ngcontent-nve-c1=""><span _ngcontent-nve-c1="">13</span> : Bombasto</li>
<!---->
<li _ngcontent-nve-c1=""><span _ngcontent-nve-c1="">14</span> : Celeritas</li>
<!---->
<li _ngcontent-nve-c1=""><span _ngcontent-nve-c1="">15</span> : Magneta</li>
<!---->
<li _ngcontent-nve-c1=""><span _ngcontent-nve-c1="">16</span> : RubberMan</li>
<!---->
<li _ngcontent-nve-c1=""><span _ngcontent-nve-c1="">17</span> : Dynama</li>
<!---->
<li _ngcontent-nve-c1=""><span _ngcontent-nve-c1="">18</span> : Dr IQ</li>
<!---->
<li _ngcontent-nve-c1=""><span _ngcontent-nve-c1="">19</span> : Magma</li>
<!---->
<li _ngcontent-nve-c1=""><span _ngcontent-nve-c1="">20</span> : Tornado</li>
</ul>
参考 : Angularの便利タグng-container, ng-content, ng-template - Qiita
https://angular.io/api/router/Event
NavigationStart,
RouteConfigLoadStart,
RouteConfigLoadEnd,
RoutesRecognized,
GuardsCheckStart,
ChildActivationStart,
ActivationStart,
GuardsCheckEnd,
ResolveStart,
ResolveEnd,
ActivationEnd
ChildActivationEnd
NavigationEnd,
NavigationCancel,
NavigationError
Scroll
解説
種別 | 発火タイミング |
---|---|
NavigationStart | ナビゲーションが開始された時。 |
ActivationStart | ナビゲーション先のコンポーネントが決まった時(GuardやResolveの前)。 |
ActivationEnd | ナビゲーション先のインスタンスが作られた後)。 |
NavigationEnd | ナビゲーションが終了した時(正常に終了した場合)。 |
NavigationCancel | ナビゲーションが終了した時(ナビゲーション処理の途中でキャンセルされた場合)。 |
NavigationError | ナビゲーションが終了した時(ナビゲーション先が存在しないなど、エラーが発生した場合)。 |
記述方法 イベント(NavigationStart)で操作を行う例」
constructor( private _router: Router ) {
_router.events.subscribe(event => {
if(event instanceof NavigationStart) {
// ここにページ遷移ごとに実行するメソッド
}
});
}
npm install --save @fullcalendar/angular @fullcalendar/core @fullcalendar/daygrid @fullcalendar/interaction @fullcalendar/timegrid
// add this
@import '~@fullcalendar/core/main.css';
@import '~@fullcalendar/daygrid/main.css';
@import '~@fullcalendar/timegrid/main.css';
// add
import { FullCalendarModule } from '@fullcalendar/angular';
......
imports: [
BrowserModule,
FullCalendarModule // add
],
// calendar
import { FullCalendarComponent } from '@fullcalendar/angular';
import { EventInput } from '@fullcalendar/core';
import dayGridPlugin from '@fullcalendar/daygrid';
import timeGrigPlugin from '@fullcalendar/timegrid';
import interactionPlugin from '@fullcalendar/interaction'; // for dateClick
export class AppComponent {
@ViewChild('calendar') calendarComponent: FullCalendarComponent; // the #calendar in the template
calendarVisible = true;
calendarPlugins = [dayGridPlugin, timeGrigPlugin, interactionPlugin];
calendarWeekends = true;
calendarEvents: EventInput[] = [
{ title: 'Event Now', start: new Date() }
];
toggleVisible() {
this.calendarVisible = !this.calendarVisible;
}
toggleWeekends() {
this.calendarWeekends = !this.calendarWeekends;
}
gotoPast() {
let calendarApi = this.calendarComponent.getApi();
calendarApi.gotoDate('2000-01-01'); // call a method on the Calendar object
}
handleDateClick(arg) {
if (confirm('Would you like to add an event to ' + arg.dateStr + ' ?')) {
this.calendarEvents = this.calendarEvents.concat({ // add new event data. must create new array
title: 'New Event',
start: arg.date,
allDay: arg.allDay
})
}
}
}
ng build
ng build --base-href=/angular/
(最後のスラッシュは忘れずに!)
ng build <プロジェクト名>
<プロジェクト名> には angular.json の
{
"projects": {
"my-app": {
}
}
}
で指定した my-app などの文字列を指定します。
なおプロジェクトは --projectで指定してもOK
$ npx ng build filter-keyup-events
ng help
ng help で build に関する記述を見てみます。
Compiles an Angular app into an output directory named dist/ at the given output path. Must be executed from within a workspace directory.
またバリデーションにも対応させます。
npm install jquery --save
npm install jquery-datetimepicker --save
"styles": [
"src/styles.scss" ,
"node_modules/jquery-datetimepicker/build/jquery.datetimepicker.min.css"
],
"scripts": [
"node_modules/jquery/dist/jquery.min.js" ,
"node_modules/jquery-datetimepicker/build/jquery.datetimepicker.full.min.js"
]
import { Component } from '@angular/core';
// jquery
declare var $: any;
ng g component page01
const routes: Routes = [
{ path: 'page01', component: Page01Component },
{ path: '**', component: Page01Component }, // 追加
];
<input type="text" class="form-control jquery-datetimepicker" name="test-date" formControlName="datepicker" autocomplete="off">
<pre class="debug">
{{ registerForm.value | json }} <br>
Validation: {{ registerForm.get( 'datepicker' ).valid }}
</pre>
import { Component, OnInit } from '@angular/core';
// jquery
declare var $: any;
@Component({
selector: 'app-page01',
templateUrl: './page01.component.html',
styleUrls: ['./page01.component.scss']
})
export class Page01Component implements OnInit {
constructor() { }
ngOnInit() {
// ===== jquery-datetimepicker =====
let _this = this;
$.datetimepicker.setLocale('ja'); // 日本語化
$('input.jquery-datetimepicker').datetimepicker({
lang: 'ja',
timepicker: false,
format:'Y-m-d',
onSelectDate:function( date ){
var year = date.getFullYear();
var month = ("0"+(date.getMonth() + 1)).slice(-2);
var date = ("0"+date.getDate()).slice(-2);
var date_formatted = year + '-' + month + '-' + date;
_this.registerForm.get('datepicker').setValue( date_formatted );
}
});
// ===== jquery-datetimepicker =====
}
}
これでコンポーネントは完成です。
jquery-datetimepicker で日付を選択時に、_this.registerForm.get('datepicker') にも値が反映されます。
https://www.miraclelinux.com/tech-blog/4cd30h
おすすめです。
オブジェクト | 記法 | 例 |
---|---|---|
インターフェース名 | パスカルケース(アッパーキャメル) | interface Result {} |
クラス名 | パスカルケース(アッパーキャメル) | export class ExceptionService |
メソッド名 | キャメルケース | getName() |
プロパティ名 | キャメルケース | private toastCount: number; |
ng new jwtauth-app
cd jwtauth-app
ng serve --open
ng g component login
ng g component home
ng g service services/authentication
ng g guard guard/auth
( CanActivate を選択して作成する )
app/guard/auth.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthenticationService } from '../services/authentication.service';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(
private authService: AuthenticationService,
private router: Router
) { }
canActivate( next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
if ( !this.authService.isAuthenticated() ) {
this.router.navigate(['login']);
return false;
}
return true;
}
}
作成したガードを app/app.module.ts へ読み込ませる
( import して @NgModuleのprovidersに追加する )
app/app.module.ts
// guard
import { AuthGuard } from './guard/auth.guard';
@NgModule({
........
providers: [AuthGuard],
})
mkdir src/app/models
vi src/app/models/user.ts
user.ts
export class User {
id: number;
username: string;
password: string;
firstName: string;
lastName: string;
token?: string;
}
app/home/<いくつかのファイル>
app/login/<いくつかのファイル>
app/services/<いくつかのファイル>
が作成されます
npm install --save bootstrap jquery popper.js
angular.json に以下を追加
"styles": [
"src/styles.scss" ,
"node_modules/bootstrap/dist/css/bootstrap.min.css"
],
"scripts": [
"node_modules/jquery/dist/jquery.slim.min.js",
"node_modules/popper.js/dist/umd/popper.min.js",
"node_modules/bootstrap/dist/js/bootstrap.min.js"
]
npm install @auth0/angular-jwt
app/app.module.ts
import { ReactiveFormsModule } from '@angular/forms';
imports: [
ReactiveFormsModule , // 追加
],
rxjs": "^6.0.0",
↓
rxjs": "6.0.0",
https://chrome.google.com/webstore/detail/augury/elgalmkoelokbchhkhacckoklkejnhcd?hl=ja
import { Router, Route } from "@angular/router";
constructor(private router: Router) { }
ngOnInit() {
this.printpath('', this.router.config);
}
printpath(parent: String, config: Route[]) {
for (let i = 0; i < config.length; i++) {
const route = config[i];
console.log(parent + '/' + route.path);
if (route.children) {
const currentPath = route.path ? parent + '/' + route.path : parent;
this.printpath(currentPath, route.children);
}
}
}
Finally, I am able to solve that issue.
do() is replaced by tap().
for reference https://www.academind.com/learn/javascript/rxjs-6-what-changed/
and tap() should be inside .pipe().
like this, .pipe(tap())
for more reference, you can refer this link,
https://alligator.io/angular/angular-6/
and
https://www.learnrxjs.io/operators/utility/do.html
import { of } from 'rxjs';
import { tap, map } from 'rxjs/operators';
<ion-content overflow-scroll="true">
......
</ion-content>
import { Router, NavigationEnd } from '@angular/router';
constructor(
private router: Router
) {
// 前のページURLを取得する
this.currentUrl = this.router.url;
router.events.subscribe(event => {
if (event instanceof NavigationEnd) {
this.previousUrl = this.currentUrl;
this.currentUrl = event.url;
}
});
}
これで this.previousUrl に前ページURLが入ります。
npm install angularfire2 firebase
var config = {
apiKey: 'Your credentials here',
authDomain: 'Your credentials here',
databaseURL: 'Your credentials here',
projectId: 'Your credentials here',
storageBucket: 'Your credentials here',
messagingSenderId: 'Your credentials here'
};
app/models/song.interface.ts
export interface Song {
id: string;
albumName: string;
artistName: string;
songDescription: string;
sonName: string;
}
Cloud Firestore のコレクション songList を操作するサービスを記述します。
ionic generate service services/data/firestore
自動生成された app/services/data/firestore.service.ts に以下を追記
import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from 'angularfire2/firestore';
import { Song } from '../../models/song.interface';
export class FirestoreService {
constructor(public firestore: AngularFirestore) { }
createSong(
albumName: string,
artistName: string,
songDescription: string,
songName: string
): Promise<void> {
const id = this.firestore.createId();
return this.firestore.doc(`songList/${id}`).set({
id,
albumName,
artistName,
songDescription,
songName,
});
}
getSongList(): AngularFirestoreCollection<Song> {
return this.firestore.collection(`songList`);
}
getSongDetail(songId: string): AngularFirestoreDocument<Song> {
return this.firestore.collection('songList').doc(songId);
}
deleteSong(songId: string): Promise<void> {
return this.firestore.doc(`songList/${songId}`).delete();
}
}
app/home/home.page.ts に下記を追加
import { FirestoreService } from '../services/data/firestore.service';
import { Router } from '@angular/router';
export class HomePage {
public songList; // add this
constructor(
private firestoreService: FirestoreService,
private router: Router
) { }
ngOnInit() {
this.songList = this.firestoreService.getSongList().valueChanges(); // add this
}
}
app/home/home.page.html に下記を追加
<ion-content class="ion-padding">
<ion-card *ngFor="let song of songList | async" routerLink="/detail/{{song.id}}">
<ion-card-header>
{{ song.songName }}
</ion-card-header>
<ion-card-content>
Artist Name: {{ song.artistName }}
</ion-card-content>
</ion-card>
</ion-content>
セキュリティ設定をしていない場合は、全てのアクセスが却下となります。 また一番ゆるく(全てを許可)すると、誰でもアクセスできるようになります。
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}
この設定
export var firebaseConfig = {
projectId: "my-project-name",
storageBucket: "my-project-name.appspot.com",
};
だけで、誰でもアクセスできてしまいます。
service cloud.firestore {
match /databases/{database}/documents {
match /songList/{songId} {
allow read, write: if true;
}
}
}
なお {songId} は ワイルドカードです。
* と記述したいところですが、{songId}と書きます。
{songListId}でもいいみたいです。(文字列はなんでも良いらしい)
引用: http://bit.ly/2Om37Cz
http://bit.ly/385JJBi
【図で解説】Firestoreでできること・できないこと
[Firebase][Cloud firestore] データをユーザーごとに所持するルール | deecode blog
npm install -g @angular/cli
ng new myapp
ng --version
ng serve --open
(モジュールの読み込み)
app/form/form.page.ts
// add
import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms';
(エラーメッセージの定義)
app/form/form.page.ts
validations = {
'username': [
{ type: 'required', message: 'お名前は入力必須です' },
],
'email': [
{ type: 'required', message: 'メールアドレスは入力必須です' },
],
'content': [
{ type: 'required', message: 'お問い合わせ内容は入力必須です' },
],
};
(バリデーションフォームの定義)
app/form/form.page.ts
public loginForm: FormGroup;
constructor(
public router: Router,
public formBuilder: FormBuilder) {
this.loginForm = new FormGroup({
username: new FormControl('', Validators.compose([
Validators.required
])),
email: new FormControl('', Validators.compose([
Validators.required
])),
content: new FormControl('', Validators.compose([
Validators.required
])),
});
}
(お名前フォームのみ)複数入力フォームがある場合は適宜追加すること
<form [formGroup]="loginForm">
<ion-item>
<ion-label position="stacked">お名前 <ion-text color="danger">*</ion-text></ion-label>
<ion-input type="text" formControlName="username"></ion-input>
<!-- error message -->
<div class="error-container">
<ng-container *ngFor="let validation of validations.username">
<div class="error-message"
*ngIf="loginForm.get('username').hasError(validation.type) && (loginForm.get('username').dirty || loginForm.get('username').touched)">
<ion-icon name="information-circle-outline" color="danger"></ion-icon>
<ion-text color="danger">{{ validation.message }}</ion-text>
</div>
</ng-container>
</div>
<!-- /error message -->
</ion-item>
<div class="ion-padding">
<ion-button [disabled]="!loginForm.valid" (click)="onClickSubmit()" expand="block" type="submit"
class="ion-no-margin">送信する</ion-button>
</div>
</form>
https://localhost/posts/ → https://localhost/posts-show/123 への画面遷移を考えてみます。
ionic generate page posts-show
app/posts-show/posts-show-routing.module.ts
const routes: Routes = [
{
path: ':postId', // ● 引数を受ける変数を追加
component: PostsShowPage
}
];
app/posts/posts.page.html
<ion-button expand="block" routerLink="/posts-show/" routerDirection="forward">詳細ページ1</ion-button>
app/posts-show/posts-show.page.html
<ion-header>
<ion-toolbar>
// ● 追加 ↓
<ion-buttons slot="start">
<ion-back-button defaultHref="/posts"></ion-back-button>
</ion-buttons>
// ● 追加 ↑
<ion-title>posts-show</ion-title>
</ion-toolbar>
</ion-header>
デフォルトのリンク先(/posts)をつけておきます。
https://ionicframework.com/jp/docs/api/back-button
https://ionicframework.com/jp/docs/angular/lifecycle
Event Name | Description |
---|---|
ngOnInit | コンポーネントの初期化中に発生します。このイベントを使用して、ローカルメンバーを初期化し、一度だけ実行する必要があるServiceを呼び出すことができます。 |
ngOnDestroy | Angularがビューを破棄する直前に発生します。 observables の unsubscribe などのクリーンアップに役立ちます。 |
Event Name | Description |
---|---|
ionViewWillEnter | コンポーネントが表示されるアニメーションがはじまる時に発火します。 |
ionViewDidEnter | コンポーネントが表示されるアニメーションが終了した時に発火します。 |
ionViewWillLeave | コンポーネントを離脱するアニメーションがはじまる時に発火します。 |
ionViewDidLeave | コンポーネントを離脱するアニメーションが終了した時に発火します。 |
https://ionicframework.com/jp/docs/api/loading
app/posts/posts.page.ts
import { LoadingController } from '@ionic/angular';
constructor に 以下のloadingController を追加
constructor(
private http: HttpClient,
public loadingController: LoadingController // この行を追加
) { }
以下を追加
async ionViewDidEnter(){
// define loading
const loading = await this.loadingController.create({
spinner: 'circular',
message: 'loading ...',
translucent: true,
});
// loading 表示
await loading.present();
// Make the HTTP request:
this.http.get('https://YOUR-SERVER.TLD/api/posts').subscribe(data => {
console.log(data);
loading.dismiss(); // これを追加(jsonデータ完了時に loading を非表示とする)
});
}
app/form/form.page.html
<form #form="ngForm" (ngSubmit)="postForm(form.value)">
<ion-item>
<ion-label position="stacked">お名前 <ion-text color="danger">*</ion-text></ion-label>
<ion-input type="text" [(ngModel)]="contact.username" name="contact.username"></ion-input>
</ion-item>
<ion-item>
<ion-label position="stacked">メールアドレス <ion-text color="danger">*</ion-text></ion-label>
<ion-input required email type="email" [(ngModel)]="contact.email" name="contact.email"></ion-input>
</ion-item>
<ion-item>
<ion-label position="stacked">お問い合わせ内容 <ion-text color="danger">*</ion-text></ion-label>
<ion-textarea [(ngModel)]="contact.content" name="contact.content"></ion-textarea>
</ion-item>
<div class="ion-padding">
<ion-button expand="block" type="submit" class="ion-no-margin">送信する</ion-button>
</div>
</form>
app/form/form.page.ts
export class FormPage implements OnInit {
// フォームパラメーターモデルの定義
contact = {
username: '' ,
email: '' ,
content: '',
};
constructor() {}
ngOnInit() {}
postForm(formValue){
console.log('postForm()');
console.log( formValue );
}
}
app/posts/posts.page.ts
export class PostsPage implements OnInit {
posts_loop : {};
ngOnInit(): void {
posts_loop = {};
}
app/posts/posts.page.html
<ion-item *ngFor="let v of posts_loop;">
<img src="{{v.file_url_array['0']}}" />
</ion-item>
app/app.module.ts
// ● Add this
import { HttpClientModule } from '@angular/common/http';
imports: [
BrowserModule,
HttpClientModule, // ● Add this
..... ,
..... ,
例 (app/posts/posts.page.ts)
app/posts/posts.page.ts
// ● Add this
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
....
....
// ● Add this
constructor(private http: HttpClient) { }
// ● Add this
ngOnInit(): void {
// Make the HTTP request:
this.http.get('https://YOUR-SERVER.TLD/api/posts').subscribe(data => {
console.log(data);
console.log(data[1]['name']);
});
}
ionic generate <type> <name> [options]
コマンド例
ionic generate
ionic generate page
ionic generate page contact
ionic generate component contact/form
ionic generate component login-form --change-detection=OnPush
ionic generate directive ripple --skip-import
ionic generate service api/user
(posts ページを追加してみます。)(フォルダも作成できます。その場合は posts/index のように記述します。)
ionic generate page posts
こちらのファイルが追加されます
CREATE src/app/posts/posts-routing.module.ts (343 bytes)
CREATE src/app/posts/posts.module.ts (465 bytes)
CREATE src/app/posts/posts.page.scss (0 bytes)
CREATE src/app/posts/posts.page.html (124 bytes)
CREATE src/app/posts/posts.page.spec.ts (640 bytes)
CREATE src/app/posts/posts.page.ts (252 bytes)
UPDATE src/app/app-routing.module.ts (712 bytes)
app/app.component.ts
public appPages = [
{
title: 'Home',
url: '/home',
icon: 'home'
},
{
title: 'List',
url: '/list',
icon: 'list'
},
// 追加
{
title: 'Posts',
url: '/posts',
icon: 'clipboard'
},
// 追加
];
icon はこちらから調べます
https://ionicons.com/
これで、サイドメニューに追加されて画面遷移が確認できます。
追加されたページに sidemenu を追加する
app/posts/posts.page.html
<ion-header>
<ion-toolbar>
<ion-title>posts</ion-title>
</ion-toolbar>
</ion-header>
↓
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title>
posts
</ion-title>
</ion-toolbar>
</ion-header>
npm i -g ionic cordova
Angular 4+ w/ Angular CLI
ionic start myapp
または
Angular 2/3 w/ @ionic/app-scripts
ionic start myapp --type=ionic-angular
ionic-angular のところは プロジェクトのタイプ を選択します。
Project Type | Description |
---|---|
angular |
Ionic Angular 4+ w/ Angular CLI for Tooling |
ionic-angular |
Ionic Angular 2/3 w/ @ionic/app-scripts for Tooling |
ionic1 |
Ionic 1 w/ AngularJS |
テンプレートの選択
Starter template: (Use arrow keys)
❯ tabs | A starting project with a simple tabbed interface
sidemenu | A starting project with a side menu with navigation in the content area
blank | A blank starter project
super | A starting project complete with pre-built pages, providers and best practices for Ionic development.
tutorial | A tutorial based project that goes along with the Ionic documentation
aws | AWS Mobile Hub Starter
cd myapp
ionic serve --devapp
ionic devapp をインストールします
https://ionicframework.com/docs/appflow/devapp
https://ionicframework.com/jp/docs/angular/your-first-app
ionic build
www フォルダをアップロードすれば普通に動きます。
ionic info
--type=ionic-angular で作成したアプリの場合
Ionic:
Ionic CLI : 5.4.13 (/Users/xxxxx/.anyenv/envs/nodenv/versions/12.14.0/lib/node_modules/ionic)
Ionic Framework : ionic-angular 3.9.9
@ionic/app-scripts : 3.2.4
Utility:
cordova-res : 0.8.1
native-run : not installed
System:
NodeJS : v12.14.0 (/Users/xxxxx/.anyenv/envs/nodenv/versions/12.14.0/bin/node)
npm : 6.13.4
OS : macOS Catalina
ionic4アプリの場合
Ionic:
Ionic CLI : 5.4.13 (/Users/xxxxx/.anyenv/envs/nodenv/versions/12.14.0/lib/node_modules/ionic)
Ionic Framework : @ionic/angular 4.11.7
@angular-devkit/build-angular : 0.801.3
@angular-devkit/schematics : 8.1.3
@angular/cli : 8.1.3
@ionic/angular-toolkit : 2.1.1
Cordova:
Cordova CLI : 9.0.0 (cordova-lib@9.0.1)
Cordova Platforms : not available
Cordova Plugins : not available
Utility:
cordova-res : 0.8.1
native-run : not installed
System:
NodeJS : v12.14.0 (/Users/xxxxx/.anyenv/envs/nodenv/versions/12.14.0/bin/node)
npm : 6.13.4
OS : macOS Catalina