updated_at: 2025-08-19 13:21

Widget(위젯) 만들기

angular에서 위젯을 만드는 과정은 재사용 가능한 컴포넌트를 생성하고 동적으로 렌더링하는 방식으로 이루어집니다. 최신 Angular 버전에서는 독립형 컴포넌트(Standalone Components)를 사용하는 것이 일반적입니다. 아래는 위젯을 만드는 순서를 차례대로 나열한 것입니다. 버전은 angular 19를 기준으로 설명드립니다.

위젯제작 및 Angular 내부에서 import 하여 사용하기

1단계: 새로운 Angular 프로젝트 생성 (필요시)

아직 프로젝트가 없다면 Angular CLI를 사용하여 새 프로젝트를 생성합니다.

ng new my-widget-app --standalone
cd my-widget-app

2단계: 위젯 컴포넌트 생성

Angular CLI를 사용하여 위젯으로 사용할 새로운 컴포넌트를 생성합니다. 예를 들어, simple-widget이라는 이름의 위젯을 만들어 보겠습니다.

ng generate component components/simple-widget

이 명령은 src/app/components/simple-widget 경로에 다음과 같은 파일들을 생성합니다.

  • simple-widget.component.ts: 컴포넌트의 로직을 담당하는 TypeScript 파일
  • simple-widget.component.html: 컴포넌트의 템플릿 파일
  • simple-widget.component.css: 컴포넌트의 스타일 파일
  • simple-widget.component.spec.ts: 컴포넌트의 테스트 파일

3단계: 위젯의 내용과 스타일 정의

  • simple-widget.component.html (템플릿)
<div class="widget-container">
  <h3>{{ title }}</h3>
  <p>{{ content }}</p>
</div>
  • simple-widget.component.css (스타일)
.widget-container {
  border: 1px solid #ccc;
  padding: 16px;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
h3 {
  margin-top: 0;
}
  • simple-widget.component.ts (로직)
import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-simple-widget',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './simple-widget.component.html',
  styleUrls: ['./simple-widget.component.css']
})
export class SimpleWidgetComponent {
  @Input() title: string = '기본 제목';
  @Input() content: string = '기본 콘텐츠입니다.';
}

4단계: 부모 컴포넌트에서 위젯 사용하기

이제 생성한 위젯을 다른 컴포넌트(예: app.component.ts)에서 사용할 수 있습니다.

    1. app.component.ts 파일 수정
import { Component } from '@angular/core';
import { SimpleWidgetComponent } from './components/simple-widget/simple-widget.component';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [SimpleWidgetComponent], // 위젯 컴포넌트 임포트
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'my-widget-app';
}
    1. app.component.html 파일 수정
<h1>내 대시보드</h1>
<app-simple-widget
  title="날씨 위젯"
  content="현재 기온은 25도입니다.">
</app-simple-widget>

<app-simple-widget
  title="주식 위젯"
  content="주식 시장이 안정적입니다.">
</app-simple-widget>

웹 컴포넌트(Custom Element)로 만들어 배포하기

이 방법은 Angular 컴포넌트를 웹 표준 기술인 '사용자 정의 요소(Custom Element)'로 변환하는 것입니다. 이렇게 변환된 위젯은 하나의 JavaScript 파일로 만들어져 어떤 웹사이트에서도 <simple-widget></simple-widget>과 같은 일반 HTML 태그처럼 사용할 수 있습니다.
이 방식도 일반적으로 두가지 방식이 있는데 아래의 두가지 방식입니다.

  • ngx-build-plus 사용 (간단한 방식)
  • 직접 스크립트 작성 (복잡하지만 세밀한 제어 가능)

여기서는 직접 스크립트를 작성 하는 방식에 대해서 설명 드리겠습니다.

1단계: 필요 라이브러리 설치

