Creating Web Component in Angular: Standalone Based
Creating Web Components in Angular Standalone Environments: A Comprehensive Guide
This article explores the process of creating Web Components within Angular. While numerous resources focus on Module-based approaches, this guide specifically addresses the creation of Web Components in Angular Standalone environments. For information on Module-based implementations, please refer to alternative documentation.
Workspace Configuration
To facilitate organized management, we’ll begin by establishing a workspace:
ng new workspace --no-create-application
Next, we’ll add both a Library, serving as the source of our Web Component, and an Application to encapsulate and transform it into a Web Component:
ng g library web-component-lib
ng g application web-component-app
Library Implementation
Within the library code, implement your desired functionality. You can leverage Angular features such as @Input and @Output as needed.
Optionally, configure the Shadow DOM for encapsulation.
import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'lib-web-component-lib',
standalone: true,
imports: [],
template: `
<button (click)="onClick($event)">Emit</button>
`,
styles: ``,
encapsulation: ViewEncapsulation.ShadowDom // optional
})
export class WebComponentLibComponent {
@Input() setValue!: string;
@Output() onResult = new EventEmitter();
onClick(e: any) {
this.onResult.emit(e);
}
}
Application Implementation
Since our objective is to invoke the library rather than a conventional web page, several adjustments to the default configuration are necessary.
Removing the app Folder
The app folder is redundant, as we’ll be directly invoking the library instead of the AppComponent. Consequently, it can be safely removed.
Adding main.config.ts
Create a main.config.ts file within the src directory and populate it with the following code:
import { ApplicationConfig } from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [
// add code here
],
};
Add any necessary providers within this file.
Installing @angular/elements
The createCustomElement() function from @angular/elements is essential for transforming the library into a Web Component. Install @angular/elements to gain access to this function.
npm i @angular/elements
Modifying main.ts
Replace the entire contents of the existing main.ts file with the code below:
import {createApplication} from '@angular/platform-browser';
import {appConfig} from './main.config';
import {createCustomElement} from '@angular/elements';
import { WebComponentLibComponent } from 'web-component-lib';
import { ApplicationRef } from '@angular/core';
(async () => {
const app: ApplicationRef = await createApplication(appConfig);
// Define Web Components
const webComponentLibComponent = createCustomElement(WebComponentLibComponent, {injector: app.injector});
customElements.define('web-component-lib', webComponentLibComponent);
})();
The first parameter of customElements.define dictates the name used to invoke the library externally.
Modifying index.html
Remove the default app-root element and insert the name of the library to be invoked. Ensure this name matches the first parameter provided to customElements.define.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Web Component</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<web-component-lib></web-component-lib>
<script src="./main.js" type="module"></script>
<script src="./polyfills.js" type="module"></script>
</body>
</html>
Input and Output Handling
If your library incorporates @Input and @Output properties, these can be utilized within the Web Component. Inputs are employed in a manner analogous to HTML attributes (note that camelCase input names should be represented in kebab-case). Outputs can be accessed by attaching event listeners.
Modify index.html as follows to illustrate input and output handling:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Web Component</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<web-component-lib set-value="'sample'"></web-component-lib>
<script src="./main.js" type="module"></script>
<script src="./polyfills.js" type="module"></script>
<script>
const el = document.querySelector('web-component-lib'); // or set id to the dom and find the id.
el.addEventListener('onResult', (event) => {
console.log({event});
});
</script>
</body>
</html>
Modifying angular.json
Remove "outputHashing": "all" or set it to "none". If "outputHashing" is set to "all", the file names (e.g., main-[hash].js) will include a hash that changes with each build, which needs to be prevented.
Build Process
Build both the library and the application separately. Defining scripts in package.json can streamline this process.
"build:lib": "ng build web-component-lib --configuration production",
"build:app": "ng build web-component-app --configuration production",
Execution
To verify the proper functionality of the built application, locate the app within the dist folder and serve it using a local HTTP server (e.g., http-server). Simply opening the file directly will not work.
If http-server is unavailable, you can use a browser extension such as Simple WebServer.
Optimization
Examine the index.html file within the app folder in the dist directory. Notice that it references main.js and polyfills.js as scripts. If you have multiple libraries, this can lead to naming conflicts and inconvenience. Let’s improve this.
Adding a Script File
Create a scripts folder and add a file named postbuild-bundler.js.
async function loadModules() {
await import('./polyfills.js');
await import('./main.js');
}
loadModules();
Modifying angular.json
Update the angular.json file:
// angular.json
"scripts": [
{
"input": "./scripts/postbuild-bundler.js",
"bundleName": "bundle",
"inject": false
}
]
Modifying package.json
Add another script to package.json to execute the script file concurrently with the build process:
"build:all": "npm run build:lib && npm run build:app",
Build
Execute the following command to build the application and simultaneously generate the script file:
npm run build:all
Modifying index.html
Now, remove the script tags referencing main.js and polyfills.js from the index.html file within the app folder of the dist directory, and include only bundle.js.
...
<script src="bundle.js" type="module"></script>
<!-- remove those codes
<script src="./main.js" type="module"></script>
<script src="./polyfills.js" type="module"></script>
-->
...
Packaging
The library can be packaged separately, allowing for flexible configurations tailored to your environment.
Import Methods
For information on consuming Web Components within Angular applications, refer to the Web Components In Angular article. Additional documentation may be provided separately as required.
Considerations
To utilize the built Web Component, the main.js, polyfills.js, and bundle.js files must be included. Combining these into a single file would be more convenient, but a suitable solution has not yet been identified.
Referencing documentation suggesting the use of fs-extra and concat to merge main.js and polyfills.js into a single file resulted in errors due to duplicated variables, rendering it unusable.
댓글남기기