반응형

1. Module로 만들기

2. Module에 export추가하기

3. 원하는 여러 모듈에 import


isDayTime 이라는 boolean 타입의 값을 한글로 변환해 주는 파이프입니다.

true면 주간으로

false면 야간으로 변경합니다.

 

최종 형태:

// day-time.pipe.module.ts

import { NgModule } from "@angular/core";
import { DayTimePipe } from "./day-time.pipe";

@NgModule({
    imports: [],
    declarations: [DayTimePipe],
    exports: [DayTimePipe]
})
export class DayTimePipeModule { }
// day-time.pipe.ts

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'dayTime'
})
export class DayTimePipe implements PipeTransform {

  transform(value: boolean): string {
    return !!value ? '주간' : '야간';
  }

}

사용 예:

<!-- 어디가.html -->
<ion-item>
  <ion-label>근무 시간 선택: {{recoderTemplate.info.isDayTime | dayTime}}점검</ion-label>
  <ion-toggle color="success" [(ngModel)]="recoderTemplate.info.isDayTime"></ion-toggle>
</ion-item>

주의사항: 'day-time.pipe.module.ts'가 아래 코드와 같이 다른 모듈이 imports 되어있다면 애러가 발생합니다.

그러니 꼭 다른 imports가 없도록 만들어야합니다.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { RouterModule, Routes } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { TimeReadComponent } from './time-read.component';

const routes: Routes = [
  {
    path: '',
    component: DayTimePipe
  }
];

@NgModule({
  imports: [
    CommonModule,
    IonicModule,
    FormsModule,
    RouterModule.forChild(routes)
  ],
  declarations: [DayTimePipe],
  exports: [DayTimePipe]
})
export class DayTimePipeModule { }

 

반응형
반응형

아이오닉(앵귤러)로 Array데이터를 필터링 하는 방법입니다.

 

기존방식: DOM을 직접 수정. Class나 style를 추가하여 DOM에 값을 추가하는 방식이었습니다.

jQuery 및 elemnt에 직접 접근하여 Class 추가 및 제거, style의 display값을 변경합니다.

기존의 방식처럼 element에 속성 값을 추가하려고 하면 에러가 발생합니다.

렌더링 할 때 제어할 수 없는 값이 추가되어 있기 때문입니다.

 

그렇다면 어떻게 해야할까요?

 

프로그래시브 프레임워크에서는 Array data를 직접 서버사이드 렌더링 처럼 처리합니다.

1. 데이터 javascript로 배열 데이터 필터

2. HTML 렌더링

<!-- HTML -->
<ion-header>
  <ion-toolbar>
    <ion-title>숫자 필터</ion-title>
  </ion-toolbar>
</ion-header>
 
<ion-content>
  <ion-item>
    <ion-button color="success" fill="solid" slot="end" (click)="filterArray($event.target, 1)" clickable>짝수</ion-button>
    <ion-button color="medium" fill="solid" slot="end" (click)="filterArray($event.target, 0)" clickable>홀수</ion-button>
  </ion-item>
  <ion-list>
    <ion-card >
      <ion-card-header color="primary">
        <ion-label>위에 필터를 눌러보세요.</ion-label>
      </ion-card-header>
      <ion-item *ngFor="let number of filteredArray; let idx = index;">
        <ion-grid>
          <ion-row>
            <ion-col size-sm="2">
              <ion-label position="floating">{{number}}</ion-label>
            </ion-col>
          </ion-row>
        </ion-grid>
      </ion-item>
    </ion-card>
  </ion-list>
</ion-content>


// angular
import { Component, OnInit } from '@angular/core';

  filteredArray: number[] = [...this.array];
  
  private _array: number[] = [1,2,3,4,5,6];
  private _filter: number[] = [];
  
  constructor() {}

  filterArray(element: HTMLElement, reminder: number) {
    this._addToFilter(reminder);
    if(this._filter.length > 0) {
      this._filter.forEach(_reminder => this._filterByReminder(_reminder))
    }
    this._toggleButtonOutline(element);
  }
  
  private _filterByReminder(reminder: number) {
    this.filteredArray = this.filteredArray.filter(number => number % 2 == reminder);
  }  

  private _addToFilter(reminder) {
    if(this._filter_has(reminder)) {
      this._resetArray();
      this._filter_pop(reminder);
    } else {
      this._filter.push(reminder);
    }
  }

  private _filter_has(reminder: number): boolean {
    return this.filter.indexOf(reminder)>-1;
  }

  private _resetArray() {
    this.filteredArray = [...this._array]
  }

  private _filter_pop(target: number) {
    this._filter = this._filter.filter(num => num != target);
  }

  private _toggleButtonOutline(button: HTMLElement) {
    button['fill'] = button['fill'] == "outline" ? "solid" : "outline";
  }

필터 버튼 보이는 것 때문에 매쏘드에서 reminder의 개념이 헤깔리네요...

반응형

'프로그래밍 > Angular' 카테고리의 다른 글

Pipe 재사용 및 Shared Module 사용법  (0) 2021.07.09
Service VS EventEmitter  (0) 2020.02.19
Dynamic Component  (0) 2019.09.13
Authentication & Route Protection  (0) 2019.09.06
Http  (0) 2019.08.20
반응형

Service:

내부 컴퍼넌트의 ngOnInit 및 ngOndestroy가 라우터로 인해서 의도와는 다르게 작동할 경우 유용합니다. 실시간으로 데이터 통신을 

하지만, 메모리 leak을 발생할 수 있기 때문에, Subscription을 이용해서 반드시 destory합니다.

 

넓은 범위에 걸쳐 brodcasting을 하기 때문에 child, parent와 상관없이 일정 범위를 지정해준다면 쉽게 data communication에 편리합니다.

편리한만큼 여러 사람들과 개발하면, 그 의미를 잊기 쉽습니다.

 

eventEmitter:

cascading으로 1단계씩 움직일 때 직관적으로 유용합니다. 지정된 형식으로 한정된 범위로 Event를 전송하기 때문에, 개발하는 입장에서 쉽게 이용할 수 있지만, 데이터 변동된 값이 trigger를 작동시키기엔 무리가 있습니다.

반응형
반응형

간단하게 ngIf를 이용하여 alert를 이용할 수 있습니다.

 

하지만 3nd-party 라이브러리를 만드는 경우, 그리고 in-code라이브러리를 사용하는 경우에 필요한 기법입니다.

순수하게 프로그래밍으로만 컴포넌트를 만드는 것이죠.

 

방법은 다음과 같습니다.

1. 생성할 Component의 Factory만들기.

2. view container 레퍼런스 (placeholder Directive를 사용하는 거죠.)

3. Angular의 도움을 받아 생성한 Component를 랜더링 합니다.

4. app.module.ts에 entryComponents를 추가합니다.

 

1. Factory생성 및 unsubscribe();