빌드 후 파일을 병합하고 관리하기 위한 Node.js 라이브러리를 설치합니다. package.json에 이미 포함되어 있지만, 만약 처음부터 구성한다면 다음 명령어를 실행합니다.

  • fs-extra: 파일 시스템 작업을 더 편리하게 해주는 라이브러리 (폴더 생성, 파일 복사 등).
  • concat: 여러 파일을 하나의 파일로 합쳐주는 라이브러리.
npm install fs-extra concat --save-dev

2단계: 위젯으로 만들 컴포넌트 준비

simple-widget.component.ts

import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-simple-widget', // 이 셀렉터는 Custom Element 이름과 달라도 됩니다.
  standalone: true,
  imports: [CommonModule],
  template: `
    <div style="border: 1px solid #ccc; padding: 16px; border-radius: 8px;">
      <h3>{{ title }}</h3>
      <p>{{ content }}</p>
    </div>
  `
})
export class SimpleWidgetComponent {
  @Input() title: string = '기본 제목';
  @Input() content: string = '기본 콘텐츠입니다.';
}

3단계: 컴포넌트를 Custom Element로 등록

애플리케이션의 진입점에서 위 컴포넌트를 웹 표준 Custom Element로 등록합니다. 보통 app.component.ts나 main.ts에서 이 작업을 수행합니다.

app.component.ts

import { Component, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { SimpleWidgetComponent } from './components/simple-widget/simple-widget.component';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [SimpleWidgetComponent],
  template: '' // 앱 자체는 템플릿이 필요 없습니다. 위젯 등록이 목적입니다.
})
export class AppComponent {
  constructor(private injector: Injector) {
    // 1. SimpleWidgetComponent를 Custom Element로 변환합니다.
    const customElement = createCustomElement(SimpleWidgetComponent, { injector });

    // 2. 브라우저에 '<simple-widget>'라는 이름의 HTML 태그로 정의합니다.
    //    이미 등록되어 있을 수 있으므로 확인 후 정의하는 것이 안전합니다.
    if (!customElements.get('simple-widget')) {
      customElements.define('simple-widget', customElement);
    }
  }
}

main.ts
위처럼 app.component.ts 하는 것도 좋지만 environment 의 widget 여부에 따라서 개발환경과 widget을 분리하기위해 저는 main.ts에서 아래와 같이 처리하였습니다.

import { createApplication } from '@angular/platform-browser';
import { ApplicationRef } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { bootstrapApplication } from '@angular/platform-browser'; // 일반 부팅 함수 import

import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';
import { environment } from './environments/environment'; // 환경 변수 import


  // 위젯환경과 일반 개발 환경 분리
if (environment.widget) {
  (async () => {
    // Angular 앱의 참조를 생성합니다.
    const app: ApplicationRef = await createApplication(appConfig);

    // AppComponent를 기반으로 커스텀 요소를 생성합니다.
    const chatBotElement = createCustomElement(AppComponent, {
      injector: app.injector,
    });

    // 브라우저에 <chat-bot> 이라는 이름의 새로운 HTML 태그를 정의합니다.
    customElements.define('simple-widget', chatBotElement);
  })();
} else {
  // 일반적인 Angular 앱 부팅
  bootstrapApplication(AppComponent, appConfig)
    .catch((err) => console.error(err));
}
  • customElements.define('simple-widget', ...): 여기서 첫 번째 인자인 'simple-widget'이 나중에 HTML에서 사용할 태그 이름이 됩니다.

4단계: angular.json에 빌드 환경 설정 추가

ng build --configuration widget 명령어가 동작하도록 angular.json 파일에 widget 설정을 추가합니다. 핵심은 파일 이름에서 해시값을 제거하는 것입니다. 그래야 build-widget.js 스크립트가 항상 동일한 이름의 파일을 찾을 수 있습니다.

angular.json

{
  // ...
  "projects": {
    "front-end": {
      // ...
      "architect": {
        "build": {
          // ... (기본 build 설정)
          "configurations": {
            "production": {
              // ...
            },
            "development": {
              // ...
            },
            "widget": { // <-- 이 부분을 추가합니다.
              "optimization": true,
              "outputHashing": "none", // 파일 이름에 해시값을 붙이지 않음
              "sourceMap": false,
              "namedChunks": false,
              "extractLicenses": true,
              "vendorChunk": false
            }
          }
          // ...
        }
      }
    }
  }
}

