@jj-viewer/skinnable v0.0.27
@jj-viewer/skinnable
This library was generated with Angular CLI version 9.1.0.
Features
Angular 프로젝트에서 다음 사항을 컴파일 이후에도 동적으로 로드하여 설정할 수 있도록 한다.
- HTML (template)
 - Css (style)
 - Json (Component의 속성 설정)
 
컴포넌트간 API를 호출할 수 있는 Mediator를 제공한다.
(주의) Jit 컴파일 일때만 정상 동작합니다.
- AOT 컴파일을 지원되지 않습니다. (--aot=false)
 <router-outlet></router-outlet>구문은 테스트되지 않았습니다.
Installation
npm install @jj-viewer/skinnable --saveTypescript 환경 설정
tsconfig.json
{
  "compilerOptions": {
    ...
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "useDefineForClassFields": true, // 생성자에서 초기화하지 않은 member도 Object.keys 목록에 추가함
    }
  },
  
  "angularCompilerOptions": {
    ...
    "enableIvy": false
  },
  ...
}proxy 설정
개발환경에서 런타임에 사용할 bundle 폴더에 접근하기 위해 proxy 서버를 설정합니다.bundle 폴더에는 skin(template, css) 파일 및 이미지등의 asset 파일을 넣어둡니다.
workspace (root) 위치가 다음 주소로 서비스된다고 가정할때
http://localhost/dev/workspaceproxy.conf.json파일 생성{ "/bundle/*": { "target": "http://localhost/dev/workspace", "secure": false, "logLevel": "debug" } }angular.json파일 수정"serve": { "options": { "proxyConfig": "proxy.conf.json", }, }또는 실행시 옵션 설정
ng serve --proxy-config=proxy.conf.jsonng serve실행http://localhost/dev/workspace/bundle서비스를http://localhost:4200/bundle로 접근 가능해짐.
Usage
testApp프로젝트에서 보다 자세한 예시를 참고하실 수 있습니다.
동적으로 Skin (template, css) 이 설정되는 과정
- 설정 파일을 정의합니다. (
bundle폴더) SkinApplicationComponent에서 설정 파일이 로드됩니다. (json 파일)- 설정 파일 내용이 각 
SkinComponent에 동적으로 적용됩니다. (template, css) 
AppModule 설정
@NgModule({
    // 중요: Skinnable Component는 AppModule에 export 되어 있어야 함
    // AppModule에 export 되어있는 module은 추가할 필요 없음
    providers: [
        {provide: SKIN_INJECT_MODULES, useValue: [AppModule]}
    ],
    schemas: [
        NO_ERRORS_SCHEMA,
        CUSTOM_ELEMENTS_SCHEMA
    ],
    imports: [
        ...imports,
        SkinnableModule
    ],
    exports: [
        ...exports
    ],
    ...
})
export class AppModule {}SKIN_INJECT_MODULES에 값 지정
컴파일된 이후에 사용되는 (다른 모듈에서 정의된) 컴포넌트를 사용하려면
- 컴포넌트는 
AppModule에서export되어 있어야 하고, exporte된 컴포넌트를 런타임에 사용하는SkinnableModule에import되어야 하므로,- 필요한 module을 배열로 지정하는 것 보다는 모든 기능(요소)을 포함하는 
AppModule을 대표로import시킵니다.. AppModule대신 필요한 모듈만import시켜도 됩니다.{provide: SKIN_INJECT_MODULES, useValue: [...modules]}
(주의) AOT 컴파일을 지원되지 않습니다. (--aot=false)
SkinnableModule에 런타임에import되는 Module을 동적으로 인식할 수없습니다.
Component에 적용
SkinComponent를 상속 받아 사용합니다.
import {
    SkinApplicationComponent,
    SkinComponent,
    SKINNABLE_TEMPLATE,
    SkinnableParameter,
    skinnableParameterProvider
} from '@jj-viewer/skinnable';
@Component({
    selector: 'jj-sample',
    providers: [skinnableParameterProvider],
    // 설정파일 또는 Attribute에 설정된 경로의 템플릿이 동적 로딩됨
    template: SKINNABLE_TEMPLATE
    // 아래 설정도 여전히 사용 가능함 (이곳 템플릿은 컴파일 됨)
    // templateUrl: 'sample.html',
    // styleUrls: ['sample.css'],
})
export class SampleComponent extends SkinComponent {
    constructor(protected param: SkinnableParameter, protected viewContainerRef: ViewContainerRef) {
        super(param, viewContainerRef);
    }
}Application에 적용
SkinApplicationComponent를 상속 받아 사용합니다.
import {
    SkinApplicationComponent,
    SKINNABLE_TEMPLATE,
    SkinnableParameter,
    skinnableParameterProvider
} from '@jj-viewer/skinnable';
@Component({
    selector: 'jj-application',
    providers: [skinnableParameterProvider],
    template: SKINNABLE_TEMPLATE
})
export class ApplicationComponent extends SkinApplicationComponent {
    constructor(protected param: SkinnableParameter, protected viewContainerRef: ViewContainerRef) {
        super(param, viewContainerRef);
    }
}컴포넌트간 통신
testApp프로젝트에서SampleComponent에서 발송한 이벤트를DefineComponent에서 받아 처리하는 예제 입니다.
컴포넌트간 서로 통신을 하려면 반드시 id가 (attribute or class value) 설정되어 있어야 합니다.
SampleComponent에서 이벤트 발송
// SampleComponent
bookLoadedEvent = new EventEmitter<any>();
// @오버라이딩
// 모든 컴포넌트 설정이 완료된 applicationCompleteEvent 발생 후 emit 한다.
// 오버라이딩해서 사용하도록 applicationCompleteEvent 청취가 구현되어 있다.
protected onApplicationComplete() {
    setTimeout(() => {
        // 예 : bookLoaded 생성 완료를 알림
        this.bookLoadedEvent.emit(this.id);
    }, 1000);
}- template에 
id설정 
<jj-sample id="3.app_skin_sample" ...>DefineComponent에서 이벤트 청취
// @오버라이딩
// 모든 컴포넌트 설정이 완료된 applicationCompleteEvent 발생 후 청취 한다.
protected onApplicationComplete() {
    // Mediator를 통해 컴포넌트 instance에 접근
    const book = this.param.mediatorService.get('3.app_skin_sample') as SampleComponent;
    // 테스트 : bookLoaded 생성 완료 청취
    const subscription = book.bookLoadedEvent.subscribe((id: string) => {
        console.error('bookLoadedEvent : ', book.nodeName, id);
        subscription.unsubscribe();
        // book API 사용
        // book.goPage(10);
    });
}설정 파일 정의
컴포넌트에 설정값을 전달하는 방법은 다음과 같습니다.
다음 순서대로 (우선순위) 설정값이 적용됩니다.
- Attribute 설정값이 있으면 우선적으로 적용됩니다.
 - 설정 파일에서 
