참고: Augular Essential
참고: 앵귤러 마스터북
angular-cli
https://www.npmjs.com/package/@angular/cli
여기서 버전 체크
angular cli 업데이트
https://poiemaweb.com/angular-cli
nvm으로 node를 구성하고 있어요~ angular-cli도 써야해요.
우선 nvm은 node를 여러개 설치할 수 있어요. 그래서 버전마다 글로벌로 설치한 라이브러리가 다를수도 있으니, 각 버전마다 npm i -g @angular/cli를 해주는게 맞아요.
declaration: 모듈에 소속될 구성요소 ( 컴포넌트, 파이프, 디렉티브 ) 를 리스트로 지정
providers: 주입 가능한 객체, 서비스의 리스트를 선언함.
imports: 의존 관계 ( 모듈내에 선언된 컴포넌트, 서비스) 에서 사용될 Angular 라이브러리 모듈, 라우팅 모듈, 써드 파티 모듈을 선언함.
1.가져오려는 모듈의 export에 컴포넌트 선언해주고
2.가져오는 쪽에서는 모듈을 import 해주면 됌.
부모가 자식한테 상태 전달할때는 @Input 데코레이터 사용했음.
//부모 컴포넌트
<child (myEvent)="myEventHandler($event)"></child>
...
myEventHandler(state: string){
//do sth with state
}
//자식 컴포넌트
<button (click)="handleClick(state)"></button>
...
@Output() select: EventEmitter<{data?: CodeInfoItem, e: Event}> = new EventEmitter();
handleClick(state){
this.myEvent.emit(state);
}
자식 컴포넌트 ---> 부모 컴포넌트
myEvent, state가 함께 전달.
부모컴포넌트랑 자식컴포넌트 모두 같은 상태객체를 참조할거에요. 그러면 한쪽에서 변경하면 모두에게 반영이 되는 구조인데, 상태 정보의 변화를 예측하기가 어렵다는 얘기에요. 따라서 모든 Frontend Framework는 상태 정보를 저장할 수 있는 stateful 컴포넌트 (smart, container) 와 상태 정보를 참조하여 화면에 출력할 뿐 직접 변경하지 않는 stateless 컴포넌트(dumb, content)로 구분할 필요가 있어요!
props로 전달하면 부모 자식이 동일한 데이터를 보기 때문에 데이터가 무결성하게 유지할 수 있으니 이렇게 쓰지 않을까?
앵귤러 에센셜 p.277
이벤트 바인딩 : 뷰의 상태 변화 ( 버튼 클릭, 체크박스 체크, input에 텍스트 입력 등 )에 의해 이벤트가 발생하면 이벤트 핸들러를 호출하는 것
컴포넌트 클래스와 템플릿 연동
예를 들어 앵귤러 방식으로 button을 만들고 그 이벤트를 달아서
컴포넌트 클래스 ( 상태) ----데이터바인딩 ---- 템플릿
app.component.ts
@Component({
selector: 'app-root',
template: `
<input type="text" [value]="name" (input)="setName($event)">
<button (click)="clearName()">clear</button>
`
})
export class AppComponent{
name: string;
setName(event) {
console.log(event);
this.name = event.target.value;
}
clearName() {
this.name = '';
}
}
이벤트 흐름 :
텍스트 입력에 의해 input 이벤트 발생하면 이벤트 바인딩을 통하여 이벤트 핸들러 setName을 호출한다.
$event를 통해 DOM 이벤트 객체를 이벤트 핸들러에 전달할 수 있음.
angular는 표준 DOM이벤트를 사용하기 때문에 $event를 통해 브라우저의 Event객체의 프로퍼티나 메서드를 자유롭게 접근할 수 있음.
input 태그의 경우는 input event가
select 태그에는 change event가 발생합니다.
이 이벤트들은 HTML DOM Events의 이벤트입니다.
앵귤러는 표준 DOM Event를 사용하기 때문입니다.
EventEmitter 는 옵저버 패턴의 구현을 지원하는 객체
요거 뭐냐면
부모 컴포넌트에서
@ViewChild('childComp') myChild: ChildComponent;
이렇게 사용하면, 자식 컴포넌트의 프로퍼티 직접 변경하고, 자식 컴포넌트의 메소드를 직접 실행할 수도 있음.
this.myChild
가 레퍼런스 변수가 되는거임.
템플릿에서는 참조변수로 쓸 수 있음.
이걸 어따 쓰나.. 네이티브 DOM이나 자식 컴포넌트에 직접 참조할때 쓰인다는데 아직은 모르겠음.
<childcomp #childComp></childcomp>
특정 요소를 기반으로 스크롤 위치를 탐지할때 사용했어요!
@ViewChildren은 myChild가 리스트형태이다.
쉽게 얘기해서 입력필드
입력필드에 관련된 데이터를 갖고있는 객체
앵귤러를 처음 접해서 이게 뭔지 모름 그래서 정리
이전에는
<input type="text" name="product-name" [(ngModel)]="product.name" />
이렇게 썼었고,
NgModel 지시자는 FormsModule에 포함되어 있기 때문에 NgModel로 양방향 바인딩을 사용하려면 FormsModule을 우리의 모듈에 임포트 해야합니다.
앵귤러 CLI로 프로젝트 생성한 경우 디폴트로 AppModule에 임포트 되어있음.
앵귤러에서는 폼의 모든 요소를 추상화하여 모델클래스?와 지시자의 조합?으로 구성하여 제공합니다.
폼을 다루는 요소의 핵심 FormControl 클래스?
FormControl은 폼에서 사용자 입력을 받는 모든 요소에 일대일로 대응하는 모델 클래스입니다. 앵귤러는 우리가 작성한 템플릿의 폼을 파싱하여 모든 입력 요소마다 FormControl 클래스?를 하나씩 생성합니다.
Abstract
-FormControl
-FormGroup
-FormArray
사용자가 입력한 값의 상태 및 변경 이벤트의 기본적인 구현부는 AbstractControl에 있습니다.
입력 요소의 실제 값이 선언된 곳은 AbstractControl입니다.
AbstractControl의 _value속성이 선언되어 있으며, 입력 요소의 실제 값은 AbstractControl에 선언된 _value에 저장됩니다.
사용자의 입력을 받는 요소가 FormControl 객체가 되고, 이를 모아서 FormGroup으로 묶을 수 있습니다.
반응형 폼은 템플릿 주도 폼과 달리 폼을 구성하는 구현 코드를 템플릿이 아닌 컴포넌트에 작성합니다.
컴포넌트 클래스에서 폼에 대응하는 모델?을 기반으로 폼을 구성하기 때문에 모델 주도 폼이라고 부르기도 합니다. 이미 앞에서 살펴보았던 템플릿 주도 폼에서 템플릿에 NgModel, NgForm과 같은 지시자로 폼을 작성하였습니다. 내부적으로 앵귤러는 NgModel에 대응하여 Form 객체를 만들고, NgForm에 대응하여 FormGroup 객체를 생성했음.
반응형 폼 방식은 템플릿에 선언된 지시자를 기준으로 내부적으로 알아서 FormControl만들고, FormGroup을 구성하였던 것을 컴포넌트에서 우리가 직접 작성하는 것뿐
클래스명 | 선택자 이름 | |
---|---|---|
FormControlDirective | formControl | FormControl 객체 바인딩 |
FormGroupDirective | formGroup | FormControl 객체 바인딩 |
FormControlName | formControlName | FormGroup에 포함된 FormControl 객체 바인딩 |
FormGroup, FormControl등 객체를 DOM의 폼 요소에 바인딩 할때 사용.
Directive는 독립적인 객체를 DOM에 바인딩할때 사용.
템플릿에 폼과 바인딩
FormGroup지시자를 사용하여 프로퍼티 바인딩으로 컴포넌트의 FormGroup을 하나 생성하였다고 가정..
FormControlName 지시자는 컴포넌트에서 선언한 FormGroup의 각 FormControl을 템플릿의 입력 요소와 바인딩할 때 사용합니다.
예를 들어, FormBuilder를 사용하여 컴포넌트에서 name키로 하는 FormControl을 포함하는 FormGroup이 있다면 formControlName 지시자에 문자열로 전달하면 됩니다.
//...component.html
<form [formGroup]="form" novalidate>
<select class="..." formControlName="somename">
<option value=".." >...</option>
...
</select>
</form>
...
//...component.ts
this.myFormGroup = this.formBuilder.group({
'somename' : [''], ...
정리: formGroup안에 있는 formControl의 상태가 Formbuilder로 생성한 group으로 생성한 객체에 상태들이 포함되어있고, 템플릿에 연결하기 위해서는 객체의 key를 formControlName 지시자의 바인딩에 넣어주면 this.myFormGroup은 템플릿에서 설정한 form값의 상태를 갖게 된다.
부모컴포넌트에서 자식컴포넌트의 입력프로퍼티 (@Input 데코레이터로 장식된 프로퍼티)로 바인딩한 값이 초기화 또는 변경되었을 때 실행된다.
기본 자료형의 값이 재할당되었을 때와 객체의 참조가 변경되었을 때만 반응한다.
모든 퍼로퍼티의 초기화가 완료된 시점에 한번 호출
constructor는 앵귤러랑 관계없어서, 프로퍼티 초기화는 ngOnIt에서 수행하는걸 권장
ngOnInit 이후, 컴포넌트 또는 디렉티브의 모든 상태 변화가 발생할 때마다 호출됌.
레퍼런스: https://angular.io/guide/lifecycle-hooks
import {Injectable} from '@angular/core';
import {FormGroup} from '@angular/forms';
@Injectable()
export class PowerPlantService {
constructor(private pmService: ProjectManagementService) {
}
}
서비스는 의존성 주입이 가능한 클래스.
@Injectable 데코레이터는 아래에 정의된 클래스가 의존성 주입이 가능ㅇ한 injectable 클래스임을 나타낸다.
싱글턴 전역 서비스 주입
@Injectable 메타데이터 객체의 providedIn 프로퍼티는 Angular6에서 새롭게 도입된 것으로 프로퍼티값으로 'root'를 설정하면 루트 인젝터에 서비스를 제공하도록 지시하여 애플리케이션 모든 구성요소에서 싱글턴 전역 서비스를 주입할 수 있도록한다.
@Injectable({providedIn: root})
angular는 의존성 주입 요청에 의해 주입되어야 할 인스턴스가 있다면 이 인스턴스의 주입을 인젝터에 요청함.
의존성 주입 요청에 의해 프로바이더를 검색하고 인스턴스를 생성하여 의존성 인스턴스를 주입한다.
인젝터는 인스턴스 풀인 컨테이너를 관리함. 있으면 있는거 주고, 없으면 생성해서 컨테이너에서 관리
이때 인스턴스를 구분짓기 위한 키로 providers의 provide 프로퍼티를 사용함.
보통은 앱이 있고, 모듈이 있고, 서비스가 있음.
module에 @NgModule의 providers 프로퍼티의 타입이 배열이다. 여기에 서비스(@Injectable데코레이터를 사용한)컴포넌트를 추가하면 , provide 키를 안써도 된다.
인젝터가 인스턴스를 검색할때 이 토큰을 키로 인스턴스를 검색한다.
... 나머지는 기억안날때 또 추가할것
@Inject 데코레이터는 클래스 형태가 아닌 interface나 primitive value를
클래스가 아닌 의존성 토큰, 객체, 문자열, 함수등을 위한 토큰을 주입받기 위해 사용
TypeScript는 트랜스파일링되어 자바스크립트로 변환된다. 하지만 자바스크립트는 인터페이스를 지원하지 않으므로 변환된 자바스크립트 파일에는 인터페이스가 사라지게 된다. 따라서 Angular가 런타임에 찾을 수 있는 타입 정보가 없기 때문에 인터페이스를 토큰으로 등록하면 에러가 발생한다. - > 인젝션 토큰이용
@NgModule({
declarations: [
// 여기에 사용할 컴포넌트 선언
],
imports: [// 여기에 쓰일 모듈선언],
providers: [AuthService],
exports: []
})
export class PowerPlantModule {}
위와 아래는 같은 의미이다.
@NgModule({
declarations: [
// 여기에 사용할 컴포넌트 선언
],
imports: [// 여기에 쓰일 모듈선언],
providers: [{
provide: AuthService, // 의존성 인스턴스의 타입 ( 토큰)
useClass: AuthService // 의존성 인스턴슬르 생성할 클래스
}],
exports: []
})
export class PowerPlantModule {}
정리: 의존성을 주입하는건 동작하는 객체일것이다.
그런데 그 주입이 클래스일수도, 팩토리 함수일 수도 있음.
클래스일 경우에는 useClass를 쓰고, 함수일 경우 useFactory를 사용.
팩토리함수? : 함수가 객체를 반환할때 팩토리함수라함.
https://ui.toast.com/weekly-pick/ko_20160905/
https://poiemaweb.com/angular-component-data-binding
https://han41858.tistory.com/42
프로퍼티 바인딩: property="{{expression}} [property]="expression"
,
이벤트 바인딩:(click)="onclick() (target)="statement"
양방향 바인딩:[(target)]="expression"
양방향 데이터 바인딩은 뷰와 컴포넌트 클래스의 상태변화를 상호 반영하는 것을 말함.
즉, 뷰의 상태가 변하는 컴포넌트의 클래스의 상태가 변하고, vice versa
NgModel 디렉티브는 앞에서 살펴본 바와 같이 폼 컨트롤 요소의 값과 유효성 검증 상태를 관리하는 FormControl 인스턴스를 생성한다고 하였다. 그런데 NgModel 디렉티브에 프로퍼티 바인딩을 사용하여 상태 프로퍼티를 바인딩하는 경우, 폼 컨트롤 요소의 상태 값을 업데이트할 수 있음.
하지만 엄밀히 말하면 양방향 바인딩은 존재하지 않습니다.
내부적으로 프로퍼티 바인딩과 이벤트 바인딩을 쓰죠.
다음과 같은 템플릿이 있다고 가정합니다.
<h3>My Budget: ₩{{budget}}</h3>
<input type="text" [(ngModel)]="budget">
<button
(click)="onAddIncome()">Add Income</button>
[(ngModel)]은 내부적으로 아래와 같이 동작합니다.
[ngModel]="budget", (ngModelChange)="budget =$event"
budget이 input이 변할때마다 값이 변하죠.
즉 템플릿 <---> 컴포넌트 양방향으로 데이터가 바인딩 되고있습니다.
양방향 바인딩은 개발 편의를 위한 syntax라고 봐야겠죠.
이벤트 바인딩과 프로퍼티 바인딩의 축약 버전입니다.
출처: https://dschci.tistory.com/84?category=978065 [IT 회사에서 즐겁게 지내기!]
양방향 바인딩은 반드시 NgModel 디렉티브를 사용하여야 하는 것은 아니며 커스텀 양방향 데이터 바인딩도 작성할 수 있습니다. 커스텀 양방향 바인딩의 간단한 예제도 작성
ERROR Error: No value accessor for form control with unspecified name attribute on switch
<input class="form-control" type="date" [(ngModel)]="data.payDate" ngDefaultControl />
ngDefaultControl 프로퍼티를 붙여줍니다.
https://stackoverflow.com/questions/46422007/error-error-no-value-accessor-for-form-control-with-unspecified-name-attribute/54755671
https://stackoverflow.com/questions/46120731/angular-ngmodel-vs-ngmodel-vs-ngmodel
[(ngModel)]="expression"
is unwrapped by the compiler into
[ngModel]="expression" (ngModelChange)="expression=$event"
formControl에 쓰이며 값이 변경될때마다 ngModelChange에 바인딩해둔 메서드가 호출되며 인자로는 update된 값이 옴.
https://stackblitz.com/edit/angular-ivy-tk3fsy?file=src%2Fapp%2Fapp.component.ts
빌트인 디렉티브라고도 함.
ngIf, ngSwitch, ngStyle, ngClass, ngFor
지시자는 동적 데이터 출력할 뿐 사용자 인터랙션을 생성하지 못함.
ngClass
[ngClass]="{클래스1명: 식1(값), 클래스2:명 식2, ...}" // 식이 true면 클래스 적용
[ngClass]="값 ? 'class1': 'class2'" // 값이 true면 class1 적용, false면 class2적용
ngStyle
[ngStyle]="{'스타일': 값 ? '스타일값1': '스타일값2'}" // 값이 true면 스타일값1 적용, false면 스타일값2 적용
[ngStyle]=" 식 ? {display: 'block'} : {display: 'none'}"
[ngStyle]="{top: item?.top + 'px', left: item?.left + 'px'}"
앵귤러는 아니지만 앵귤러에서 공식으로 사용하는 상태관리? 비동기처리? 를 담당하는 라이브러리
출처:
https://feel5ny.github.io/2018/03/25/angular_observable/
옵저버블은 앵귤러 고유 기능 ES7 스펙으로 제안되어 있는 비동기 데이터를 처리하기 위한 표준입니다.
Angular 필수 패키지 Rxjs는 비동기 데이터 스트림을 처리하는 API를 제공하는 자바스크립트 라이브러리이다.
HTTP 요청은 비동기 ( 요청에 대한 응답을 대기하지 않고 다음 작업을 할 수 있다. )
비동기처리는 프로미스! -> 옵저버블
RxJS의 원리는 Observable은 일련의 데이터를 발생시키는 원천?
Ajax 통신결과, 웹 소켓, 사용자 이벤트 등 데이터를 만들어 내는 것은 무엇이든 Observable로 만들 수 있음.
Observable
.fromEvent(this.elInput.nativeElement, 'input')
.debounce(ev => ev.hasSomeValue ? timer(2000) : EMPTY)
.subscribe(event => this.onInput(event));
https://stackoverflow.com/questions/53044981/how-to-make-observable-debouncetime-conditional
뜬금포 이건 자바스크립트에 관련된 내용.
setTimeout을 사용하면 자바스크립트는 현재 실행 대기열이 모두 완료되어야 이 함수를 실행한다. 컴포넌트가 렌더링되고 함수가 실행되기 때문에 실행 스케줄을 렌더링 함수 이후에 하고싶으면 setTimeout 사용
이벤트 루프 태그해논거랑 관련있는 얘기
파이프는 template에서 사용할 메서드라고 보면 된다.
import { Pipe, PipeTransform } from '@angular/core';
import * as moment from 'moment';
/**
* FromNowPipe let's us convert a date into a human-readable relative-time
* such as "10 minutes ago".
*/
@Pipe({
name: 'fromNow'
})
export class FromNowPipe implements PipeTransform {
transform(value: any, args: Array<any>): string {
return moment(value).fromNow();
}
}
export const fromNowPipeInjectables: Array<any> = [
FromNowPipe
];
파이프를 컴포넌트에서 사용하고싶으면 해당 컴포넌트가 declarations된 곳에 pipe를 추가한다면
컴포넌트에서 pipe를 사용할 수 있음.
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import {
AppState,
default as reducer
} from './app.reducer';
import { AppComponent } from './app.component';
// 다른 컴포넌트들
import { FromNowPipe } from './pipes/from-now.pipe';
import {
AppStore,
appStoreProviders
} from './app.store';
@NgModule({
declarations: [
AppComponent,
// 다른 컴포넌트들
FromNowPipe
],
imports: [
BrowserModule,
FormsModule,
HttpModule
],
providers: [
appStoreProviders
],
bootstrap: [AppComponent]
})
export class AppModule { }
커스텀 파이프 만들고, module에도 선언해줬으면 다음과 같이 파이프를 사용해준다.
<p class="time">{{message.sender}} • {{message.sentAt | fromNow}}</p>
컴포넌트 내부 프로퍼티 값을 분기해서 scss를 변경할 수 있음.
[ngClass]="{'msg-sent': !incoming, 'msg-receive': incoming}">
incoming이 true면 msg-receive 클래스가 적용되고, false이면 msg-sent 가 적용된다.
ExpressionChangedAfterItHasBeenCheckedError
자식컴포넌트에서 셀렉트박스를 그린후에 부모컴포넌트에서 셀렉트 박스에 들어갈 데이터를 전달하는 상황에서
발생했음.
부모컴포넌트에서 afterViewInit 사이클에서 데이터를 자식 컴포넌트에서 전달할 데이터를 참조했으나,
ExpressionChangedAfterItHasBeenCheckedError 가 발생해서 부모컴포넌트에서 Docheck 사이클에서 데이터를 전달하니 문제없이 해결이 됩니다.
ERROR TypeError: Cannot create property 'validator' on string
<form formGroup="supportBusinessForm"> -> <form [formGroup]="supportBusinessForm">
radio 쓸때 name 프로퍼티랑, formControlName 이 값이 같아야돼요.
<input
class="ctmgLcalGovFundYn"
id="ctmgLcalGovFundYn1"
type="radio"
name="businessCategory"
formControlName="businessCategory"
value="주택지원사업"
/>
core.js:1449 ERROR Error: Uncaught (in promise): TypeError: undefined is not a function
TypeError: undefined is not a function
at Array.map (<anonymous>)
at webpackAsyncContext (eval at ./src/$$_lazy_route_resource lazy recursive (main.bundle.js:13), <anonymous>:69:34)
at SystemJsNgModuleLoader.loadAndCompile (core.js:6570)
at SystemJsNgModuleLoader.load (core.js:6554)
at RouterConfigLoader.loadModuleFactory (router.js:4595)
at RouterConfigLoader.load (router.js:4575)
at MergeMapSubscriber.eval [as project] (router.js:2061)
at MergeMapSubscriber._tryNext (mergeMap.js:128)
at MergeMapSubscriber._next (mergeMap.js:118)
at MergeMapSubscriber.Subscriber.next (Subscriber.js:95)
at resolvePromise (zone.js:814)
at resolvePromise (zone.js:771)
at eval (zone.js:873)
at ZoneDelegate.invokeTask (zone.js:421)
at Object.onInvokeTask (core.js:4751)
at ZoneDelegate.invokeTask (zone.js:420)
at Zone.runTask (zone.js:188)
at drainMicroTaskQueue (zone.js:595)
at ZoneTask.invokeTask [as invoke] (zone.js:500)
at invokeTask (zone.js:1540)
https://stackoverflow.com/questions/48947314/lazy-load-angular-5-error-lazy-route-resource-lazy-recursive
별거아님 그냥 다시 실행하면 됌
npx @angular/cli@<sepecificversion> new AngularProject
ng add <라이브러리 이름>
path는 src/app 이후 패스를 적어줘야함.
ng g s <path>
예를 들어, /src/app/route/login.service.ts 로 주고싶으면 /route/login만 주면 됌.
왜 이런것이냐? 앵귤러에게 루트 path는 angular.json에 설정되어있음.
https://angular.io/guide/workspace-config
{
...
"projects": {
"angular": {
"sourceRoot": "src",
"prefix": "app",
요런식으로 source 루트가 src이고, 뭘 만들든지 app이 접두어로 붙는 형태가 됌.
그래서 ng g s /route/login
이렇게 하면
src/app/route/login.service.ts에 service가 생성될것임.
결론만 말하자면 BFF 구조를 이용해서 cors를 피해버림.
https://www.positronx.io/handle-cors-in-angular-with-proxy-configuration/
angular 프로젝트 루트 디렉토리에서
proxy.conf.dev.ts
파일 생성해줍니다. 그리고 다음과 같은 코드를 넣어주고
'/api/*': {
target: 'https://o5x5jzoo7z.sse.codesandbox.io/graphql',
secure: false,
logLevel: 'debug',
},
package.json
에서 scripts를 다음과 같이 수정해줍니다.
"start": ng serve --proxy-config proxy.conf.dev.ts
배포때는? 공홈에서는 cors에러에 대해서는 서버에서 요청을 accept 해주는 설정을 하라네요. 사실상 cors는 서버가요청을 받아주면 그만이고, 클라이언트가 신경쓸 것이 아님.
https://angular.io/guide/deployment
tsconfig.json
{
"compilerOptions":{
"baseUrl": "src",
"paths": {
"@app/*": [
"/app/*"
]
}
}
}
https://www.typescriptlang.org/tsconfig#paths
to append the li tag dynamically
[기존 리스트] + [새로응답받은 리스트]
의 데이터 리스트에서 새로응답받은 리스트
만 렌더링 하고싶어요.
백엔드에서 개발해준 API를 통해 받은 데이터를 화면에 보여줄때 보통 리스트로 보여주잖아요.
그런데 더보기같은 경우, lazy-loading의 경우 기존에 리스트에 새로 응답받은 리스트를 추가하는건데,
새로 응답받은 리스트만 렌더링 해야하잖아요.
*ngFor trackBy
를 이용합니다.
sth.component.html
<ul class="similar_list">
<li *ngFor="let post of favoritePosts; trackBy: trackByFn">
<a routerLink="{{ post.link }}">
<div class="thumb">
<img src="{{ post.postThumbnailUrl }}" alt="" />
</div>
<div class="text">
<h3>
{{ post.postTitle }}
</h3>
<time>{{ post.postDate }}</time>
</div>
</a>
</li>
</ul>
sth.component.ts
trackByFn(index, item) {
return index; // or item.id
}
favoritePosts
의 형태는 다음과 같아요.
export class InsightPost {
id: number;
link: string;
postCtgr: string;
postDate: string;
postTime: string;
postTitle: string;
postThumbnailUrl: string;
postContent: string;
}
리스트에 존재하는 데이터의 유일한 프로퍼티?를 trackBy 의 구분자로 두는 방식입니다.
https://netbasal.com/angular-2-improve-performance-with-trackby-cc147b5104e5
외부에서 html을 스트링으로 받아다가 그걸 렌더링 시키는 법
예를 들어,
db에 컨텐츠라는 데이터가 저장되어있고, html 마크업이 그대로 문자열로 저장되어있다고 가정하면,
ajax를 통해 앱이 구동되는 중간에 html을 삽입해야하는 상황이 있다면
angular의 innerHTML를 프로퍼티를 이용해서 사용하면 됩니다.
이렇게 컴포넌트에서 데이터 가져다가
example.component.ts:
export class ExampleComponent implements OnInit {
async ngOnInit(){
...
variable = '<h1>Hello World</h1>';
// 데이터틑 ajax로 가져왔다면 이럴수 있겠죠.
variable = await this.exampleService.getHtml();
}
템플릿에는 다음과 같이 해주면
example.component.html:
<div class='inserthtml' [innerHTML]="variable"></div>
실제로 앱이 동작할때는 개발자 도구로 확인해보면, 다음과 같이 들어가 있을거에요.
<div class='inserthtml'>
<h1>Hello World</h1>
</div>
https://www.quora.com/What-is-meant-by-innerHTML-in-Angular-4
대신에 문제가 있어요. styling이 적용이 되지 않아요.
방법은 두가지에요.
https://joeonsoftware.com/2019/08/18/styling-inner-html-with-angular-special-selectors/
https://angular.io/guide/component-styles#deprecated-deep--and-ng-deep
다행히 공시 문서에 어떻게 해야하는지 정확히 나와있어요.
https://angular.io/guide/build
크게 알려준 path에 environment 파일들을 만들어주고,
└──myProject/src/environments/
└──environment.ts
└──environment.prod.ts
└──environment.stage.ts
angular.json에 architect > build > configurations에 'dev'을 를 추가해서 다음과 같이 파일을 넣어줘요.
"configurations": {
"production": { ... },
"dev": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.dev.ts"
}
]
}
}
serve 쪽도 추가해줍니다.browserTarget <- 이 프로퍼티 다음과 같이 넣어줘요.
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "your-project-name:build"
},
"configurations": {
"production": {
"browserTarget": "your-project-name:build:production"
},
"dev": {
"browserTarget": "your-project-name:build:dev"
}
}
},
package.json
에 run script를 다음과 같이 바꿔줘요.
"start:dev": "ng serve -c=dev
configuration에서 build option만 좀더 자세히 보면
"projects": {
"angular": {
architect: {
"build":{
"configurations": {
"dev": {
"optimization": false,
"outputHashing": "none",
"sourceMap": true,
"extractCss": false,
"namedChunks": false,
"extractLicenses": false,
"vendorChunk": false,
"buildOptimizer": false,
}
}
}
}
}
}
이제 ng serve, ng build의 경우 --configuration=<환경> 옵션을 붙여서 실행하면 됩니다.
"ng serve --configuration=dev"
앵귤러 9부터는 default compiler가 aot이에요.
aot : compiles app and libraries at build time
https://angular.io/guide/aot-compiler#choosing-a-compiler
앵귤러 빌드 속도를 높이는 옵션들
https://stackoverflow.com/a/56992143
개발에서는 빠르게 빌드되고, 수정할때마다 compile 속도가 빠를수록 개발 피로도가 덜하겟죠.
optimization, sourceMap 부분만 건드려도 빨라집니다.
프론트 개발시 chrome 개발자도구 보면서 HTML element에 style를 바로 입혀보는 작업을 자주 하는데,
sourceMap 같은 경우는 더 자세히 설정할 수 있습니다.
https://angular.io/guide/workspace-config#source-map-configuration
"sourceMap": {
"scripts": true,
"styles": true,
"hidden": false,
"vendor": false
},
해결한 방법 레퍼런스
https://stackoverflow.com/questions/48006629/angular-navigate-to-the-same-route-with-different-parameter
이슈: list page -> detail page 에서 작업하다가 뒤로가기를 했을때 이전 list화면에 검색 필터가 기억됐으면 좋겠어요.
뒤로가기는 앵귤러의 import {Location} from '@angular/common';
의 location의 back()이란 메서드를 통해 가능하다. 이렇게 간단하게 해결되는데,
문제는 같은 url이지만 queryparams가 달라지는데 router가 동작을 안해요.
꼼수를 부려요. '/'로 navigate를 하는데, skipLocationChange: true로 줌으로써 history가 남지 않아요. ㅎ
reloadByParams(url: string, queryParams: {}) {
this.router
.navigateByUrl('/', {
skipLocationChange: true
})
.then(() => {
this.router.navigate([url], {queryParams});
});
}
시행착오:
angular router same url
이란 키워드로 검색해서 대부분의 방법을 해본 것 같다.
onSameUrlNavigation
방법도 안 먹힘. 이거 어떻게 동작하는지를 모르겠음.
onSameUrlNavigation 프로퍼티랑 같이 github에서 긁어온 글
@rainstormza ... hmm, just I forgot to say that there is another property which affects the behavior of re-running guards and resolvers ... when onSameUrlNavigation: true is set ... then ... it is runGuardsAndResolvers ... so there has to be runGuardsAndResolvers: 'always' (... there are also 'paramsChange' or 'paramsOrQueryParamsChange' possibilities) set on each route definition which you want to affect.
router.navigate메서드를 사용하되 첫 번째 인자인 route에 빈배열을 줍니다.
constructor(private router: Router) { }
public myMethodChangingQueryParams() {
const queryParams: Params = { myParam: 'myNewValue' };
this.router.navigate(
[],
{
queryParams: queryParams,
queryParamsHandling: 'merge', // remove to replace all query params by provided
});
}
canDeactivate를 이용! but route가 다른곳으로 이동할때만 동작
예를 들어, 쿼리 파람으로 분기처리하는 곳에서는 동작 안합니다.
참고: https://medium.com/swlh/angular-9-candeactivate-route-guard-example-1329fd0b4653
<div class="input_box_area _develop">
<span class="input_box">
<input
type="radio"
class=""
id="rental0"
name="rentalBusinessHouse0"
checked=""
(change)="handleChangeBusinessInterest($event, 'rental')"
/>
<label for="rental0">rental</label>
</span>
<span class="input_box">
<input
type="radio"
class=""
id="rental1"
name="rentalBusinessHouse0"
(change)="handleChangeBusinessInterest($event, 'supply')"
/>
<label for="rental1">supply</label>
</span>
</div>
다음과 같은 상황에서 input 태그에 value="rental" value="supply" 이런식으러 값을 넣으면 change 이벤트, click 이벤트가 제대로 동작하지 않아요.
input 태그에 value 값을 주지않고 이벤트 핸들러한테 값을 주세요.
formControlName을 쓰면 . dot을 이용한 참조를 안해도 된다네용.
<div>
<input type="text" [formControl]="myForm.controls.firstName"/>
<input type="text" [formControl]="myForm.controls.lastName"/>
<input type="text" [formControl]="myForm.controls.email"/>
<input type="text" [formControl]="myForm.controls.title"/>
</div>
Is equivalent to:
<div [formGroup]="myForm">
<input type="text" formControlName="firstName"/>
<input type="text" formControlName="lastName"/>
<input type="text" formControlName="email"/>
<input type="text" formControlName="title"/>
</div>
https://angular.io/guide/form-validation
사용자 정보를 데이터베이스에 저장할때는 form을 많이 이용하지
form으로 input태그를 쌓아서 사용합니다.
angular에서는 각 하나의 input을 formControl, 이런 input들의 뭉태기를 formGroup으로 관리합니다.
여기선, 필수체크와 최소 길이에 대한 유효성체크는 기본적으로 제공됩니다.
ngOnInit(): void {
this.heroForm = new FormGroup({
'name': new FormControl(this.hero.name, [
Validators.required,
Validators.minLength(4),
forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
]),
'alterEgo': new FormControl(this.hero.alterEgo),
'power': new FormControl(this.hero.power, Validators.required)
});
}
get name() { return this.heroForm.get('name'); }
get power() { return this.heroForm.get('power'); }
validation에서 걸리는것은 다음과 같이 표현이 되요.
<input id="name" class="form-control"
formControlName="name" required >
<div *ngIf="name.invalid && (name.dirty || name.touched)"
class="alert alert-danger">
<div *ngIf="name.errors.required">
Name is required.
</div>
<div *ngIf="name.errors.minlength">
Name must be at least 4 characters long.
</div>
<div *ngIf="name.errors.forbiddenName">
Name cannot be Bob.
</div>
</div>
사용자 정의 validation!
일단 validator 함수를 만들어요.
/** A hero's name can't match the given regular expression */
export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
return (control: AbstractControl): {[key: string]: any} | null => {
const forbidden = nameRe.test(control.value);
return forbidden ? {'forbiddenName': {value: control.value}} : null;
};
}
이걸 formGroup에 넣습니다.
this.heroForm = new FormGroup({
'name': new FormControl(this.hero.name, [
Validators.required,
Validators.minLength(4),
forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
]),
'alterEgo': new FormControl(this.hero.alterEgo),
'power': new FormControl(this.hero.power, Validators.required)
});
https://netbasal.com/angular-reactive-forms-the-ultimate-guide-to-formarray-3adbe6b0b61a
formGroup 생성
특이점은 FormArray에 대한 레퍼런스를 getter를 통해 만들어놔야해요.
export class UserComponent {
user = new FormGroup({
name: new FormControl(''),
skills: new FormArray([])
});
get skills() {
return this.user.get('skills') as FormArray;
}
}
getter를 써줘야 formArrayName="skills" 이 부분이 동작해용~
[formControlName] = i 에는 다음과 같이 인덱스를 넣어줘야해요.
<form [formGroup]="user">
<input formControlName="name" />
<ng-container formArrayName="skills">
<div *ngFor="let _ of skills.controls; index as i">
<input [formControlName]="i" />
</div>
</ng-container>
</form>
예를 들어, 신규 정보를 생성하는 컴포넌트인데, 신규 생성이니 입력컴포넌트에 값들이 비어있잖아요.
angular form을 쓰면서 Validator.required (필수값)으로 정해두면, 처음부터 화면에 빨간줄 (default)로 나오고 난리란말이죠.
유효성 체크는 하되! 화면에만 안보여주는거죠. 뭔소리냐
<input
type="text"
class="form-control"
formControlName="customerName"
maxlength="30"
[ngClass]="
isFieldValid('customerName')
? ''
: formSumitAttempt > 0
? 'has-error'
: ''
"
/>
/**
* 유효성 체크를 위한 메서드
*/
isFieldValid(field: string) {
return (
this.supportBusinessForm.get(field) &&
this.supportBusinessForm.get(field).valid
);
}
이렇게 로직을 짜보는거에요. 우선 템플릿부터 보면 customerName이란 필드를 isFieldValid 메서드가 호출되는 형식이고 이게 [ngClass]의 값으로 들어가죠? [ngClass]는 변됭될때마다 실행되요. 마치 onchange 이벤트 핸들러처럼 내부적으로 onchange로 구현되어있겠죠? 저기보면 'has-error'
기본적으로 angular form에서 필수값으로 체크한 필드가 에러(invalid || untouched)일 경우 생기는 class에요. 자동으로 생기는 거니까 굳이 다른 css class를 만들필요없죠. 기본적으로 angular에서 제공하는 error 스타일링을 그대로 쓸거기때문에~
아무튼 요점은 ngClass와 FormControl과 유효성 체크 메서드를 이용해서 분기처리할 수 있다는 것
유효성체크로직 자체를 동적으로 넣고 빼고싶을때 :
https://velog.io/@noyo0123/%EC%95%B5%EA%B7%A4%EB%9F%AC-%EC%9C%A0%ED%9A%A8%EC%84%B1-%EC%B2%B4%ED%81%AC%EB%8F%84-%EB%B6%84%EA%B8%B0%EC%B2%98%EB%A6%AC
어떤 상황에서 써야해?
https://medium.com/sjk5766/ng-template%EA%B3%BC-ng-container-fb913ff1984d
ng-template... 삽질 ng-template은 *ngIf가 적용안됌..
우리가 구조 디렉티브를 사용할 경우 보통은 ngIf와 같이 사용하는데 ng-template 에서는 [ngIf]와 같이 사용해야 합니다. ngIf 문법을 사용하면 적용되지 않습니다.
특정 조건일때 화면에 보이고싶으면
<ng-container *ngIf="조건">조건에만 보임</ng-container>
ul>li 같이 여러 태그를 반복 렌더링해야할때
<ng-container *ngFor="let item of itemList">{{item}}</ng-container>
좀 더 검색을 해봐야겠지만,
일단은 ng-container만 쓰면 되겠다.
<categoryCode, categoryNm> 타입의 배열이 있어요.
그리고 category란 데이터를 기준으로 조회를 분기하고싶어요.
이 상황에서는 onClick이벤트 핸들러의 인자로 둬도 됩니다.
dataset를 *ngFor와 같이 사용하고 싶을때는?
[attr.data-category]를 사용하면 됩니다.
<ng-container *ngFor="let item of categoryList">
<button
type="button"
[attr.data-category]="item.categoryCode"
(click)="sortItemSelect($event)"
class="active"
>
{{ item && item.categoryNm }}
</button>
</ng-container>
https://stackoverflow.com/questions/34542619/how-can-i-write-data-attributes-using-angular
angular.json에 sourceRoot, prefix가 있다고 가정합니다.
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"angular": {
...
"root": "",
"sourceRoot": "src",
"prefix": "app",
폴더 구성
src > app > components
components밑에 components를 추가하고싶어요.
ng g c FieldErrorDisplay /components/FieldErrorDisplay
이렇게 하면 src > app > components > field-error-display > field-error-display.component.ts 가 생성됩니다.
모듈에는 declaration에 추가되고요.
꼭 워드프레스가 아니더라도, html 태그를 그대로 가져오고 싶을 경우 사용할 수 있어요. -> innerHTML을 쓰시면 됩니다.
여기에 플러스++
innerHTML안에 youtube url이 있다면?
youtube url을 iframe으로 감싸서 보여줄거기때문에 angular dom sanitizer를 사용해야하고요.
워드프레스 html은 core-embed-youtube라고 xml태그로 둘러싸여있어요. 요걸 정규식으로 찾아서(match) youtube url을 iframe
으로 덮은 후 치환해주면 됩니다.
<- 이건 그때 상황에 따라 다르기 때문에 정규식 노가다를 해줘야합니다.
그리고 iframe을 여러개 쓸때는 self closing을 쓰면 안됩니다.
<iframe>...</iframe>
처럼 정상적으로 tag를 닫아줘야합니다.
파이프 만들어서 사용하는 것까지
파이프를 만들어줘요.
import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
@Pipe({
name: 'safeHtml',
})
export class InsightPipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) {}
transform(value: string): SafeHtml {
return this.sanitizer.bypassSecurityTrustHtml(value);
}
}
이제 component.html 에서 다음과 같이 사용하면 됩니다.
content에 html 태그를 string으로 매핑이 되어있습니다.
<div
class="contents_wrap"
[innerHTML]="content | safeHtml"
...
></div>
레퍼런스:
https://stackoverflow.com/questions/8068578/how-do-i-use-multiple-iframes-in-my-html-page
how to import the Component declarated from other Module at angular
지금 작업중인 컴포넌트가 A고, A의 컴포넌트가 declaration된 module을 찾아요.
그리고 재활용할 컴포넌트가 B이고 B컴포넌트가 선언된 module을 찾아요.
다음과 같이 A 모듈에서 B모듈을 임포트해줘요.
import {BModule} from '../BModule';
@NgModule({
imports: [
...,
BModule
],
declarations: [
...
],
providers: [...],
exports: [...]
})
export class AModule {}
그리고 B 모듈에서도 BComponent를 내보내줘야해요.
import {BComponent} from './components/bcomponent';
@NgModule({
declarations: [
...
],
imports: [
...
],
providers: [...],
exports: [BComponent]
})
export class BModule {}
다음과 같이 angular에서 select를 사용중이에요.
<label class="col-form-label text-right w-10 pr-2">
발전소 유무
</label>
<select
class=" custom-select custom-select-sm "
[(ngModel)]="searchParam.pwpYN">
<option
[value]="item.value"
*ngFor="let item of pwpRegPhaseSelect">
{{ item && item.label }}
</option>
</select>
searchParam이란 객체가 있고, 이 객체에 pwpYN 프로퍼티를 이용해서 셀렉트 option 값을 바인딩 한다고 가정합니다.
select의 option을 보여줄 객체 배열이 있습니다.
초기값을 두고 싶을때는
ngOnInit 부분에서 셀렉트 초기값을 지정해줘야해요.
그래야 화면에도
이렇게 초기값을 보여줍니다.
ngOnInit() {
this.searchParam = {};
// select에서 보여줄 options
this.pwpRegPhaseSelect = [
{label: '선택', value: ''},
{
label: '있음',
value: '1'
},
{label: '없음', value: '2'}
];
/**
* select 초기값
*/
this.searchParam = {
...this.searchParam,
pwpYN: this.searchParam.pwpYN ? this.searchParam.pwpYN : ''
}
https://github.com/angular/angular-cli/blob/master/docs/documentation/stories/proxy.md
antd를 앵귤러에 붙이고싶어서 찾아보니 ng-zorro라는걸 찾았는데,
ng add ng-zorro-antd
를 하라고 한다. npm i 도 있는데 ng add? 검색 ㄱㄱ
ng add <package>
는 npm install 작업 플러스 angular 설정을 업데이트하고요, 이때 필요한 추가적인 라이브러리까지 다운받아 쓴다네요.
https://itnext.io/4-ways-to-listen-to-page-scrolling-for-dynamic-ui-in-angular-ft-rxjs-5a83f91ee487
Angular Renderer2사용해서
this.listener = this.renderer.listen('window', 'scroll', (e) => this.handleScrollHeader());
scroll 이벤트 핸들러를 등록해주고
ngOnDestroy에서
this.listener();
이렇게 핸들러를 제거해줍니다.
Lazy Loading한 Module route 이동시 앵귤러에서 Module에 바인딩한 Service 죽일까?
안죽임!
그동안 NgModule에 providers로 Service 바인딩해서 개발했어요.
그리고 NgModule의 경우 Lazy Loading Lazy Module를 로드 한 경우에
예를 들어 라우터 이동으로 그 컴포넌트가 뷰에서 사라졌어요. NgDestroy도 됐고요. Module에 바인딩된 Service 인스턴스 메모리에서 사라졌을까요? 그대로 있어요. 상태값이 그대로 있어서, Component레벨에서 providers 사용해서 service를 바인딩 하든지, component Destroy할때마다 상태값을 초기화시켜주는 작업이 필요해요.
비즈니스가 복잡하다 - 컨테이너 컴포넌트 ngDestroy에 상태값 초기화
비즈니스가 간단하다 - 컨테이너 역할하는 컴포넌트 레벨에 Service바인딩해요.
표시된대로 Matdialog를 사용하지 않는데, contructor에서 inject 해서그래요.
ERROR NullInjectorError: R3InjectorError(ProductStatusModule)[MatDialogRef -> MatDialogRef -> MatDialogRef -> MatDialogRef -> MatDialogRef]:
NullInjectorError: No provider for MatDialogRef!
ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: '[object Object]'. It seems like the view has been created after its parent and its children have been dirty checked. Has it been created in a change detection hook?
Wrap script in setTimeout()
https://medium.com/ngconf/bye-bye-entrycomponents-a4cd933e8eaf
angular 9부터 ivy rendering engine이 반영되었음.
앵귤러는 ngModule에 선언된 템플릿, 컴포넌트만 생성했어요(NgFactory로). 이런 컴포넌트들은 tree-shaking 되지 않았어요.
entryCompoents의 목적은 root component에 렌더하기 위해서 썼어요. entryComponent에 컴포넌트를 선언해두면, compiler가 browser에 launch되야하는 컴포넌트로 보고 bootstrapping of the application
모든 컴포넌트는 entering components로 여겨지며, entryComponents에 명시할 필요없음.
원래는 부모 컴포넌트의 변경감지가 발생해도, 자식 컴포넌트, 손자 컴포넌트까지 변경감지 프로세스가 발생함.
변경감지 자체가 성능에 부하를 준다. rerender되고 그러진 않음..
decorater를 통해 설정하는 방법은 런타임시 분기처리가 되지 않는데, 분기처리까지 하고싶으면 ChangeDetectionRef를 사용하면 됌.
레퍼런스 : https://blog.angular-university.io/how-does-angular-2-change-detection-really-work/
https://ksrae.github.io/angular/creating-library/
https://www.digitalocean.com/community/tutorials/detect-responsive-screen-sizes-in-angular
대 다 나 다 !