// auth.component.ts
private showErrorAlert(message: string) {
    // const alertCmp = new AlertComponent();
    const alertCmpFactory = this.componentFactoryResolver.resolveComponentFactory(
      AlertComponent
    );
    const hostViewContainerRef = this.alertHost.viewContainerRef;
    hostViewContainerRef.clear();

    const componentRef = hostViewContainerRef.createComponent(alertCmpFactory);

    componentRef.instance.message = message; // alert.component.ts의 @Input() message: string;
    this.closeSub = componentRef.instance.close.subscribe(() => {
      this.closeSub.unsubscribe();
      hostViewContainerRef.clear();
    });
  }

 

2. view container 사용법 <ng-template>를 이용합니다.

<!-- auth.component.html -->
<ng-template appPlaceholder></ng-template>
<div class="row">
  <div class="col-xs-12 col-md-6 col-md-offset-3">
    <!-- <div class="alert alert-danger" *ngIf="error">
      <p>{{ error }}</p>
    </div> -->
    <!-- <app-alert
      [message]="error"
      *ngIf="error"
      (close)="onHandleError()"
    ></app-alert> -->
    <div *ngIf="isLoading" style="text-align: center;">
      <app-loading-spinner></app-loading-spinner>
    </div>
// placeholder.directive.ts
import { Directive, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[appPlaceholder]'
})
export class PlaceholderDirective {
  constructor(public viewContainerRef: ViewContainerRef) {}
}

3. 랜더링할 컴포넌트 입니다.

// alert.component.ts

import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-alert',
  template: `
  <div class="backdrop" (click)="onClose()"></div>
  <div class="alert-box">
    <p>{{ message }}</p>
    <div class="alert-box-actions">
      <button class="btn btn-primary" (click)="onClose()">Close</button>
    </div>
  </div>`,
  styles: [`
  .backdrop {position: fixed;top: 0;left: 0;width: 100vw;height: 100vh;background: rgba(0, 0, 0, 0.75);z-index: 50;}
  .alert-box {position: fixed;top: 30vh;left: 20vw;width: 60vw;padding: 16px;z-index: 100;background: white;box-shadow: 0 2px 8px rbga(0, 0, 0, 0.26);}
  .alert-box-actions {text-align: right;}
  `]
})
export class AlertComponent {
  @Input() message: string;
  @Output() close = new EventEmitter<void>();

  onClose() {
    this.close.emit();
  }
}

4. entryComponents를 선언합니다.

//app.module.ts
...
bootstrap: [AppComponent],
  entryComponents: [
    AlertComponent
  ]
})
export class AppModule {}

 

*ngIf를 이용하는것이 쉬운 방법이며, Angular의 강력한 도구이기도 합니다.

ngIf가 false인 경우, DOM을 랜더링하지 않습니다.

필요한 경우에만 다이나믹 컴포넌트를 사용하는것이 좋겠죠?

반응형
반응형

환경 설정

* 환경 : firebase DB & REST API

 

1. firebase에서 프로젝트를 선택(or 생성)합니다.

2. Database -> 규칙

{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}

3. Authntication -> 로그인 방법 -> 이메일/비밀번호 활성화


유저 생성

 

아래와 같이 구글에서 firebase auth rest api를 검색합니다.

https://firebase.google.com/docs/reference/rest/auth

 

Firebase Auth REST API  |  Firebase

API Usage You can query the Firebase Auth backend through a REST API. This can be used for various operations such as creating new users, signing in existing ones and editing or deleting these users. Throughout this document, API_KEY refers to the Web API

firebase.google.com


사이트 오른쪽 목차에서 Sign up with email / password 로 아래와 같은 조건을 만족시키는 통신을 합니다.

 

Method: POST

Content-Type: application/json

Request Body Payload

Property NameType Description
email string 유저 생성을 위한 email
password string 유저 생성을 위한 비밀번호
returnSecureToken boolean

ID 와 새로고침 토큰을 반환 하는지 안하는지, 항상 true입니다.

 

post로 https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=[API_KEY] 를 합니다.

API_KEY는 화면 왼쪽 drawer 에서 톱니바퀴(설정)을 눌러 확인할 수 있습니다.

내 프로젝트 [웹 API 키]를 복사하여 이용합니다.

// auth.service.ts
signup(email: string, password: string) {
  return this.http
    .post(
    'https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=AIzaSyC4NdTkUmluvTX-wfuFKVZPdFCXvZKiWEQ',
    {
      email: email,
      password: password,
      returnSecureToken: true
    }
)

Observable을 반환하기 때문에 return을 사용해줍니다.

Response Payload

Property NameType Description
idToken string 신규 유저의 Auth ID 토큰.
email string 신규 유저를 위한 email.
refreshToken string 신규 유저를 위한 Auth 새로고침 토큰.
expiresIn string ID 토큰의 만료를 초를 숫자로 나타냅니다.
localId string The uid of the newly created user.

리스폰스에 알맞는 데이터를 받기 위해 아래와 같은 인터페이스를 만들어 포스트의 리턴타입을 입력합니다.

// auth.service.ts
export interface AuthResponseData {
  idToken: string;
  email: string;
  refreshToken: string;
  expiresIn: string;
  localId: string;
}

signup(email: string, password: string) {
  return this.http
  .post<AuthResponseData>(
  'https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=AIzaSyC4NdTkUmluvTX-wfuFKVZPdFCXvZKiWEQ',
  {
    email: email,
    password: password,
    returnSecureToken: true
  }
)
// auth.component.ts

onSubmit(form: NgForm) {
  if (!form.valid) {
    return;
  }
  const email = form.value.email;
  const password = form.value.password;
  
  this.authService.subscribe(
    resData => {
      console.log(resData);
    },
    errorMessage => {
      console.log(errorMessage);
    }
  );
  form.reset();
}

로딩 스피너

1. 컴포넌트 만들기

share폴더에

  loading-spinner 폴더와 ts, css파일을 생성합니다.

    loading-spinner.component.ts

    loading-spinner.component.css

 

구글에 검색합니다. css loading spinners https://loading.io/css/

해당 사이트에서 원하는 스피너를 선택한 뒤에, css에 값과 html값을 복붙합니다.

// loading-spinner.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-loading-spinner',
  template:
    '<div class="lds-ring"><div></div><div></div><div></div><div></div></div>',
  styleUrls: ['./loading-spinner.component.css']
})
export class LoadingSpinnerComponent {}

2. HTML에 추가하기

<!-- auth.component.html -->

<div class="row">
  <div class="col-xs-12 col-md-6 col-md-offset-3">
    <div *ngIf="isLoading">
      <app-loading-spinner></app-loading-spinner>
    </div>
    <form #authForm="ngForm" (ngSubmit)="onSubmit(authForm)" *ngIf="!isLoading">
      <!-- ... -->
    </form>
  </div>
</div>

form 태그에 isLoading을 이용하여 조건별로 나타날 수 있도록 합니다.

에러 핸들링

UI를 위해 error를 추가합니다.

