Category Archives: TypeScript

[Angular2] 다이나믹하게 페이지 타이틀 설정하기 (Dynamic Title)

페이지별로 타이틀을 다르게 설정하고 싶을 때, 어떻게 하면 좋을까?

routing module에 타이틀 정보를 넣어두고, 그 정보를 바탕으로 타이틀을 설정하는 방법이 있다.

 

1. Title Service 만들기

import {Injectable} from "@angular/core";
import {Title}     from "@angular/platform-browser";

@Injectable()
export class TitleService {
    static DEFAULT_TITLE = "기본 타이틀";
    constructor(
        private title: Title
    ) {}

    setTitle(str?: string): void {
        let titleString = str ? `${str} : ${TitleService.DEFAULT_TITLE}` : TitleService.DEFAULT_TITLE;
        this.title.setTitle(titleString);
    }
}

angular/platform-browser 의 타이틀 설정을 활용하자.

 

2. routing module에 타이틀 정보 넣어두기

import {NgModule} from "@angular/core";
import {RouterModule} from "@angular/router";
import {AuthGuard} from "../core/services/auth-guard.service";

@NgModule({
    imports: [
        RouterModule.forChild([
            {
                path: "",
                component: IndexComponent,
                children: [
                    {
                        path: "apply",
                        component: ApplyComponent,
                        data: {
                            title: "신청",
                        },
                    }
                ],
            },
        ]),
    ],
})

export class RoutingModule {
}

component하단에  data: { title: “신청”} 형식으로 타이틀을 넣어두었다. 말인 즉슨 타이틀 이외에도 다른 정보를 넣어서 다른 처리도 가능하다.

 

3. app.component.ts OnInit 에서의 처리

this.router.events

            .filter((event) => event instanceof NavigationEnd)

            .map(() => this.route)

            .map((route) => {

                while (route.firstChild) route = route.firstChild;

                return route;

            })

            .filter((route) => route.outlet === "primary")

            .subscribe((subject) => {

                if (subject.data) {

                    let event = subject.data["value"];
                    console.log("subscribe!", event)
                    this.titleService.setTitle(event["title"]); 

                    if (window['ga']) {

                        window['ga']('set', 'page', location.pathname);

                        window['ga']('send', 'pageview');

                    }

                }

        });

하나씩 살펴보자

  1. router.events 는 router 를 observable 형태로 볼수 있다. 자세한 것은 여기 문서 를 참조.
  2. event 에 필터를 걸어서 NavigationEnd, 즉 탐색이 완전히 끝나는 정보만 본다.
  3. this.route 는 constructor의 private router: Router 를 의미한다. 그리고 state tree의 마지막 정보를 보기 위해 while문을 추가하였다.
  4. .filter((route) => route.outlet === “primary”)
 로 필터링 하여 children정보도 볼 수 있도록 한다.
  5. 이제 가져온 정보에서 title을 세팅하면 된다.

기존에 Google링 한 자료에서는 .mergeMap(route => route.data) 까지 필요했었는데, 

이작업까지 할경우 같은 컴포넌트에서 라우팅을 할 경우 (같은 페이지로의 이동) subscribe가 중복으로 붙으면서 내부로직을 누적해서 호출하게 된다.(..)

 

[Angular2] DOMSanitizer by Pipe

innertHTML등으로 string의 내용을 html DOM 에 바인딩 할때,  스크립트 공격 등을 방어하기 위해 Angular2는 DOMSanitizer를  제공한다.

이를 pipe형태로 간단하게 템플릿에서 사용하기 위해 아래와 같이 처리했다.

import {Pipe, PipeTransform} from "@angular/core";
import {DomSanitizer} from "@angular/platform-browser";

@Pipe({name: "safeHtml"})
export class SafeHtmlPipe implements PipeTransform {
    constructor(private sanitized: DomSanitizer) {
    }

    transform(value: string) {
        return this.sanitized.bypassSecurityTrustHtml(value);
    }
}

@Pipe({name: "safeCss"})
export class SafeCssPipe implements PipeTransform {
    constructor(private sanitized: DomSanitizer) {
    }

    transform(value: string) {
        return this.sanitized.bypassSecurityTrustStyle(value);
    }
}

@Pipe({name: "safeScript"})
export class SafeScriptPipe implements PipeTransform {
    constructor(private sanitized: DomSanitizer) {
    }

    transform(value: string) {
        return this.sanitized.bypassSecurityTrustScript(value);
    }
}