[태그(selector)]항목에서 정의된 설정값 - 설정 파일에서 
[global]항목에서 정의된 설정값 
설정 파일에 대해 설명합니다.
{
  "global": {
    /*
    컴포넌트들의 공통 속성중 기본값으로 설정할 값을 전역 설정합니다.
    이곳에서는 properties, skins 외 다른 속성은 정의할 수 없습니다.
    새로운 속성을 추가하려면 properties 항목을 이용합니다.
    */
    
    // 이곳에 속성을 추가해도 컴포넌트에 전달되지 않습니다.
    "can't add propertise": "전달되지 않는 속성값",
    "properties": {
      "can add propertise": "추가할 수 있는 속성 영역",
      // 예) a, b 속성을 모든 컴포넌트에 전달 
      "a": 1, "b": 2
    },
    "skins": {
      "useLoader": true,
      "urlTemplate": "",
      "urlCss": ""
    }
  },
  // 'jj-application' selector를 가진 컴포넌트에 전달되는 속성 설정
  "jj-application": {
    "properties": {
      // 예) jj-application 컴포넌트에 global 설정값까지 함께 전달됨 
      // "a": 1, "b": 10, "c": 20
      "b": 10, "c": 20
    },
    "skins": {
      "useLoader": false,
      "urlTemplate": "bundle/skins/application.html",
      "urlCss": "bundle/skins/application.css"
    }
  },
  // 'jj-sample' selector를 가진 컴포넌트에 전달되는 속성 설정
  "jj-sample": {
    // attribute에 값이 지정된 경우 값 지정 생략해도 됨.
    "skins": {}
  }
  ...
}Skin 파일 지정 방법
url-configuration에 지정된 설정파일에 정의- attribute에서 직접 지정하는 방법
 
Template (HTML) 작성 방법
attribute에 다음 속성을 활용합니다.
url-configuration- 설정파일 경로를 지정 합니다.
 SkinApplicationComponent에만 설정할 수 있습니다.
url-template- 동적 로드되는 템플릿(HTML) 파일 경로입니다.
 - 파일 내부에 @import url() 구문 사용할 수 없습니다.
 
url-css- 동적 로드되는 CSS 파일 경로입니다.
 
use-loader- 파일 로드 방식을 설정
 - (false) angular 로더를 사용하여 파일 내용을 로드
 - (true) 새로 작성된 http 로더로 파일 내용을 로드
 - true 이면 같은 url의 파일은 요청을 한번만 호출함 (cache 사용과는 다름)
 
Attribute 속성으로 사용
<!--문자열은 "'"로 감싸야 함.-->
<jj-book [use-loader]="false"
         [url-template]="'bundle/skins/book.component.html'"
         [url-css]="'bundle/skins/book.component.css'">Attribute 값으로 사용
<!--type을 유지하려면 속성 바인딩 구문 사용해야 함.-->
<!--그렇지 않으면 아래 use-loader 속성은 문자열 "false"로 인식됨.-->
<jj-sample use-loader="false"
         url-template="bundle/skins/book.component.html"
         url-css="bundle/skins/book.component.css">Attribute 바인딩 사용
<!--interpoliate 구문 사용-->
<jj-application url-configuration="{{urlConfiguration}}">경로 설정
Template, Css에 사용되는 URL은 publishing 후 index.html을 기준으로한 상대경로로 작성해야 합니다.
Template 내부에서 경로 지정
<jj-sample urlTemplate="./bundle/skins/sample.html" urlCss="./bundle/skins/sample.css"> </jj-sample>Css 내부에서 경로 지정
@import url("./bundle/skins/test_import.css"); /* styleUrl로 지정된 경우 파일 내부에 @import url() 구문 사용 불가함. */
디렉티브로 기능을 구현하지 않은 이유
- Directive Attribute으로 로드 기능을 구현하는 경우 클래스 상속을 활용할 수없다.
 - 기능이 필요한 모든 노드에 직접 디렉티브를 명시해 주어야 한다.
(설정값까지 attribute에 작성하면 작업이 너무 번거로워 진다.)