<!-- auth.component.html -->

<div class="row">
  <div class="col-xs-12 col-md-6 col-md-offset-3">
  <div class="alert alert-danger" *ngIf="error">
      <p>{{ error }}</p>
    </div>
    <div *ngIf="isLoading">
      <app-loading-spinner></app-loading-spinner>
    </div>
    <form #authForm="ngForm" (ngSubmit)="onSubmit(authForm)" *ngIf="!isLoading">
      <!-- ... -->
    </form>
  </div>
</div>
// auth.component.ts

isLoading = false;
error: string = null;

onSubmit(form: NgForm) {
  if (!form.valid) {
    return;
  }
  const email = form.value.email;
  const password = form.value.password;
  this.isLoading = true;
  
  this.authService.subscribe(
    resData => {
      console.log(resData);
      this.isLoading = false;
    },
    errorMessage => {
      console.log(errorMessage);
      this.error = errorMessage;
      this.isLoading = false;
    }
  );
  form.reset();
}

 

firebase auth rest api 에서 조금 더 아래로 내리면 아래와 같은 내용을 발견할 수 있습니다.

 

Common error codes

  • EMAIL_EXISTS: The email address is already in use by another account.
  • OPERATION_NOT_ALLOWED: Password sign-in is disabled for this project.
  • TOO_MANY_ATTEMPTS_TRY_LATER: We have blocked all requests from this device due to unusual activity. Try again later.

errorRes를 보면, 

error안에 error가 있고, message를 보면, EAMIL_EXISTS와 같은 코드를 받을 수 있습니다.

 

에러 코드를 이용하면 사용자에게 쉽게 안내할 수 있습니다.

errorRes => {
  console.log(errorRes);
  switch (errorRes.error.error.message) {
  case 'EMAIL_EXISTS':
  this.error = 'This email exists already';
  break;
  case 'EMAIL_NOT_FOUND':
  this.error = 'This email does not exist.';
  break;
  case 'INVALID_PASSWORD':
  this.error = 'This password is not correct.';
  break;
  }
}

로그인 만료 시간 핸들링

로그인 하기 전에는 브라우져로 다시 들어갈 경우, 지속적으로 로그인 상태가 됩니다.

이런 상황을 방지하기 위한 방법 입니다.

 

Response Payload

Property NameTypeDescription

expiresIn string The number of seconds in which the ID token expires.

expiresIn은 ID토큰의 만료 시간입니다.

const expirationDate = new Date(new Date().getTime() + expiresIn * 1000);

곱하기 1000을 해서 밀리세컨드(ms = 1/1000 초)를 하여 생성되는 아이디의 초과 시간을 Local에 저장해 둡니다.


Auto Loggin

 

로컬 스토리지에 userData저장.

localStorage.setItem('userData', JSON.stringify(user)); // JSON.stringify()를 꼭사용해주세요.

 

불러오기

autoLogin() {

	// userData snapshot을 불러옵니다.
    const userData: {
      email: string;
      id: string;
      _token: string;
      _tokenExpirationDate: string;
    } = JSON.parse(localStorage.getItem('userData')); // JSON.parse로 반드시 파싱합니다.
    if (!userData) { // 데이터가 없을경우 끗.
      return;
    }

    const loadedUser = new User(
      userData.email,
      userData.id,
      userData._token,
      new Date(userData._tokenExpirationDate)
    );

	// 로그인 제한시간을 확인합니다.
    if (loadedUser.token) {
      this.user.next(loadedUser);
      const expirationDuration =
        new Date(userData._tokenExpirationDate).getTime() -
        new Date().getTime();
      this.autoLogout(expirationDuration);
    }
  }

Guard (Router Protection)

// auth.guard.ts
import {
  CanActivate,
  ActivatedRouteSnapshot,
  RouterStateSnapshot
} from '@angular/router';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

import { AuthService } from './auth.service';

@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
  constructor(private authService: AuthService) {}

  canActivate(
    route: ActivatedRouteSnapshot,
    router: RouterStateSnapshot
  ):
    | boolean
    | Promise<boolean>
    | Observable<boolean> {
    return this.authService.user.pipe(
      map(user => {
        return !!user;
      })
    );
  }
}

Activate 인터페이슨는 route와 state(RouterStateSnapshot)을 필요로 합니다.

또한 boolean, Promise<boolean>, Observable<boolean>의 형태로 반환해야 합니다.

Authenticated User는 pipe(map())으로 묶어서 반환합니다.

 

// app-routing.module.ts

canActivate: [AuthGuard]//를 추가합니다

리다이렉트 시키기!

URLTree를 이용하여 원하는 곳으로 이동시킬 수 있습니다.