환경변수에 의한 분리를 하기위해서는 아래와 같이 추가합니다.

"widget": { // <-- 이 부분을 추가합니다.
    ..........
    "outputHashing": "none",
    "fileReplacements": [
      {
        "replace": "src/environments/environment.ts",
        "with": "src/environments/environment.widget.ts"
      }
    ]
  }

5단계: build-widget.js 스크립트 작성

이제 프로젝트의 루트 경로에 build-widget.js 파일을 생성하고, 빌드된 JS 파일들을 하나로 합치는 코드를 작성합니다.

build-widget.js

// 필요한 라이브러리를 불러옵니다.
const fs = require('fs-extra');
const concat = require('concat');

(async function build() {
    // Angular 빌드 결과물이 생성되는 경로
    const sourcePath = './dist/front-end/browser/';

    // 최종 결과물을 저장할 경로와 파일 이름
    const destinationPath = './dist/widget/';
    const destinationFile = destinationPath + 'my-widget.js'; // 최종 파일 이름

    console.log('Starting custom widget build...');

    // 최종 결과물을 저장할 폴더가 없으면 생성합니다.
    await fs.ensureDir(destinationPath);

    // Angular 빌드로 생성된 JS 파일 목록
    // 중요: 순서가 매우 중요합니다! runtime -> polyfills -> main 순서를 지켜야 합니다.
    const files = [
        sourcePath + 'runtime.js',
        sourcePath + 'polyfills.js',
        sourcePath + 'main.js',
    ];

    // 파일들을 하나로 합칩니다.
    await concat(files, destinationFile);

    console.info(`Widget created successfully at: ${destinationFile}`);

    // (선택 사항) 스타일시트(CSS) 파일도 하나로 합치고 싶다면 아래 코드를 추가할 수 있습니다.
    // const cssFiles = [ sourcePath + 'styles.css' ];
    // await concat(cssFiles, destinationPath + 'styles.css');
    // console.info(`CSS bundled successfully.`);

})();

6단계: package.json 스크립트 설정

이제 npm run build:widget 명령어가 모든 과정을 순차적으로 실행하도록 package.json의 scripts 섹션을 설정합니다.

{
  // ...
  "scripts": {
    // ...
    "build:widget": "ng build --configuration widget && node build-widget.js"
  },
  // ...
}
  • &&: 앞의 명령어(ng build ...)가 성공적으로 완료되어야 뒤의 명령어(node ...)가 실행됨을 의미합니다.

7단계: 실행 및 사용

7.1. 빌드 실행: 터미널에서 다음 명령어를 실행합니다.

npm run build:widget

7.2. 결과 확인

빌드가 완료되면 dist/widget/my-widget.js 파일이 생성된 것을 확인할 수 있습니다.

7.3. 외부에서 사용

이제 이 my-widget.js 파일 하나만 있으면 어떤 HTML 페이지에서도 위젯을 사용할 수 있습니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <title>My Widget Test</title>
</head>
<body>
    <h1>Angular Widget in a Plain HTML Page</h1>

    <!-- 3단계에서 정의한 태그 이름과 Input 속성을 사용합니다. -->
    <simple-widget
        title="외부에서 주입한 제목"
        content="이 위젯은 Angular 프로젝트 외부에서 렌더링되었습니다.">
    </simple-widget>

    <hr>

    <simple-widget
        title="두 번째 위젯"
        content="하나의 JS 파일로 여러 위젯을 사용할 수 있습니다.">
    </simple-widget>

    <!-- 2단계에서 생성된 my-widget.js 파일을 로드합니다. -->
    <script src="my-widget.js"></script>
</body>
</html>
평점을 남겨주세요
평점 : 2.5
총 투표수 : 1

질문 및 답글