@Pipe({name: "safeResourceUrl"})
export class SafeResourceUrlPipe implements PipeTransform {
    constructor(private sanitized: DomSanitizer) {
    }

    transform(value: string) {
        return this.sanitized.bypassSecurityTrustResourceUrl(value);
    }
}

 

[Angular2] RouterOutlet 의 부모 정보 가져오기

는 바로 Host 데코레이터를 사용하면 됩니다.

constructor (
        @Host() private parent: ParentComponent) { }
    
    
    ngOnInit() {
        console.log(this.parent.category)
    }

Host 데코레이터는 현재 컴포넌트에서 host를 선언한 엘리먼트가 닿을 때까지 의존성 트리를 뒤지면서,

이와 일치하는 의존성을 가진 인젝터를 가져옵니다.

router-outlet을 썼을 경우, 부모 – 자식 관계를 갖기 때문에 이런식으로 부모의 값을 가져올 수 있습니당

 

 

[Angular2, Typescript…] json결과를 Model 객체로 Serialize하기

Angular2, Typescript 와만 관련이 있다고 볼순 없지만..

보통 api서버를 통해서 결과를 json객체로 가져돈다.

그 json객체를 typescipt에서 선언한 모델 객체로 변환하는 방법을 적어본다.

 

일단 이렇게  api결과가 왔고

{
    "id": 1,
    "name": "안녕",
    "created_at": "2015-10-07T11:42:17.000+09:00",
    "updated_at": "2016-04-29T11:15:28.000+09:00",
    "blocked": false,
    "show": true,
    "order": null
}

이 Test 객체에 담아 보자.

export class Test {
  id: number;
  name: string;
  blocked: boolean;
  show: boolean;
  order: number;
  created_at: Date;
  updated_at: Date;
}

모델에 이런 메소드를 만들자.

이 메소드는 object assign을 이용하여 any객체를 test 모델에 할당할 것이다.

그리고 created_at, updated_at이 있다면 이를 Date로 변환할 것이다.

참고로 http 는 import {Http } from “@angular/http”;

요거다.

static fromJSON(obj: any) {
        return Object.assign(new Test(), obj, {
            created_at: obj.created_at && new Date(obj.created_at as string),
            updated_at: obj.updated_at && new Date(obj.updated_at as string)
        });
    }

이제 웹 Api에서  serialize 하자

test() {
        return this.http.post(`/this/is/test/url`)
            .toPromise()
            .then(a => Test.fromJSON(a.json()))
    }

요렇게 하자.

리스트 형태로 날라오면 아래와 같이 하자.

test() {
        return this.http.post(`/this/is/test/url`)
            .toPromise()
            .then(r => (<any[]>r.json()).map(json => Test.fromJSON(json)))
    }

 

 

 

[Angular2] MaxLength Directive

HTML5의 maxlength는 복사 붙여넣기를 할 경우에 해당 길이를 초과할 경우 붙여넣기가 되지 않는다. (500자만큼 잘라서 넣어주지 않는다)

그래서 maxLength 디렉티브를 직접 구현해보기로 했다.

 

input-maxlength.directive.ts

import { NgModel } from '@angular/forms/src/directives';
import { EventEmitter, Output } from '@angular/core';
import {
    Directive,
    ElementRef,
    HostListener,
    Input,
    OnChanges,
    Renderer,
} from '@angular/core';

@Directive({ 
    selector: '[inputMaxLength]',
    host: {
        "(input)": 'onInputChange($event)'
    },
})

export class InputMaxLengthDirective{

    constructor(private el: ElementRef, 
                private renderer: Renderer) { }
    
    @Input('inputMaxLength') inputMaxLength: number;
    @Output() ngModelChange:EventEmitter<any> = new EventEmitter()

    onInputChange($event: any) {
        let text = $event.target.value
        if (text.length > this.inputMaxLength) {
            this.ngModelChange.emit(text.substr(0, this.inputMaxLength))
            this.el.nativeElement.value = text.substr(0, this.inputMaxLength)
        }
    }
}

 

사용예제

<textarea [(ngModel)]="text" [inputMaxLength]="500"></textarea>

 

과정 중에서 몇가지 삽질이 있었는데,  단순히  nativeElement로 제어 할 경우에는, 그에 바인딩 된 model이 제대로 컨트롤 되지 않는 다는 것이다.

그래서 ngModelChange를 불러와서 model에 substr된 값을 emit 했다.