constructor(private authService: AuthService, private router: Router) {}

  canActivate(
    route: ActivatedRouteSnapshot,
    router: RouterStateSnapshot
  ):
    | boolean
    | UrlTree
    | Promise<boolean | UrlTree>
    | Observable<boolean | UrlTree> {
    return this.authService.user.pipe(
      take(1), // 불필요한 subscribe를 방지합니다. 1번만 실행.
      map(user => {
        const isAuth = !!user;
        if (isAuth) {
          return true;
        }
        return this.router.createUrlTree(['/auth']); // URLTree를 생성하여 login하도록 리다이렉트.
      })
      // angular 옛 버전에서는 직접 리다이렉트 주소를 기입해야 합니다.
      // tap(isAuth => {
      //   if (!isAuth) {
      //     this.router.navigate(['/auth']); // 일일히 리다이렉트 주소 입력.
      //   }
      // })
    );

 

 

반응형

'프로그래밍 > Angular' 카테고리의 다른 글

Service VS EventEmitter  (0) 2020.02.19
Dynamic Component  (0) 2019.09.13
Http  (0) 2019.08.20
Pipe  (0) 2019.08.15
Form - 복습 및 보충 내용  (0) 2019.08.14
반응형

http.get

import { HttpClient } from '@angular/common/http';

private fetchPosts() {
    this.http
      .get('https://PROJECT_NAME.firebaseio.com/posts.json')
      .subscribe(posts => {
        console.log(posts);
      });
  }

HttpClient를 이용하여, 매우 간단하게 가져올 수 있습니다.


자료 가공하기

import { map } from 'rxjs/operators';

id라는 key를 추가하여 post를 get하는 코드입니다.

private fetchPosts() {
    this.http
      .get('https://ng-complete-guide-be717.firebaseio.com/posts.json')
      .pipe(
        map(responseData => {
          const postsArray = [];
          for (const key in responseData) {
            if (responseData.hasOwnProperty(key)) {
              postsArray.push({ ...responseData[key], id: key });
            }
          }
          return postsArray;
        })
      )
      .subscribe(posts => {
        // ...
        console.log(posts);
      });
  }

get()와 subscribe()사이에 pipemap으로 변경하였습니다.

 

pipe는 앞에서 사용해 보았듯이, 

여러가지 operator로 observable 데이터를 가공(연산)할 수 있도록 합니다.

 

여기선 map Operator를 사용하였습니다.

한번더 observable로 레핑(wrapping)하는 역할입니다.

 

id는 랜덤으로 설정되는 글자이기 때문에 string으로 하였습니다.

 

결과는 캡쳐화면처럼 id의 key:value를 response에 추가해 줍니다.


Type사용하기.

export interface Post {
  title: string;
  content: string;
  id?: string; // optional 물음표 보이시죠? 있을수도 있고 없을 수도 있습니다.
}

먼저 Post를 선언합니다.

 

private fetchPosts() {
		...
      .pipe(
        map((responseData: {[key: string]: Post}) => {
          const postsArray = [];
		    ...
          return postsArray;
        })
      )
		...
  }

map의 데이터의 타입을 지정해줍니다. key는 string으로 되어있고, value는 Post로 합니다.

 

이렇게 하여 response데이터의 타입을 알려주게 됩니다.

또한 맵 안에 있는 postsArray도 타입을 지정할 수 있게됩니다.

private fetchPosts() {
		...
      .pipe(
        map((responseData: {[key: string]: Post}) => {
          const postsArray: Post[] = [];
		    ...
          return postsArray;
        })
      )
		...
  }

 

조금 더 편리한 방법은 response body의 타입을 지정해 주는 방식입니다.

private fetchPosts() {
    this.http
      .get<{[key: string]: Post}>('https://PROJECT_NAME.firebaseio.com/posts.json')
      .pipe(
        map(responseData => {
          const postsArray: Post[] = [];
          for (const key in responseData) {
            if (responseData.hasOwnProperty(key)) {
              postsArray.push({ ...responseData[key], id: key });
            }
          }
          return postsArray;
        })
      )
      .subscribe(posts => {
        // ...
        console.log(posts);
      });
  }

get에 <>를 추가하여 제네릭을 추가합니다.

따로 map과 같은 operator를 사용할 때마다 지정하지 않아도 body와 동일한 타입으로 인식합니다.


Post에도 response body 타입 지정해보기.

onCreatePost(postData: { title: string; content: string }) {
    // Send Http request
    this.http
      .post(
        'https://PROJECT_NAME.firebaseio.com/posts.json',
        postData
      )
      .subscribe(responseData => {
        console.log(responseData);
      });
  }

위와 같은 코드가 있을 경우, subscribe의

responseData는 (parameter) responseData: Object로 나타납니다.

 

 

하지만 .post<name: string> 을 추가할 경우,

onCreatePost(postData: { title: string; content: string }) {
    // Send Http request
    this.http
      .post<{name: string}>(
        'https://PROJECT_NAME.firebaseio.com/posts.json',
        postData
      )
      .subscribe(responseData => {
        console.log(responseData);
      });
  }

responseData는 (parameter) responseData: {name: string;}으로 나타납니다.


Error Handling

 

http에서 지원하는 error사용.

.subscribe(
  (posts: Post[])=> {
    this.isFetching = false;
    this.loadedPosts = posts;
  }, (error) => {
    console.log(error);
    this.error = error.message;
});

console.log(error)의 내용 입니다.

여러곳에서 에러를 다루어야할 경우, Subject를 생성합니다.

// service.ts
import { Subject} form 'rxjs';

error = new Subject<string>();

.subscribe(
	...
  },
  error => {
  	this.error.next(error.message);
  }
)

다른 곳에서 service.error.subscribe()를 통하여 에러메시지를 핸들링 할 수 있습니다.

 

catchError를 이용하여, analytics server등을 던질 수 있습니다.

import { throwError } form 'rxjs';

  ...
,
catchError(errorRes => {
	return throwError(errorRes);
});

제네릭 에러를 핸들링하는 작업이 필요할 경우 사용하면 됩니다.


Header Setting

import { HttpHeaders } from '@angular/common/http';

.get<{ [key: string]: Post }>(
  'https://ng-complete-guide-be717.firebaseio.com/posts.json',
  {
    headers: new HttpHeaders({'Custom-Header': 'Hello'})
  }
)

hangular/common/http 에서 지원하는 HttpHeaders를 이용하면, 원하는 Header를 추가할 수 있습니다.

해더에 추가한 대로, Custom-Header에 Hello라는 key-value pair로 나오죠?


파라미터 추가하기

 

import { HttpPramas } from '@angular.common,http';

params: new HttpParams().set('print', 'pretty')
let searchParams = new HttpParams(); // multi. 파라미터 추가하기.
searchParams = searchParams.append('print', 'pretty');
searchParams = searchParams.append('custom', 'key');

javascript로 데이터 받기(data type 변경하기)

2번째 argu.에 observe를 추가합니다.

this.http
  .post<{ name: string }>(
    'https://ng-complete-guide-c56d3.firebaseio.com/posts.json',
    postData,
    {observe: 'response'}
)

tap operator

tap 연산자는 Observable을 디버깅 할 수 있도록 정확한 값이나, 다른 사이드 이팩트에 유용합니다.

Note: Observable의 측면에서 subscribe와는 다릅니다. 설령 subscribe하지 않은 Observable이 tap에 의해서 return되어도 , Observer에 의한 사이드 이팩트는 절대 발생하지 않습니다. tap은 단순히 존재하는 실행만 엿볼뿐, subscribe처럼 이벤트를 실행하지 않습니다.

 

Response body Type 변경하기

deletePosts() {
  return this.http.delete(
    'https://ng-complete-guide-c56d3.firebaseio.com/posts.json', {
    observe: 'events',
    responseType: 'json'
    }
  );
}

interceptors

모든 

// auth-intercptor.service.ts

import { HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http';

export class AuthInterceptorService implements HttpInterceptor {
    intercept(req: HttpRequest<any>, next: HttpHandler) {
      console.log('Resquest in an Its way');
      return next.handle(req);
    }
}
// app.module.ts

providers: [
  {
    provide: HTTP_INTERCEPTORS, // 토큰
    useClass: AuthInterceptorService,
    multi: true
  } 
], 

Header 추가하기

intercept(req: HttpRequest<any>, next: HttpHandler) {
  console.log(req.url);
  const modifiedRequest = req.clone({headers: req.headers.append('Auth', 'xyz')})
  console.log('Resquest in an Its way');
  return next.handle(modifiedRequest);
}

로깅 인터셉터

// logging-interceptor.service.ts

import { HttpInterceptor, HttpRequest, HttpHandler, HttpEventType } from '@angular/common/http';
import { tap } from 'rxjs/operators';

export class LoggingIntercptorService implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler) {
    console.log('Outgoing request');
    console.log(req.url);
    console.log(req.headers);
    return next.handle(req).pipe(
      tap(event => {
        if (event.type === HttpEventType.Response) {
          console.log('Incoming response');
          console.log(event.body);
      }
    }));
  }
}
// app.module.ts

providers: [
    {
      provide: HTTP_INTERCEPTORS, // 토큰
      useClass: AuthInterceptorService,
      multi: true
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: LoggingIntercptorService,
      multi: true
    }
  ], 

여러개의 인터셉터를 사용할 경우에는 순서매우 중요합니다.

providers에서 Logging은 뒤쪽에 위치해야 합니다.

 

 

반응형

'프로그래밍 > Angular' 카테고리의 다른 글

Dynamic Component  (0) 2019.09.13
Authentication & Route Protection  (0) 2019.09.06
Pipe  (0) 2019.08.15
Form - 복습 및 보충 내용  (0) 2019.08.14
FormHandling - Reavice  (0) 2019.07.02
반응형

Pipe는 보이는 값(view)에만 영향을 미칩니다.

당연히 template에 로직이 있는게 정석이죠.

 

예제) username을 대문자로 나타내시오.

 

username = 'kim' 일 경우, 템플릿에 {{username}}이라고 합니다.

pipe는 {{username | uppercase}} 로,  '|' 뒤에 붙여서 output만 변경 시킵니다.

 

pipe 연산 순서

보통 왼쪽에서 오른쪽으로 -> 연산하게 됩니다.

{{server.start | date:'fullDate' | uppercase }} // Mon Aug 09 1920 00:00:00 GMT+0900 (한국 표준시)

{{server.start | date:'fullDate' | uppercase }} // 에러가 발생합니다. Object를 uppercase할 수 없기 때문

API : https://angular.io/api?query=pipe


커스텀 pipe 만들기

 

10글자 이상일 경우 뒤에 ... 붙여서 보여주기.

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'shorten'
})
export class ShortenPipe implements PipeTransform {
  transform(value: any) {
    if (value.length > 10) {
      return value.substr(0, 10) + '...';
    }
    return value;
  }
}

 

Pipe데코레이터를 선언합니다.

impements PipeTransform 인만큼 transform매쏘드가 필요합니다. (1개 이상의 파라미터를 요구합니다.)

반드시 return값이 있어야 겠져?

 

app.moule.ts에서 declarations에 'ShortenPipe'를(class name) 추가해 줍니다

 

{{servername | shorten}} // name을 적어주면 됩니다.


pipe로 array 필터링 하기

 

input(필터)에 속성 값을 입력한 것이 나오도록 하는 필터 입니다.

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'filter'
})
export class FilterPipe implements PipeTransform {

  transform(value: any, filterString: string, propName: string): any {
    if ( value.length === 0 || filterString === '') {
      return value;
    }
    const resultArray = [];
    for (const item of value) {
      if (item[propName] === filterString) {
        resultArray.push(item);
      }
    }
    return resultArray;
  }
}

필터 값이 비어있다면, 필터링 되지 않은 전체를 보여줍니다.

여기서는 서버의 상태(status)를 필터 합니다

<div class="container">
  <div class="row">
    <div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
      <input type="text" [(ngModel)]="filterStatus">
      <hr>
      <ul class="list-group">
        <li
          class="list-group-item"
          *ngFor="let server of servers | filter:filterStatus:'status'"
          [ngClass]="getStatusClasses(server)">
          <span
            class="badge">
            {{ server.status }}
          </span>
          <strong>{{ server.name }}</strong> | {{ server.instanceType }} | {{ server.started }}
        </li>
      </ul>
    </div>
  </div>
</div>

2-way-bind할 수 있도록 [(ngModel)]을 씁니다. 당연히 ts.file에 filterStatus라는 변수가 선언되어야 겠죠?

 

*ngFor뒤쪽에 pipe를 추가합니다 (filter)

*ngFor="let server of servers | filter:filterStatus:'status'"

filter (pipeName)

:filterStatus (첫번째 파이프의 파라미터) // 여기에선 사용자가 입력한 값 입니다.

:'status' 대상값이 됩니다. 

아래와 같은 Object에서 name에 해당하는 status를 대상으로 예제를 진행 합니다.

 

{
  instanceType: 'medium',
  name: 'Production Server',
  status: 'stable',
  started: new Date(15, 1, 2017)
},

위와 같은 실행 결과가 나오게 됩니다.

 


@Pipe({
  name: 'filter',

  pure : false // 페이지의 값이 변경될 때마다 계산을 합니다. (매우 퍼포먼스가 좋지 않기 때문에 사용에 주의하시기 바랍니다.)
})

 

async와 같은 경우, 데이터가 변할 지라도, pure:false가 적용되지 않습니다. 그럴땐.

{{appStatus | async}}

server request와 같은 async 자료는 아래와 같이 표기를 해주면 됩니다. 

반응형

'프로그래밍 > Angular' 카테고리의 다른 글

Authentication & Route Protection  (0) 2019.09.06
Http  (0) 2019.08.20
Form - 복습 및 보충 내용  (0) 2019.08.14
FormHandling - Reavice  (0) 2019.07.02
Handling Form - Template Driven  (0) 2019.07.02
반응형

Template Driven

 

-HTML

form태그 (ngSubmit)='onAddItem(f)" #f="ngForm"

앵귤러 submit과, 로컬레퍼런스 f를 선언합니다.

Validation 속성 : pattern 정규식, required가 있습니다.

<!-- child HTML for form -->

<form (ngSubmit)="onAddItem(f)" #f="ngForm">
  <div class="row">
    <div class="col-sm-2 form-group">
    <label for="amount">Amount</label>
    <input
    type="number"
    id="amount"
    class="form-control"
    name="amount"
    ngModel
    required
    pattern="^[1-9]+[0-9]*$"
    >
    </div>
  </div>
  <div class="row">
    <div class="col-xs-12">
      <button class="btn btn-success" type="submit" [disabled]="!f.valid">Add</button>
      <button class="btn btn-danger" type="button">Delete</button>
      <button class="btn btn-primary" type="button">Clear</button>
    </div>
  </div>
</form>

-TS

// child TS

onAddItem(form: NgForm) {
  const value = form.value;
  const newIngredient = new Ingredient(value.amount);
  this.slService.addIngredient(newIngredient);
}

NgForm을 form.value로 값에 접근할 수 있습니다.

 

아이템 값 불러오기 - Index이용.

 

1. Array에서 인덱스 값을 가져옴.

2. service를 이용하여 인덱스 값을 넘김.

3. subscribe로 child에 값을 받음.

 

// service.ts

startedEditng = new Subject<number>();

서비스에 subscribe할 수 있도록 Subject를 만들고, index는 number이기 때문에 제네릭 타입을 number로 지정.

 

-HTML

<!-- parent HTML -->

<ul class="list-group">
  <a
    class="list-group-item"
    style="cursor: pointer"
    *ngFor="let ingredient of ingredients; let i = index"
    (click)="onEditItem(i)"
    >
    {{ ingredient.name }} ({{ ingredient.amount }})
  </a>
</ul>

*ngFor에 let i = index로 index를 지정함.

(click)이벤트를 이용하여 index값을 ts로 넘김.

// parent TS

onEditItem(index: number) {
  this.slService.startedEditng.next(index);
}

child의 ngOnInit()에서 subscribe로 해당 HTML에 값을 입력합니다.

// child TS

@ViewChild('f') slForm: NgForm; // 랜더링에 맞는 view를 가져오기 위함.
  subscription: Subscription;
  editedItemIndex: number;
  editedItem: Ingredient;
//  editMode = false;

  constructor(private slService: ShoppingListService) { }

  ngOnInit() {
    this.subscription = this.slService.startedEditng
    .subscribe(
      (index: number) => {
//      	this.editMode = true;
        this.editedItemIndex = index;
        this.editedItem = this.slService.getIngredient(index);
        this.slForm.setValue({
        name: this.editedItem.name,
        amount: this.editedItem.amount
        })
      }
    );
  }
  
  ngOnDestroy() {
    this.subscription.unsubscribe();
  }

Subscription을 만들어 둡니다. (destroy를 위해...)

 

1. startedEditing을 subscribe합니다: 해당 parameter였던 index를 이용하여 값을 설정합니다.

2. index를 이용하여 service에서 Array[index]의 값을 받아옵니다.

ViewChild로 되어있는 NgForm에 SetValue()를 이용하여 값을 설정해 줍니다.

 

어레이 아이템 삭제하기 - view단에서 실수 요소 차단하기

 

아이템 값 불러오기 - Index이용. 에서 editedItemIndex와 같은 변수를 매소드의 const로 쓰지 않아서 이상하다고 생각하셨죠?

 

주석으로 된 editMode를 해제하고 아래와 같은 HTML을 생성합니다.

<button 
  class="btn btn-danger" 
  type="button"
  (click)="onDelete()"
  *ngIf="editMode"
  >Delete</button>

ngIf로, editMode일 때만 활성화 하도록 합니다.

onDelete() {
  this.slService.deleteIngredient(this.editedItemIndex);
  this.onClear();
}

그렇게 되면, TS파일 내부에서 editMode를 따로 확인할 필요 없으면서, 클라이언트의 실수를 줄일 수 있습니다.

물론 코딩하는 스타일마다 다르지만 지금은 Template Driven입니다. 매우 유용한 스킬이라 생각됩니다.

 


ReactiveFormsModule

 

HTML에 

FormGorup 바인딩 - [formGroup]="formName"

<form [formGroup]="recipeForm" (ngSubmit)="onSubmit()"> ... </form>

FormControl 바인딩 - formControlName 속성

<input 
  type="text"
  id="name"
  formControlName="name"
  class="form-control">

 

버튼을 눌렀을 때, 중복이 submit이 될 때 - type="button"

<button 
  type="button"
  class="btn btn-success"
  (click)="onAddIngredient()"
  >Add Ingredient</button>

Fixing a Bug

form array의 controls에 접속하는 코드에서 문제가 발생할 수 있습니다:

*ngFor="let ingredientCtrl of recipeForm.get('ingredients').controls; let i = index"

이 코드는 Angular 최신버전에서 실패 할겁니다.

.ts 파일에서 매소드를 만들어 controls를 얻을 수 있습니다.

getControls() {
	return (<FormArray>this.recipeForm.get('ingredients')).controls;
}

템플릿에서는 이렇게 작성해 주셔야 합니다:

*ngFor="let ingredientCtrl of getControls(); let i = index"

대체방안으로, getter를 사용하는 캐스팅 구문을 사용할 수 있습니다:

get controls() {
	return (this.recipeForm.get('ingredients') as FormArray).controls;
}

그리고 템플릿은 이렇게 변경해주세요:

*ngFor="let ingredientCtrl of controls; let i = index"

image 미리보기 기능

<div class="row">
  <div class="col-xs-12">
    <div class="form-group">
      <label for="imagePath">Image URL</label>
      <input 
        type="text"
        id="imagePath"
        formControlName="imagePath"
        class="form-control"
        #imagePath>
    </div>
  </div>
</div>
<div class="row">
  <div class="col-xs-12">
	<img [src]="imagePath.value" class="img-responsive">
  </div>
</div>

로컬 레퍼런스 #imagePath

[src]="imagePath.value" 이렇게 사용합니다.


FromArray의 모든 아이템 삭제하기

 

Angular 8에서, 모든 아이템을 제거하는 새로운 방식이 있습니다.

(<FormArray>this.recipeForm.get('ingredients')).clear();

clear()매쏘드는 FormControls(혹은 FormGorups)에 등록된 모든 곳을 돌면서 자동적으로 제거해 줍니다.

removeAt()으로 일일이 루프를 돌면서 할 필요 없습니다.

반응형

'프로그래밍 > Angular' 카테고리의 다른 글

Http  (0) 2019.08.20
Pipe  (0) 2019.08.15
FormHandling - Reavice  (0) 2019.07.02
Handling Form - Template Driven  (0) 2019.07.02
Observable & Subject  (0) 2019.06.26
반응형

qpp.module.ts 에서 imports에 - ReactiveFormsModule추가하기

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    ReactiveFormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

 

 

이미지 처럼 3개의 필드가 있는 모양을 TypeScript를 이용하여 만들것입니다.


FormGroup 생성하기.

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  genders = ['male', 'female'];
  signupForm: FormGroup;

  ngOnInit() {
    // initialize 
    this.signupForm = new FormGroup({
      'username': new FormControl(null),
      'email': new FormControl(null),
      'gender': new FormControl('male')
    });
  }
}

FormGroup에 각 항목 마다 key-value pair로 값을 지정해 줍니다.

key = 항목 명

value = FormControl객체


HTML Input과 바인딩(syncronize)하기

<form>
  <div class="form-group">
    <label for="username">Username</label>
    <input
      type="text"
      id="username"
      class="form-control">
    </div>
    <div class="form-group">
    <label for="email">email</label>
    <input
      type="text"
      id="email"
      class="form-control">
    </div>
    <div class="radio" *ngFor="let gender of genders">
    <label>
      <input
        type="radio"
        [value]="gender">{{ gender }}
    </label>
  </div>
  <button class="btn btn-primary" type="submit">Submit</button>
</form>

 

HTML에 directive를 오버라이드 합니다. - <form>태그에 formGroup 연결

<form [formGroup]="signupForm">

위에서 선언한 signForm을 formGorup속성으로 넣어줍니다.

 

각 항목마다 formControlName을 사용하여, Angular에게 내가 사용하는 typeScript의 input 이름을 알려줍니다.

<input
  type="text"
  id="username"
  class="form-control"
  formControlName="username">

물론 속성 값으로   [formControlName]="'username'" 를 사용해도 됩니다. string을 전달해야 하기 떄문에 ""안에 ''를 넣어야 합니다.

 

브라우저로 들어가 제대로 매칭이 되었는지 확인해 봅니다.

그림과 같이 ng-untouched와 같은 클래스가 있습니다.

개발 도구를 이용하면 ng로 시작하는 클래스가 있음으로, type스크립트가 작동한다는 것을 알 수 있습니다.


Validation 추가하기 - formControl 생성시 2번째 argu.에 추가합니다.

this.signupForm = new FormGroup({ //first: default Value, second: validation
      'username': new FormControl(null, Validators.required),
      'email': new FormControl(null, [Validators.required, Validators.email]),
      'gender': new FormControl('male')
    });

[]를 이용하여 복수의 validation을 추가합니다.

 

HTML에 안내 문 추가 하기

<span 
  *ngIf="!signupForm.get('username').valid && signupForm.get('username').touched"
  >Please enter a valid username!</span>

signupForm 객체에 들어가 valid, touched와 같은 validation 값에 접근할 수 있습니다.

 

구릅핑 하기 - Form Group

ts파일에 userData로 그릅핑 하겠습니다. - nested Group

'userData': new FormGroup({
  'username': new FormControl(null, Validators.required),
  'email': new FormControl(null, [Validators.required, Validators.email])
}),      

html도 같이 변경해 주어야 합니다.

<div formGroupName="userData">
	... <!-- username과 email <input> -->
</div>

validation도 역시 다르게 접근해야 합니다.

<span *ngIf="!signupForm.get('userData.username').valid && signupForm.get('userData.username').touched">...</span>

폼 컨트롤 어레이 추가하기 - FormArray

 

사용자가 원하는 만큼 text항목을 추가합니다. 

취미 리스트나 todo list에 사용할 수 있습니다.

 

ts에서 formGroup에 FormArray를 추가합니다.

this.signupForm = new FormGroup({
  'userData': new FormGroup({
    'username': new FormControl(null, Validators.required),
    'email': new FormControl(null, [Validators.required, Validators.email])
  }),      
  'gender': new FormControl('male'),
  'hobbies': new FormArray([])
});

hobbies라는 FormArray를 빈 상태로 추가합니다. - 취미가 없는 사람이 있을 수 있기 때문입니다.

<div formArrayName="hobbies">
  <h4>Youre Hobbies</h4>
    <button 
    class="btn btn-default"
    type="button"
    (click)="onAddHobby()">Add Hobby</button>
      <div 
      class="form-group"
      *ngFor="let hobbyContrl of signupForm.get('hobbies').controls; let i = index">
      <input type="text" class="form-control" [formControlName]="i">
  </div>
</div>

HTML에 formArrayName으로 바인딩 합니다.

onAddHobby라는 매쏘드로 사용자에게 반응합니다.

 

몇개의 취미가 있을지 모르기 때문에 ngFor을 이용하여 FormArray에 값을 추가합니다.

 

<Add Hobby 버튼>을 누를때 마다 onAddHobby()매쏘드가 작동하여 FormControl push합니다.

onAddHobby() {
  const control = new FormControl(null, Validators.required);
  (<FormArray>this.signupForm.get('hobbies')).push(control);
}

 

ngFor에 의하여 <input type="text"> 가 나타납니다.

생성과 동시에 formControlName의 매칭을 추가해야 하기 때문에, [formControlName]="i"를 사용합니다.

[]로 속성으로 만들어야 "i" index를 formControlName으로 줄 수 있기 때문입니다.


Custom Validators

특정 username을 사용하지 못하도록 합니다.

 

변수를 사용하여 지정합니다.

forbiddenUsernames = ['Chris', 'Anna'];

매쏘드를 만듭니다.

forbiddenNames(control: FormControl): {[s: string]: boolean} {
  if(this.forbiddenUsernames.indexOf(control.value) !== -1) return {'nameIsForbidden': true};
  return null;
}

foo(argu): {[s: string]:boolean} {} 

foo(argu)뒤에 오는 :{...}은 반환인자 입니다.

foo(): {} foo() 매쏘드는 구조체를 반환합니다.

key/value pair를 반환합니다.

key는 string이고, value는 boolean 입니다.


Indexable Types

...더보기

위 indexable Types 링크를 누르면 이전에 어떻게 interface를 사용할 수 있는지 나와있습니다.

a[10], ageMap["daniel"] 처럼 "index into"로 타입을 기술 할 수 있습니다.

Indexable(색인가능한) 타입은 객체에 색인할 수 있도록하는 인덱스 특징(index signature 이하 인덱스 특징)을 갖고 있으며, 인덱싱 할 때 객체와 상응하는 리턴 타입이 함께합니다. 아래 예제를 보겠습니다.

interface StringArray {
    [index: number]: string;
}

let myArray: StringArray;
myArray = ["Bob", "Fred"];

let myStr: string = myArray[0];

위쪽, StringArray 인터페이스는 하나의 인덱스 특징을 갖고 있습니다.
이 인덱스 서명은 StringArray가 number로 인덱싱 되어있음을 나타내고, string을 반환한다고 보여주고 있습니다.


forbiddenUsernames의 index를 돌면서 해당 FormControl의  값을 찾습니다.

존재하지 않으면 -1을 반환하는 indexOf를 이용하기떄문에 !== -1를 이용하여 true, false를 결정합니다.

 

forbiddenNames를 호출할 당시의 this는 우리가 생각하는 this가 아니므로 작동하지 않습니다.

'username': new FormControl(null, [Validators.required, this.forbiddenNames.bind(this)]),

validator를 추가할때 bind(this)를 넘겨주는 꼼수을 사용합니다.


Async Vaildator

HTTP 통신을 통해서 Valid 여부를 확인 할 수 있습니다.

 

서버의 응답을 처리할 때 사용하는 기능입니다. 

  static asyncInvalidProjectName(control: FormControl): Promise<any> | Observable<any> {
    const promise = new Promise((resolve, reject) => {
      setTimeout(() => {
        if (control.value === 'Testproject') {
          resolve({'invalidProjectName': true});
        } else {
          resolve(null);
        }
      }, 2000);
    })
    return promise;
  }

 

setTimeout()를 서버에서 응답으로 가정했습니다.

반응형

'프로그래밍 > Angular' 카테고리의 다른 글

Pipe  (0) 2019.08.15
Form - 복습 및 보충 내용  (0) 2019.08.14
Handling Form - Template Driven  (0) 2019.07.02
Observable & Subject  (0) 2019.06.26
Router 정리  (0) 2019.06.25
반응형

 

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';


import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

template driven 에서는 FormsModule을 사용합니다.

 

template driven에 들어가기 앞서 용어와 개념을 정리해보았습니다.

 

투 웨이 바인딩(2-way-binding) 이해하기

 

What are the difference between one way binding and two way binding?

Answer (1 of 5): A two-way binding involves an implementation of a single “watcher set” for both the model and the component, and updating either one when the other changes. Angular 1 uses this method by default using ng-Model. Watchers are created to hand

www.quora.com

 

이벤트 바인딩

<button (click) = “TheEventMethod()”>Save</button>

DOM에서 JavaScript function을 작동 시키도록 event를 알립니다.

 

속성(프로퍼티) 바인딩

<p[size] = “fontSizePx”></p>

JavaSciprt로 설정된 fontSizePx라는 변수값을 가져옵니다.

 

2-way-바인딩

<input type='text' placeholder= "Enter stock symbol" [(ngModel)] = "lastStockSymbol">

이벤트 바인딩과 속성 바인딩을 동시에 사용하는 것이 2-way-바인딩 입니다.

클릭 이벤트 function을 상징하는 ()와 속성 바인딩 []두개가 동시에 쓰인것이 보이죠?

<input type='text' placeholder= "Enter stock symbol" [value]="lastStockSymbol" (input) = "lasStockSymbol=$event.target.value">
<br>The value of lastStockSymbol is {{lastStockSymbol}}

service를 이용하여 DOM을 update함으로써, 2-way-binding을 구현할 수 있습니다.

 


#foo 로컬 레퍼런스

 

1. regist - input input태그를 이용하여 Angular에게 알리기를 실행해 봅니다..

 

input 태그에 directive를 추가합니다. ngModel :2-way-binding이 됩니다.

html directive를 추가해줍니다

<form>
  <div id="user-data">
    <div class="form-group">
      <label for="username">Username</label>
      <input 
        type="text"
        id="username" 
        class="form-control"
        ngModel
        name="username">
    </div>
  </div>
</form>

submit할 수 있도록 만들어 보겠습니다.

 

form태그 안에 submit 버튼을 만들어 type="submit"을 만들게 클릭하게 한다면, HTML의 defalut로 작동하는 call이 trigger되어 request를 보내기 때문에, form태그에 (ngSubmit)="onSubmit()"을 추가합니다.

<form (ngSubmit)="onSubmit()">...</form>

ts

onSubmit() {
  console.log("submitted!")
}

또한 form의 reference를 받으려면 #f를 추가하면 됩니다.

 

HTML

<form (ngSubmit)="onSubmit(f)" #f="ngForm">...</form>

#f 는 local reference입니다.

Angular가 자동적으로 Javascript 객체를 생성해줍니다.

 

typescript

// <form (ngSubmit)="onSubmit(f)" #f></form>
onSubmit(form: ElementRef) {
  console.log(form);
}

// <form (ngSubmit)="onSubmit(f)" #f="ngForm"></form>
onSubmit(form: NgForm) {
  console.log(form);
}

form을 보면 아래와 같이 접근할 수 있습니다.

NgForm.value={

  key: value

}

 

2-way-바인딩을 조금더 편하게 사용하기 위해 @ViewChild를 이용합니다.

import { Component, ViewChild } from '@angular/core';
import { NgForm } from '@angular/forms';

export class AppComponent {
  @ViewChild('f') singupForms: NgForm;
  
  onSubmit() {
    console.log(this.singupForms);
  }
}

ViewChild는 로컬 레퍼런스와 element를 TypeScript 코드로 변경 하기에 유용하기 때문입니다.


Form State

 

form태그 안쪽에 있는 input에 다음과 같은 directive를 사용할 수 있습니다.

또한  required, eail과 같은 vaild설정을 할 수 있습니다.

 

local reference 사용하기.

 

<input 
  type="email" 
  id="email" 
  class="form-control"
  ngModel
  required
  email
  #email="ngModel">
<span class="help-block" *ngIf="!email.vaild && email.touched">Please enter for vaild</span>

email input태그를 선택 후 아무것도 입력하지 않고 벗어나면, please enter for vaild라는 문구가 나오게 됩니다.

#email="ngModel" 을 사용함으로써 local reference로 HTML에서 email을 사용할 수 있습니다.

 

이와 마찬가지로,

submit 버튼에 disabled를 추가할 수 있습니다.

<button 
  class="btn btn-primary" 
  type="submit"
  [disabled]="!f.vaild">
Submit</button>

CSS 스타일을 관리합니다.

input.ng-invaild { ... }

Angular가 class를 추가하여 form에 위와 같은 상태를 갖고 있습니다.

 


default값 설정하기! - one-way-binding

기본값을 지정할 수 있습니다. [ngModel]

 

HTML

<select 
  id="secret" 
  class="form-control"
  [ngModel]="defaultQuestion"
  name="secret">
  <option value="pet">Your first Pet?</option>
  <option value="teacher">Your first teacher?</option>
</select>

ts

export class AppComponent {

  defaultQuestion = "pet";

}

그림과 같이 pet option태그가 설정되어 있습니다.

 


2-way-바인딩

 

<div class="form-group">
  <textarea 
    name="questionAnswer" 
    rows="3"
    [(ngModel)]="answer"></textarea>
</div>
<p>Your reply: {{answer}}</p>
export class AppComponent {
  answer = '';
}

 

No-바인딩 : Angular에게 input이 동작만 전달합니다.

1-way-바인딩 : 기본값(default value)을 이용하여 값에 나타납니다.

2-way-바인딩 : 사용자 입력과 동시에 value와 화면에 나타납니다.

 


그룹핑 하기 - ngMoudelGroup

<form (ngSubmit)="onSubmit()" #f="ngForm">
  <div
    id="user-data"
    ngModelGroup="userData"
    #userData="ngModelGroup">
      <div class="form-group">
        <label for="username">Username</label>
        <input
        type="text"
        id="username"
        ngModel
      </div>
      <div class="form-group">
        <label for="email">Mail</label>
        <input
        type="email"
        id="email"
        class="form-control"
        ngModel
        name="email">
      </div>
  </div>
</form>

ngForm.value.userData 가 그룹으로 묶여 있음을 확인할 수 있습니다.


2-way-바인딩 다른 방법

 

ngForm.setValue();

this.signupForm.setValue({
  userData: {
    username: suggestedName,
    email: ''
  },
  secret: 'pet',
  questionAnswer: '',
  gender: 'male'
});

모든 값이 덮어쓰기가 됩니다.

 

ngForm.form.patchValue();

this.signupForm.form.patchValue({
  userData: {
    username: suggestedName
  }
});

한개의 값만 변경합니다.


값 빼내기

 

user = {
    username: '',
    email: '',
    secretQuestion: '',
    answer: '',
    gender: ''
  };

onSubmit() {
  this.user.username = this.signupForm.value.userData.username;
  this.user.email = this.signupForm.value.userData.email;
  this.user.secretQuestion = this.signupForm.value.secret;
  this.user.answer = this.signupForm.value.questionAnswer;
  this.user.gender = this.signupForm.value.gender;

  this.signupForm.reset();
}

위와 같이 객체를 만들어 사용합니다.

 

반응형

'프로그래밍 > Angular' 카테고리의 다른 글

Form - 복습 및 보충 내용  (0) 2019.08.14
FormHandling - Reavice  (0) 2019.07.02
Observable & Subject  (0) 2019.06.26
Router 정리  (0) 2019.06.25
Routes  (0) 2019.06.17

+ Recent posts