Browse Source

Add Angular support for Tabler Icons (#1091)

* Add icons-angular package (v0.0.0-alpha)

* Describe loading all icons in readme

* Support for Angular 18

* Update pnpm-lock.yaml

* Add @tabler/icons to dependencies in icons-angular

* Start creating test-angular app

* Update TypeScript version and Angular dependencies in icons-angular package

* Refactor clean scripts in package.json files to use find with -delete for improved performance and clarity

* Upgrade Angular dependencies to version 21, update TypeScript configuration, and enhance icon imports in the icons-angular package for improved module resolution and clarity.

* Refactor icons-angular package configuration by removing exports from package.json, adding type exports in public-api.ts, and updating TypeScript configuration to skip library checks and exclude spec files for cleaner builds.

* Refactor TablerIconComponent to use Angular's dependency injection for improved readability and maintainability, enhance error handling for icon input, and update README links for accuracy. Remove unnecessary paths from TypeScript configuration.

* Update TablerIconComponent to improve documentation and error handling, modify icon input in tests, and enhance child element removal logic for better performance.

* Enhance ngOnChanges method in TablerIconComponent to optimize updates based on relevant input changes, improve class handling by filtering empty strings, and ensure proper node checks for child elements.

* Add ChangeDetectionStrategy to TablerIconComponent for optimized rendering

* Add labeler configuration for Angular package to include file changes in 'packages/icons-angular/*'

* Angular package update to v21 (#1468)

* Update peer dependencies in package.json for Angular compatibility and refactor TablerIconComponent to utilize Angular's reactive features

* Update README to reflect Angular version 19.0.0 support and refactor TablerIconComponent tests

* Update TypeScript configuration in icons-angular package to target ES2022 and disable useDefineForClassFields for improved compatibility.

* Refactor TablerIcon configuration and provider in icons-angular package to use InjectionToken for improved flexibility and tree-shaking.

* Refactor TablerIconComponent to improve SVG rendering logic by separating icon creation and update processes, enhancing performance and maintainability. Introduce methods for resolving icons, checking SVG recreation conditions, and applying attributes and classes efficiently.

* Refactor TablerIconComponent to utilize Renderer2 for attribute and class manipulation, ensuring zoneless compatibility. Remove deprecated TablerIconConfig export and update module documentation for standalone application support.

* Remove old `test-angular` project

* Add new Angular project structure for test-angular application

- Introduced a new Angular application named 'test-angular' with essential configuration files including angular.json, package.json, and TypeScript configurations.
- Set up initial application structure with main entry point, styles, and a sample component demonstrating the use of Tabler icons.
- Configured build and serve options for development and production environments.
- Added .gitignore to exclude Angular build artifacts.

* Add more samples in test-angular project

* Remove unused Tabler icons-angular dependency from pnpm-lock.yaml to streamline project dependencies.

* Update package.json and tsconfig.json for Tabler icons-angular integration to use package from workspace

* Update package.json and tsconfig.json for Tabler icons-angular integration to use package from workspace

* Update peer dependencies for Angular to require version 21 or higher

* Upgrade Angular dependencies to version 21.1.x in package.json and pnpm-lock.yaml

* Provide icon configuration in test-angular project

* Update zone.js in test-angular to match version from the library

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Add skipLibCheck option to tsconfig.json for test-angular project

* Chang favicon to brand-angular tabler icon, set title for test-angular project

* Adjust TypeScript target and module settings in tsconfig.json for icons-angular package

* Refactor TablerIconComponent to include DestroyRef for cleanup and improve code formatting

* Update README.md for icons-angular package: change minimum Angular version to 21.0.0, enhance usage instructions for standalone and NgModule applications, and clarify global configuration options.

* Update `tslib` version to ^2.8.1 in icons-angular and test-angular projects

* Fix formatting in app.component.html by removing unnecessary whitespace in tabler-icon component

* Update `rxjs` version to ~7.8.2 in test-angular project

* Add package.json export and import resolution options in tsconfig.json for test-angular project

* Update packageManager version to pnpm@10.26.2 in package.json

---------

Co-authored-by: Bartłomiej Gawęda <bagaweda@gmail.com>
Co-authored-by: BG-Software <73077398+BG-Software-BG@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Remove standalone flag from TablerIconComponent and its related test components (default in Angular v21)

* Update TablerIconComponent to specify ElementRef type as HTMLElement for improved type safety and clarity

* Refactor: Remove content projection in `TablerIconComponent`

* Change `config` from `computed` to `signal` (depends only on non-reactive data)

* Change `toPascalCase` to a normal function; Format the `TablerIconComponent`

* Refactor TablerIconComponent: rename `class` to `svgClass` for clarity and update usage in app.component.html. Modify package.json script for development to include cleaning and installation steps.

* Refactor provideTablerIconConfig function to return EnvironmentProviders instead of Provider, enhancing compatibility with Angular's environment provider system.

* Remove TablerIconModule, update README for icon usage, and adjust tests to utilize provideTablerIcons for improved standalone application support.

* Refactor TablerIconProvider methods to use standard function syntax for improved readability and consistency.

* Update defaultAttributes.ts to use 'as const' for improved type safety and immutability.

* Remove unnecessary comments from TypeScript configuration files in the icons-angular package for cleaner code.

* Update README and component templates to use self-closing tags for tabler-icon

* Update TypeScript configuration in icons-angular package: remove sourceMap and downlevelIteration options for cleaner setup.

* Remove @prettier/sync dependency and icon template formatting

* Enhance TablerIconComponent: add svgAttributes for additional SVG element attributes and update README with new properties

* Update test-angular project to add aria-label and role attributes to tabler-icon

* Update package.json files for icons and icons-svelte-runes

* Update pnpm-lock.yaml to upgrade vitest dependencies and remove outdated versions

* Update packages/icons-angular/package.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update packages/icons-angular/build.mjs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Refactor TablerIconProvider to use an array for TABLER_ICONS injection token

Updated the TABLER_ICONS injection token to initialize with an empty array instead of a new TablerIconProvider instance, improving flexibility for icon management.

* Improve attribute handling in TablerIconComponent

Updated the attribute setting logic to check for both undefined and null values before applying attributes to the element, enhancing robustness in rendering.

* Refactor TablerIconComponent tests to use svgClass binding

Updated test specifications for TablerIconComponent to replace the class attribute with svgClass for better flexibility in styling. Added a new test case to verify stroke width handling.

* Update README.md to reflect svgClass usage in TablerIconComponent examples

* Update README to not include providing all icons as it is not recommended

* Remove duplications and improve README for icons-angular

* Change <a> to <button> in test-angular sample

* Add button type to buttons in test-angular and format the template

* Include Angular package in build:packages aggregate script (#1496)

* Initial plan

* Add build:angular to build:packages script in package.json

Co-authored-by: codecalm <1282324+codecalm@users.noreply.github.com>
Agent-Logs-Url: https://github.com/tabler/tabler-icons/sessions/6146caef-64c9-427b-880c-0a30bcd72de2

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: codecalm <1282324+codecalm@users.noreply.github.com>

---------

Co-authored-by: Bartłomiej Gawęda <bgaweda@abis.krakow.pl>
Co-authored-by: codecalm <codecalm@gmail.com>
Co-authored-by: Paweł Kuna <1282324+codecalm@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
pull/1484/head
BG-Software 7 months ago
committed by GitHub
parent
commit
d596cf3dfc
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      .github/labeler.yml
  2. 2
      .gitignore
  3. 3
      package.json
  4. 42
      packages/icons-angular/.gitignore
  5. 211
      packages/icons-angular/README.md
  6. 44
      packages/icons-angular/angular.json
  7. 33
      packages/icons-angular/build.mjs
  8. 39
      packages/icons-angular/karma.conf.js
  9. 7
      packages/icons-angular/ng-package.json
  10. 76
      packages/icons-angular/package.json
  11. 21
      packages/icons-angular/src/defaultAttributes.ts
  12. 0
      packages/icons-angular/src/icons/.gitkeep
  13. 77
      packages/icons-angular/src/lib/tabler-icon-filled.component.spec.ts
  14. 90
      packages/icons-angular/src/lib/tabler-icon-outline.component.spec.ts
  15. 299
      packages/icons-angular/src/lib/tabler-icon.component.optimization.spec.ts
  16. 273
      packages/icons-angular/src/lib/tabler-icon.component.ts
  17. 29
      packages/icons-angular/src/lib/tabler-icon.config.ts
  18. 45
      packages/icons-angular/src/lib/tabler-icon.provider.ts
  19. 6
      packages/icons-angular/src/public-api.ts
  20. 3
      packages/icons-angular/src/types.ts
  21. 28
      packages/icons-angular/tsconfig.json
  22. 11
      packages/icons-angular/tsconfig.lib.json
  23. 9
      packages/icons-angular/tsconfig.lib.prod.json
  24. 8
      packages/icons-angular/tsconfig.spec.json
  25. 8006
      pnpm-lock.yaml
  26. 1
      test/test-angular/.gitignore
  27. 78
      test/test-angular/angular.json
  28. 31
      test/test-angular/package.json
  29. 59
      test/test-angular/src/app/app.component.html
  30. 38
      test/test-angular/src/app/app.component.ts
  31. BIN
      test/test-angular/src/brand-angular.png
  32. 13
      test/test-angular/src/index.html
  33. 14
      test/test-angular/src/main.ts
  34. 67
      test/test-angular/src/styles.css
  35. 13
      test/test-angular/tsconfig.app.json
  36. 33
      test/test-angular/tsconfig.json

6
.github/labeler.yml

@ -80,6 +80,12 @@
- any-glob-to-any-file:
- 'packages/icons-sprite/*'
# For Angular package
🔗 angular package:
- changed-files:
- any-glob-to-any-file:
- 'packages/icons-angular/*'
# For React Native package
🔗 react-native package:
- changed-files:

2
.gitignore

@ -27,9 +27,9 @@ packages/icons*/icons/*
packages/icons*/src/icons/*
packages/icons*/src/icons.js
packages/icons*/src/icons.ts
packages/icons*/src/aliases.ts
packages/icons*/LICENSE
packages/icons*/stats/*
packages/icons*/icons/*
.turbo
.sass-cache

3
package.json

@ -33,8 +33,9 @@
"validate": "node ./.build/validate-icons.mjs",
"release": "git pull && release-it --verbose",
"build:copy": "rm -rf ./icons && mkdir ./icons && cp ./_site/tags.json tags.json && cp ./_site/icons/* ./icons/ && rm -rf ./_site/",
"build:packages": "pnpm run build:icons && pnpm run build:sprite && pnpm run build:react && pnpm run build:react-native && pnpm run build:preact && pnpm run build:solidjs && pnpm run build:svelte && pnpm run build:svelte-runes && pnpm run build:vue && pnpm run build:png && pnpm run build:pdf && pnpm run build:esp && pnpm run build:webfont",
"build:packages": "pnpm run build:icons && pnpm run build:sprite && pnpm run build:react && pnpm run build:react-native && pnpm run build:preact && pnpm run build:solidjs && pnpm run build:svelte && pnpm run build:svelte-runes && pnpm run build:vue && pnpm run build:angular && pnpm run build:png && pnpm run build:pdf && pnpm run build:esp && pnpm run build:webfont",
"build:icons": "pnpm --filter @tabler/icons build",
"build:angular": "pnpm --filter @tabler/icons-angular build",
"build:react": "pnpm --filter @tabler/icons-react build",
"build:react-native": "pnpm --filter @tabler/icons-react-native build",
"build:preact": "pnpm --filter @tabler/icons-preact build",

42
packages/icons-angular/.gitignore

@ -0,0 +1,42 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db

211
packages/icons-angular/README.md

@ -0,0 +1,211 @@
# Tabler Icons for Angular
<p align="center">
<img src="https://raw.githubusercontent.com/tabler/tabler-icons/main/.github/packages/og-package-angular.png" alt="Tabler Icons" width="838">
</p>
<p align="center">
Implementation of the Tabler Icons library for Angular applications.
</p>
<p align="center">
<a href="https://tabler.io/icons/"><strong>Browse all icons at tabler.io &rarr;</strong></a>
</p>
<p align="center">
<a href="https://github.com/tabler/tabler-icons/releases"><img src="https://img.shields.io/npm/v/@tabler/icons" alt="Latest Release"></a>
<a href="https://github.com/tabler/tabler-icons/blob/master/LICENSE"><img src="https://img.shields.io/npm/l/@tabler/icons.svg" alt="License"></a>
</p>
## Sponsors
**If you want to support our project and help us grow it, you can [become a sponsor on GitHub](https://github.com/sponsors/codecalm) or just [donate on PayPal](https://paypal.me/codecalm) :)**
<a href="https://github.com/sponsors/codecalm">
<img src='https://raw.githubusercontent.com/tabler/static/main/sponsors.png'>
</a>
## Prerequisites
- **Angular:** `>=21.0.0` (`@angular/core` and `@angular/common`).
## Installation
```
yarn add @tabler/icons-angular
```
or
```
npm install @tabler/icons-angular
```
or
```
pnpm add @tabler/icons-angular
```
## How to use
The package is built with ES modules and is tree-shakable. You choose which icons to include.
You can provide icons via `provideTablerIcons()`, or pass `TablerIcon` objects directly to the component.
Icons are exported as `Icon…` named symbols from `@tabler/icons-angular`. Legacy/alternate names are re-exported from the same package as additional `Icon…` symbols (e.g. `Icon123`).
### I. Using the provider (icon names in templates)
#### 1a. Standalone applications (recommended)
In `main.ts`:
```ts
import { provideTablerIcons, IconBrandAngular, IconHome } from '@tabler/icons-angular';
bootstrapApplication(AppComponent, {
providers: [
provideTablerIcons({ IconBrandAngular, IconHome })
]
});
```
Add any number of icons by including more `Icon*` imports in the object passed to `provideTablerIcons()`.
Or in a route configuration:
```ts
import { IconBrandAngular, provideTablerIcons } from '@tabler/icons-angular';
export const routes: Routes = [
{
path: 'demo',
component: DemoComponent,
providers: [provideTablerIcons({ IconBrandAngular })]
}
];
```
In any component that uses icons, import `TablerIconComponent` into that component’s `imports` array (standalone-style components, including the root app component):
```ts
import { TablerIconComponent } from '@tabler/icons-angular';
@Component({
imports: [TablerIconComponent],
// ...
})
export class DemoComponent {}
```
#### 1b. NgModule-based applications
Angular 21 still supports `NgModule`. Import `TablerIconComponent` into the module’s `imports` and register `provideTablerIcons()` / `provideTablerIconConfig()` in the module’s `providers` the same way as with `bootstrapApplication`.
```ts
import { TablerIconComponent, provideTablerIcons, IconBrandAngular, IconHome } from '@tabler/icons-angular';
@NgModule({
imports: [TablerIconComponent],
providers: [provideTablerIcons({ IconBrandAngular, IconHome })],
// ...
})
export class AppModule {}
```
#### 2. Use the icon in a template (by name)
```html
<tabler-icon icon="brand-angular" />
<tabler-icon icon="icon-brand-angular" />
```
### II. Passing an icon object (no provider)
#### 1. Import `TablerIconComponent` (standalone component or `NgModule`)
```ts
import { TablerIconComponent, IconBrandAngular } from '@tabler/icons-angular';
@Component({
imports: [TablerIconComponent],
// ...
})
export class AppComponent {
iconBrandAngular = IconBrandAngular;
}
```
If you use `NgModule`, import `TablerIconComponent` in the module’s `imports` instead. You do not need `provideTablerIcons()` when you only bind `[icon]` to imported `TablerIcon` objects.
#### 2. Use the icon in a template (by reference)
```html
<tabler-icon [icon]="iconBrandAngular" />
```
## Props
The component uses Angular `input()` APIs (signal inputs) and supports both outline and filled icon types.
| name | type | default |
| --------------- | --------------------------------- | ------------ |
| `icon` | _TablerIcon \| string_ | _(required)_ |
| `size` | _number_ | 24 |
| `color` | _string_ | currentColor |
| `stroke` | _number_ | 2 |
| `svgClass` | _string_ | — |
| `svgAttributes` | _Record<string, string \| number \| undefined>_ | — |
- **`icon`** — Icon to display. Pass a `TablerIcon` object (e.g. from an import) or a string name when using `provideTablerIcons()`.
- **`size`** — Width and height of the icon in pixels.
- **`color`** — For outline icons this sets the stroke color; for filled icons it sets the fill color.
- **`stroke`** — Stroke width for outline icons. Has no effect on filled icons.
- **`svgClass`** — Extra CSS classes to apply to the SVG element (in addition to `tabler-icon` and `tabler-icon-{name}`).
- **`svgAttributes`** — Extra attributes to apply to the SVG element (e.g. `aria-label`, `role`) for accessibility. Component-managed attributes (`size`, `color`, `stroke`, etc.) always take precedence and cannot be overridden.
```html
<tabler-icon icon="brand-angular" [size]="48" color="blue" [stroke]="1.75" svgClass="my-icon" />
<!-- When using a TablerIcon object, bind a component property (e.g. alarmIcon = IconAlarm in the class): -->
<tabler-icon [icon]="alarmIcon" [svgAttributes]="{ 'aria-label': 'Alarm', 'role': 'img' }" />
```
## Global configuration
To change default property values globally, use `provideTablerIconConfig()` in your providers. You can set any combination of `size`, `color`, and `stroke`.
```ts
import { provideTablerIconConfig, provideTablerIcons, IconHome } from '@tabler/icons-angular';
bootstrapApplication(AppComponent, {
providers: [
provideTablerIcons({ IconHome }),
provideTablerIconConfig({
size: 40,
stroke: 1,
color: 'blue'
})
]
});
```
Or only some defaults:
```ts
provideTablerIconConfig({ size: 40 })
```
## Contributing
For more info on how to contribute please see the [contribution guidelines](https://github.com/tabler/tabler-icons/blob/main/CONTRIBUTING.md).
Caught a mistake or want to contribute to the documentation? [Edit this page on Github](https://github.com/tabler/tabler-icons/blob/main/packages/icons-angular/README.md)
## License
Tabler Icons is licensed under the [MIT License](https://github.com/tabler/tabler-icons/blob/master/LICENSE).
## Sponsor Tabler
<a href="https://github.com/sponsors/codecalm" target="_blank"><img src="https://github.com/tabler/tabler/raw/dev/src/static/sponsor-banner-readme.png?raw=true" alt="Sponsor Tabler" /></a>

44
packages/icons-angular/angular.json

@ -0,0 +1,44 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"icons-angular": {
"projectType": "library",
"root": ".",
"sourceRoot": "src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"project": "ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"tsConfig": "tsconfig.spec.json",
"polyfills": [
"zone.js",
"zone.js/testing"
],
"karmaConfig": "karma.conf.js"
}
}
}
}
},
"cli": {
"analytics": false
}
}

33
packages/icons-angular/build.mjs

@ -0,0 +1,33 @@
import { buildJsIcons } from '../../.build/build-icons.mjs';
const componentTemplate = ({ type, name, namePascal, children }) => {
const nodes = JSON.stringify(Array.isArray(children) ? children : [children]);
return `import { TablerIcon } from '../types';
const ${namePascal}: TablerIcon = {
name: '${name}',
type: '${type}',
nodes: ${nodes}
};
export default ${namePascal};`
};
const indexItemTemplate = ({
_,
namePascal
}) => `export { default as Icon${namePascal} } from './Icon${namePascal}';`;
const aliasTemplate = ({ fromPascal, toPascal }) => `export { default as Icon${fromPascal} } from './icons/Icon${toPascal}';\n`;
buildJsIcons({
name: 'icons-angular',
componentTemplate,
indexItemTemplate,
aliasTemplate,
indexFile: 'index.ts',
pascalCase: true,
extension: 'ts',
pretty: false
});

39
packages/icons-angular/karma.conf.js

@ -0,0 +1,39 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true // removes the duplicated traces
},
coverageReporter: {
dir: require('path').join(__dirname, './coverage/icons-angular'),
subdir: '.',
reporters: [
{ type: 'html' },
{ type: 'text-summary' }
]
},
reporters: ['progress', 'kjhtml'],
browsers: ['Chrome'],
restartOnFileChange: true
});
};

7
packages/icons-angular/ng-package.json

@ -0,0 +1,7 @@
{
"$schema": "./node_modules/ng-packagr/ng-package.schema.json",
"dest": "dist",
"lib": {
"entryFile": "src/public-api.ts"
}
}

76
packages/icons-angular/package.json

@ -0,0 +1,76 @@
{
"name": "@tabler/icons-angular",
"version": "3.7.0-alpha.1",
"license": "MIT",
"description": "A set of free MIT-licensed high-quality SVG icons for you to use in your Angular projects.",
"homepage": "https://tabler.io/icons",
"bugs": {
"url": "https://github.com/tabler/tabler-icons/issues"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/codecalm"
},
"repository": {
"type": "git",
"url": "git+https://github.com/tabler/tabler-icons.git",
"directory": "packages/icons-angular"
},
"publishConfig": {
"access": "public"
},
"files": [
"dist",
"README.md",
"LICENSE"
],
"keywords": [
"angular",
"icons",
"svg",
"tabler",
"ui",
"icon-pack"
],
"sideEffects": false,
"scripts": {
"build": "pnpm run clean && pnpm run copy:license && pnpm run build:icons && pnpm run typecheck && pnpm run build:ng",
"build:icons": "node build.mjs",
"build:ng": "ng build --configuration=production",
"copy:license": "cp ../../LICENSE ./LICENSE",
"clean": "rm -rf dist && find src/icons -mindepth 1 ! -name '.gitkeep' -delete",
"typecheck": "tsc",
"test": "ng test --no-watch --no-progress",
"test:watch": "ng test"
},
"main": "./dist/fesm2022/tabler-icons-angular.mjs",
"module": "./dist/fesm2022/tabler-icons-angular.mjs",
"types": "./dist/types/tabler-icons-angular.d.ts",
"devDependencies": {
"@angular-devkit/build-angular": "^21.1.2",
"@angular/animations": "^21.1.1",
"@angular/cli": "^21.1.2",
"@angular/common": "^21.1.1",
"@angular/compiler": "^21.1.1",
"@angular/compiler-cli": "^21.1.1",
"@angular/core": "^21.1.1",
"@angular/platform-browser": "^21.1.1",
"@angular/platform-browser-dynamic": "^21.1.1",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.1.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"ng-packagr": "^21.1.0",
"tslib": "^2.8.1",
"typescript": "~5.9.3",
"zone.js": "~0.14.10"
},
"peerDependencies": {
"@angular/common": ">=21.0.0",
"@angular/core": ">=21.0.0",
"@tabler/icons": "^3.7.0"
}
}

21
packages/icons-angular/src/defaultAttributes.ts

@ -0,0 +1,21 @@
export default {
outline: {
xmlns: 'http://www.w3.org/2000/svg',
width: 24,
height: 24,
viewBox: '0 0 24 24',
fill: 'none',
stroke: 'currentColor',
'stroke-width': 2,
'stroke-linecap': 'round',
'stroke-linejoin': 'round'
},
filled: {
xmlns: 'http://www.w3.org/2000/svg',
width: 24,
height: 24,
viewBox: '0 0 24 24',
fill: 'currentColor',
stroke: 'none'
}
} as const;

0
packages/icons-angular/src/icons/.gitkeep

77
packages/icons-angular/src/lib/tabler-icon-filled.component.spec.ts

@ -0,0 +1,77 @@
import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import defaultAttributes from '../defaultAttributes';
import { TablerIcon, TablerIcons } from '../types';
import { TablerIconComponent } from './tabler-icon.component';
import { provideTablerIcons } from './tabler-icon.provider';
describe('TablerIconComponent', () => {
let hostComponent: TestHostComponent;
let fixture: ComponentFixture<TestHostComponent>;
const getSvgAttr = (attr: string) => fixture.nativeElement.querySelector('svg').getAttribute(attr);
let icon: TablerIcon = { name: 'test', type: 'filled', nodes: [['path', { d: 'M8 7h-4', key: 'svg-0' }]] };
let icons: TablerIcons = { IconTest: icon };
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TablerIconComponent, TestHostComponent],
providers: [provideTablerIcons(icons)]
}).compileComponents();
fixture = TestBed.createComponent(TestHostComponent);
hostComponent = fixture.componentInstance;
})
it('should create', () => {
fixture.detectChanges();
expect(hostComponent).toBeTruthy();
});
it('should have all default values if not provided', () => {
fixture.detectChanges();
expect(getSvgAttr('width')).toBe(defaultAttributes.filled.width.toString(10));
expect(getSvgAttr('fill')).toBe(defaultAttributes.filled.fill);
expect(getSvgAttr('stroke')).toBe(defaultAttributes.filled.stroke);
expect(getSvgAttr('stroke-width')).toBe(null);
expect(getSvgAttr('stroke-linecap')).toBe(null);
expect(getSvgAttr('stroke-linejoin')).toBe(null);
});
it('should set size', () => {
const size = 40;
hostComponent.size = size;
fixture.detectChanges();
expect(getSvgAttr('height')).toBe(size.toString(10));
expect(getSvgAttr('width')).toBe(size.toString(10));
})
it('should set color', () => {
const color = '#abcabc';
hostComponent.color = color;
fixture.detectChanges();
expect(getSvgAttr('fill')).toBe(color);
})
it('should add all classes', () => {
fixture.detectChanges();
expect(getSvgAttr('class')).toBe('tabler-icon tabler-icon-test test-class');
})
@Component({
selector: 'tabler-test',
imports: [TablerIconComponent],
template: `
<tabler-icon
icon="test"
[svgClass]="customClass"
[color]="color"
[size]="size"
/>
`,
})
class TestHostComponent {
size?: number;
color?: string;
customClass = 'test-class';
}
})

90
packages/icons-angular/src/lib/tabler-icon-outline.component.spec.ts

@ -0,0 +1,90 @@
import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import defaultAttributes from '../defaultAttributes';
import { TablerIcon } from '../types';
import { TablerIconComponent } from './tabler-icon.component';
describe('TablerIconComponent', () => {
let hostComponent: TestHostComponent;
let fixture: ComponentFixture<TestHostComponent>;
const getSvgAttr = (attr: string) => fixture.nativeElement.querySelector('svg').getAttribute(attr);
let icon: TablerIcon = { name: 'test', type: 'outline', nodes: [['path', { d: 'M8 7h-4', key: 'svg-0' }]] };
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TablerIconComponent, TestHostComponent]
}).compileComponents();
fixture = TestBed.createComponent(TestHostComponent);
hostComponent = fixture.componentInstance;
})
it('should create', () => {
fixture.detectChanges();
expect(hostComponent).toBeTruthy();
});
it('should have all default values if not provided', () => {
fixture.detectChanges();
expect(getSvgAttr('width')).toBe(defaultAttributes.outline.width.toString(10));
expect(getSvgAttr('fill')).toBe(defaultAttributes.outline.fill);
expect(getSvgAttr('stroke')).toBe(defaultAttributes.outline.stroke);
expect(getSvgAttr('stroke-width')).toBe(defaultAttributes.outline['stroke-width'].toString(10));
expect(getSvgAttr('stroke-linecap')).toBe(defaultAttributes.outline['stroke-linecap']);
expect(getSvgAttr('stroke-linejoin')).toBe(defaultAttributes.outline['stroke-linejoin']);
});
it('should set size', () => {
const size = 32;
hostComponent.size = size;
fixture.detectChanges();
expect(getSvgAttr('height')).toBe(size.toString(10));
expect(getSvgAttr('width')).toBe(size.toString(10));
})
it('should set color', () => {
const color = '#abcabc';
hostComponent.color = color;
fixture.detectChanges();
expect(getSvgAttr('stroke')).toBe(color);
})
it('should set stroke width', () => {
const stroke = 1.75;
hostComponent.stroke = stroke;
fixture.detectChanges();
expect(getSvgAttr('stroke-width')).toBe(stroke.toString(10));
})
it('should set stroke width to 0', () => {
hostComponent.stroke = 0;
fixture.detectChanges();
expect(getSvgAttr('stroke-width')).toBe('0');
})
it('should add all classes', () => {
fixture.detectChanges();
expect(getSvgAttr('class')).toBe('tabler-icon tabler-icon-test test-class');
})
@Component({
selector: 'tabler-test',
imports: [TablerIconComponent],
template: `
<tabler-icon
[icon]="iconTest"
[svgClass]="customClass"
[color]="color"
[size]="size"
[stroke]="stroke"
/>
`,
})
class TestHostComponent {
size?: number;
color?: string;
stroke?: number;
customClass = 'test-class';
iconTest = icon;
}
})

299
packages/icons-angular/src/lib/tabler-icon.component.optimization.spec.ts

@ -0,0 +1,299 @@
import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TablerIcon, TablerIcons } from '../types';
import { TablerIconComponent } from './tabler-icon.component';
import { provideTablerIcons } from './tabler-icon.provider';
describe('TablerIconComponent - Optimization & Edge Cases', () => {
let hostComponent: TestHostComponent;
let fixture: ComponentFixture<TestHostComponent>;
let component: TablerIconComponent;
const getSvgAttr = (attr: string) => fixture.nativeElement.querySelector('svg')?.getAttribute(attr);
const getSvgElement = () => fixture.nativeElement.querySelector('svg');
let icon: TablerIcon = { name: 'test', type: 'outline', nodes: [['path', { d: 'M8 7h-4', key: 'svg-0' }]] };
let icons: TablerIcons = { IconTest: icon };
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TablerIconComponent, TestHostComponent],
providers: [provideTablerIcons(icons)]
}).compileComponents();
fixture = TestBed.createComponent(TestHostComponent);
hostComponent = fixture.componentInstance;
component = fixture.debugElement.query(p => p.componentInstance instanceof TablerIconComponent)?.componentInstance as TablerIconComponent;
});
describe('ChangeDetectionStrategy.OnPush', () => {
it('should render icon correctly with OnPush strategy', () => {
fixture.detectChanges();
expect(getSvgElement()).toBeTruthy();
expect(getSvgAttr('class')).toContain('tabler-icon');
});
it('should update when input changes', () => {
fixture.detectChanges();
const initialColor = getSvgAttr('stroke');
hostComponent.color = '#ff0000';
fixture.detectChanges();
expect(getSvgAttr('stroke')).toBe('#ff0000');
expect(getSvgAttr('stroke')).not.toBe(initialColor);
});
it('should update DOM when size input changes', () => {
fixture.detectChanges();
const initialSize = getSvgAttr('width');
hostComponent.size = 100;
fixture.detectChanges();
expect(getSvgAttr('width')).toBe('100');
expect(getSvgAttr('height')).toBe('100');
});
});
describe('Signal reactivity and optimization', () => {
it('should skip update when same values are set', () => {
fixture.detectChanges();
const initialSvg = getSvgElement();
const initialStroke = getSvgAttr('stroke');
// Signal inputs handle equality checks automatically
hostComponent.color = '#ff0000';
fixture.detectChanges();
expect(getSvgAttr('stroke')).toBe('#ff0000');
expect(getSvgAttr('stroke')).not.toBe(initialStroke);
});
it('should update when icon changes', () => {
fixture.detectChanges();
const initialName = getSvgAttr('class');
hostComponent.iconName = 'test';
fixture.detectChanges();
expect(getSvgElement()).toBeTruthy();
expect(getSvgAttr('class')).toContain('tabler-icon-test');
});
it('should update when size changes', () => {
fixture.detectChanges();
const initialSize = getSvgAttr('width');
hostComponent.size = 48;
fixture.detectChanges();
expect(getSvgAttr('width')).toBe('48');
expect(getSvgAttr('width')).not.toBe(initialSize);
});
it('should update when stroke changes', () => {
fixture.detectChanges();
const initialStrokeWidth = getSvgAttr('stroke-width');
hostComponent.stroke = 3;
fixture.detectChanges();
expect(getSvgAttr('stroke-width')).toBe('3');
expect(getSvgAttr('stroke-width')).not.toBe(initialStrokeWidth);
});
it('should update when class changes', () => {
fixture.detectChanges();
hostComponent.customClass = 'new-class another-class';
fixture.detectChanges();
const classAttr = getSvgAttr('class');
expect(classAttr).toContain('new-class');
expect(classAttr).toContain('another-class');
});
});
describe('Error handling', () => {
it('should throw error when icon name is empty string', () => {
hostComponent.iconName = '';
expect(() => fixture.detectChanges()).toThrowError('Icon name cannot be empty.');
});
it('should throw error when icon name is whitespace only', () => {
hostComponent.iconName = ' ';
expect(() => fixture.detectChanges()).toThrowError('Icon name cannot be empty.');
});
it('should throw error when icon is not provided by any provider', () => {
hostComponent.iconName = 'non-existent-icon';
expect(() => fixture.detectChanges()).toThrowError(/The non-existent-icon icon is not provided/);
});
it('should throw error when icon object is invalid', () => {
hostComponent.iconObject = null as any;
hostComponent.iconName = undefined;
expect(() => fixture.detectChanges()).toThrowError('Icon must be provided as a TablerIcon object or a string name.');
});
it('should throw error when icon type is invalid', () => {
hostComponent.iconObject = { name: 'test', type: 'invalid' as any, nodes: [] };
expect(() => fixture.detectChanges()).toThrowError(/Invalid icon type: invalid/);
});
it('should throw error when icon object has no nodes', () => {
hostComponent.iconObject = { name: 'test', type: 'outline', nodes: null as any };
hostComponent.iconName = undefined;
expect(() => fixture.detectChanges()).toThrowError('Icon must be provided as a TablerIcon object or a string name.');
});
});
describe('CSS classes handling', () => {
it('should handle multiple classes separated by spaces', () => {
hostComponent.customClass = 'class1 class2 class3';
fixture.detectChanges();
const classAttr = getSvgAttr('class');
expect(classAttr).toContain('class1');
expect(classAttr).toContain('class2');
expect(classAttr).toContain('class3');
});
it('should handle classes with multiple spaces', () => {
hostComponent.customClass = 'class1 class2 class3';
fixture.detectChanges();
const classAttr = getSvgAttr('class');
expect(classAttr).toContain('class1');
expect(classAttr).toContain('class2');
expect(classAttr).toContain('class3');
});
it('should handle classes with leading and trailing spaces', () => {
hostComponent.customClass = ' class1 class2 ';
fixture.detectChanges();
const classAttr = getSvgAttr('class');
expect(classAttr).toContain('class1');
expect(classAttr).toContain('class2');
});
it('should filter out empty class names', () => {
hostComponent.customClass = 'class1 class2';
fixture.detectChanges();
const classAttr = getSvgAttr('class');
const classes = classAttr?.split(' ') || [];
expect(classes.filter((c: string) => c.length === 0).length).toBe(0);
});
it('should add default tabler-icon classes', () => {
fixture.detectChanges();
const classAttr = getSvgAttr('class');
expect(classAttr).toContain('tabler-icon');
expect(classAttr).toContain('tabler-icon-test');
});
});
describe('Icon name conversion (toPascalCase)', () => {
it('should convert kebab-case to PascalCase', () => {
hostComponent.iconName = 'brand-angular';
// The component should look for IconBrandAngular in providers
// Since we only have IconTest, this will throw, but we can verify the conversion happens
expect(() => fixture.detectChanges()).toThrowError(/The brand-angular icon is not provided/);
});
it('should handle single word icon names', () => {
hostComponent.iconName = 'home';
expect(() => fixture.detectChanges()).toThrowError(/The home icon is not provided/);
});
it('should handle icon names with multiple hyphens', () => {
hostComponent.iconName = 'brand-angular-outline';
expect(() => fixture.detectChanges()).toThrowError(/The brand-angular-outline icon is not provided/);
});
});
describe('Icon types', () => {
it('should render outline icon correctly', () => {
hostComponent.iconObject = { name: 'outline-test', type: 'outline', nodes: [['path', { d: 'M8 7h-4', key: 'svg-0' }]] };
fixture.detectChanges();
expect(getSvgAttr('stroke')).toBeTruthy();
expect(getSvgAttr('stroke-width')).toBeTruthy();
});
it('should render filled icon correctly', () => {
hostComponent.iconObject = { name: 'filled-test', type: 'filled', nodes: [['path', { d: 'M8 7h-4', key: 'svg-0' }]] };
fixture.detectChanges();
expect(getSvgAttr('fill')).toBeTruthy();
});
});
describe('Size handling', () => {
it('should use default size when not provided', () => {
hostComponent.size = undefined;
fixture.detectChanges();
expect(getSvgAttr('width')).toBeTruthy();
expect(getSvgAttr('height')).toBeTruthy();
});
it('should apply size to both width and height', () => {
hostComponent.size = 64;
fixture.detectChanges();
expect(getSvgAttr('width')).toBe('64');
expect(getSvgAttr('height')).toBe('64');
});
});
describe('Color handling', () => {
it('should use default color when not provided for outline icon', () => {
hostComponent.color = undefined;
fixture.detectChanges();
expect(getSvgAttr('stroke')).toBeTruthy();
});
it('should use provided color for outline icon', () => {
hostComponent.color = '#ff5733';
fixture.detectChanges();
expect(getSvgAttr('stroke')).toBe('#ff5733');
});
it('should use provided color for filled icon', () => {
hostComponent.iconObject = { name: 'filled-test', type: 'filled', nodes: [['path', { d: 'M8 7h-4', key: 'svg-0' }]] };
hostComponent.color = '#ff5733';
fixture.detectChanges();
expect(getSvgAttr('fill')).toBe('#ff5733');
});
});
@Component({
selector: 'tabler-test',
imports: [TablerIconComponent],
template: `
<tabler-icon
[icon]="iconObject || iconName"
[color]="color"
[size]="size"
[stroke]="stroke"
[svgClass]="customClass"
/>
`,
})
class TestHostComponent {
size?: number;
color?: string;
stroke?: number;
customClass?: string;
iconName?: string = 'test';
iconObject?: TablerIcon;
}
});

273
packages/icons-angular/src/lib/tabler-icon.component.ts

@ -0,0 +1,273 @@
import {
ChangeDetectionStrategy,
Component,
DestroyRef,
effect,
ElementRef,
inject,
input,
Renderer2,
signal,
} from '@angular/core';
import defaultAttributes from '../defaultAttributes';
import { TablerIcon, TablerIconNode } from '../types';
import { TABLER_ICON_CONFIG } from './tabler-icon.config';
import { ITablerIconProvider, TABLER_ICONS } from './tabler-icon.provider';
type SvgAttributes = { [key: string]: string | number | undefined };
/**
* Component for rendering Tabler icons in Angular applications.
* Supports both outline and filled icon types.
*
* @example
* ```html
* <!-- Using icon name (requires provideTablerIcons) -->
* <tabler-icon icon="home" [size]="24" color="blue" />
*
* <!-- Using direct icon object (best for tree-shaking) -->
* <tabler-icon [icon]="IconHome" [size]="24" />
* ```
*/
@Component({
selector: 'tabler-icon',
template: '',
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
'[style.height.px]': 'size() ?? config().size',
'[style.width.px]': 'size() ?? config().size',
},
})
export class TablerIconComponent {
/**
* Icon to display. Can be a TablerIcon object or a string name.
* @required
*/
icon = input.required<TablerIcon | string>();
/**
* Color of the icon. For outline icons, this sets the stroke color.
* For filled icons, this sets the fill color.
*/
color = input<string>();
/**
* Stroke width for outline icons. Defaults to 2.
*/
stroke = input<number>();
/**
* Additional CSS classes to apply to the icon element.
*/
svgClass = input<string>();
size = input<number>();
/**
* Additional attributes to apply to the SVG element (e.g. aria-label, role).
* Component-managed attributes (size, color, stroke, etc.) always take precedence.
*/
svgAttributes = input<Record<string, string | number | undefined>>();
private readonly renderer = inject(Renderer2);
private readonly iconProviders = inject<ITablerIconProvider[]>(TABLER_ICONS);
private readonly elementRef = inject(ElementRef<HTMLElement>);
private readonly iconConfig = inject(TABLER_ICON_CONFIG, { optional: true });
private readonly destroyRef = inject(DestroyRef);
protected readonly config = signal({
size: defaultAttributes.outline.width,
color: defaultAttributes.outline.stroke,
stroke: defaultAttributes.outline['stroke-width'],
...(this.iconConfig ?? {}),
});
private svgElement: SVGElement | null = null;
constructor() {
this.destroyRef.onDestroy(() => {
this.svgElement = null;
});
effect(() => {
const iconData = this.icon();
const color = this.color();
const stroke = this.stroke();
const size = this.size();
const className = this.svgClass();
const extraAttributes = this.svgAttributes();
const icon = this.resolveIcon(iconData);
if (!this.svgElement || this.shouldRecreateSvg(icon)) {
this.renderNewSvg(icon, color, stroke, size, className, extraAttributes);
} else {
this.updateExistingSvg(icon, color, stroke, size, className, extraAttributes);
}
});
}
private resolveIcon(iconData: TablerIcon | string): TablerIcon {
if (typeof iconData === 'string') {
if (!iconData || iconData.trim() === '') {
throw new Error('Icon name cannot be empty.');
}
const foundIcon = this.getIconFromProviders(this.toPascalCase(iconData));
if (foundIcon) {
return foundIcon;
} else {
throw new Error(`The ${iconData} icon is not provided by any of the icon providers.`);
}
} else if (iconData != null && Array.isArray(iconData.nodes)) {
return iconData;
} else {
throw new Error('Icon must be provided as a TablerIcon object or a string name.');
}
}
private shouldRecreateSvg(icon: TablerIcon): boolean {
if (!this.svgElement) return true;
// If the icon name or type changed, SVG structure has to be recreated
const currentIconName = this.svgElement.getAttribute('data-tabler-icon');
const currentIconType = this.svgElement.getAttribute('data-tabler-type');
return currentIconName !== icon.name || currentIconType !== icon.type;
}
private renderNewSvg(
icon: TablerIcon,
color?: string,
stroke?: number,
size?: number,
className?: string,
extraAttributes?: Record<string, string | number | undefined>,
): void {
if (icon.type !== 'outline' && icon.type !== 'filled') {
throw new Error(`Invalid icon type: ${icon.type}. Must be 'outline' or 'filled'.`);
}
const attributes = this.getSvgAttributes(icon, color, stroke, size, extraAttributes);
const iconElement = this.createElement(['svg', attributes, icon.nodes]);
// Use Renderer2 for attribute setting to ensure zoneless compatibility
this.renderer.setAttribute(iconElement, 'data-tabler-icon', icon.name);
this.renderer.setAttribute(iconElement, 'data-tabler-type', icon.type);
this.applyClasses(iconElement, icon.name, className);
// Remove only previous SVG, preserve ng-content
if (this.svgElement) {
this.renderer.removeChild(this.elementRef.nativeElement, this.svgElement);
}
this.svgElement = iconElement;
this.renderer.appendChild(this.elementRef.nativeElement, iconElement);
}
private updateExistingSvg(
icon: TablerIcon,
color?: string,
stroke?: number,
size?: number,
className?: string,
extraAttributes?: Record<string, string | number | undefined>,
): void {
if (!this.svgElement) return;
const attributes = this.getSvgAttributes(icon, color, stroke, size, extraAttributes);
Object.entries(attributes).forEach(([key, value]) => {
if (value !== undefined) {
this.renderer.setAttribute(this.svgElement!, key, value.toString());
}
});
this.applyClasses(this.svgElement, icon.name, className);
}
private getSvgAttributes(
icon: TablerIcon,
color?: string,
stroke?: number,
size?: number,
extraAttributes?: Record<string, string | number | undefined>,
): SvgAttributes {
const typeAttributes =
icon.type === 'outline'
? {
stroke: color ?? this.config().color,
'stroke-width': stroke ?? this.config().stroke,
}
: {
fill: color ?? this.config().color,
};
const componentAttributes: SvgAttributes = {
...defaultAttributes[icon.type],
...typeAttributes,
width: size ?? this.config().size,
height: size ?? this.config().size,
};
return {
...(extraAttributes ?? {}),
...componentAttributes,
};
}
private applyClasses(element: SVGElement, iconName: string, className?: string): void {
// Reset classes to base using Renderer2 for zoneless compatibility
const baseClasses = `tabler-icon tabler-icon-${iconName}`;
this.renderer.setAttribute(element, 'class', baseClasses);
if (className) {
const classes = className
.trim()
.split(/\s+/)
.filter((cls) => cls.length > 0);
if (classes.length > 0) {
// Use Renderer2 for class manipulation to ensure zoneless compatibility
classes.forEach((cls) => this.renderer.addClass(element, cls));
}
}
}
private createElement([tag, attributes, nodes]: readonly [
string,
SvgAttributes,
TablerIconNode[]?,
]) {
const element = this.renderer.createElement(tag, 'http://www.w3.org/2000/svg');
Object.keys(attributes).forEach((key) => {
const attrValue = attributes[key];
if (attrValue !== undefined && attrValue !== null) {
const value = typeof attrValue === 'string' ? attrValue : attrValue.toString();
this.renderer.setAttribute(element, key, value);
}
});
if (nodes && nodes.length > 0) {
nodes.forEach((node: TablerIconNode) => {
const childElement = this.createElement(node);
this.renderer.appendChild(element, childElement);
});
}
return element;
}
private getIconFromProviders(iconName: string): TablerIcon | null {
for (const provider of this.iconProviders) {
const icon = provider.getIcon(iconName);
if (icon) return icon;
}
return null;
}
private toPascalCase(value: string): string {
return value
.split('-')
.map((v) => v.charAt(0).toUpperCase() + v.slice(1).toLowerCase())
.join('');
}
}

29
packages/icons-angular/src/lib/tabler-icon.config.ts

@ -0,0 +1,29 @@
import { EnvironmentProviders, InjectionToken, makeEnvironmentProviders } from '@angular/core';
export interface TablerIconConfig {
size?: number;
color?: string;
stroke?: number;
}
export const TABLER_ICON_CONFIG = new InjectionToken<TablerIconConfig>('TablerIconConfig');
/**
* Provides the configuration for Tabler icons.
*
* @example
* ```ts
* bootstrapApplication(AppComponent, {
* providers: [
* provideTablerIconConfig({
* size: 24,
* color: 'red',
* stroke: 2
* })
* ]
* });
* ```
*/
export function provideTablerIconConfig(config: TablerIconConfig): EnvironmentProviders {
return makeEnvironmentProviders([{ provide: TABLER_ICON_CONFIG, useValue: config }]);
}

45
packages/icons-angular/src/lib/tabler-icon.provider.ts

@ -0,0 +1,45 @@
import { EnvironmentProviders, InjectionToken, makeEnvironmentProviders } from '@angular/core';
import { TablerIcon, TablerIcons } from '../types';
export interface ITablerIconProvider {
getIcon(name: string): TablerIcon | null;
}
export class TablerIconProvider implements ITablerIconProvider {
constructor(private readonly icons: TablerIcons) {}
getIcon(name: string): TablerIcon | null {
name = name.startsWith('Icon') ? name : `Icon${name}`;
return this.iconExists(name) ? this.icons[name] : null;
}
private iconExists(name: string): boolean {
return name in this.icons;
}
}
export const TABLER_ICONS = new InjectionToken<ITablerIconProvider[]>('TablerIcons', {
factory: () => []
});
/**
* Provides a set of Tabler icons to the application.
*
* @example
* ```ts
* bootstrapApplication(AppComponent, {
* providers: [
* provideTablerIcons({ IconHome, IconUser })
* ]
* });
* ```
*/
export function provideTablerIcons(icons: TablerIcons): EnvironmentProviders {
return makeEnvironmentProviders([
{
provide: TABLER_ICONS,
multi: true,
useValue: new TablerIconProvider(icons)
}
]);
}

6
packages/icons-angular/src/public-api.ts

@ -0,0 +1,6 @@
export * from './types';
export * from './lib/tabler-icon.component';
export * from './lib/tabler-icon.config';
export * from './lib/tabler-icon.provider';
export * from './icons';
export * from './aliases';

3
packages/icons-angular/src/types.ts

@ -0,0 +1,3 @@
export type TablerIconNode = [elementName: string, attrs: Record<string, string>];
export type TablerIcon = { name: string, type: 'outline' | 'filled', nodes: TablerIconNode[] };
export type TablerIcons = { [key: string]: TablerIcon };

28
packages/icons-angular/tsconfig.json

@ -0,0 +1,28 @@
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"declaration": false,
"moduleResolution": "bundler",
"resolvePackageJsonExports": true,
"resolvePackageJsonImports": true,
"importHelpers": true,
"target": "es2022",
"useDefineForClassFields": false,
"module": "es2022",
"lib": ["es2022", "dom"],
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true,
},
}

11
packages/icons-angular/tsconfig.lib.json

@ -0,0 +1,11 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/lib",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": []
},
"exclude": ["**/*.spec.ts"]
}

9
packages/icons-angular/tsconfig.lib.prod.json

@ -0,0 +1,9 @@
{
"extends": "./tsconfig.lib.json",
"compilerOptions": {
"declarationMap": false
},
"angularCompilerOptions": {
"compilationMode": "partial"
}
}

8
packages/icons-angular/tsconfig.spec.json

@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": ["jasmine"]
},
"include": ["**/*.spec.ts", "**/*.d.ts"]
}

8006
pnpm-lock.yaml

File diff suppressed because it is too large

1
test/test-angular/.gitignore

@ -0,0 +1 @@
/.angular/

78
test/test-angular/angular.json

@ -0,0 +1,78 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"test-angular": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "css",
"standalone": true
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": [
"zone.js"
],
"tsConfig": "tsconfig.app.json",
"assets": [
"src/brand-angular.png"
],
"styles": [
"src/styles.css"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kB",
"maximumError": "1MB"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kB",
"maximumError": "4kB"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "development"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "test-angular:build:production"
},
"development": {
"buildTarget": "test-angular:build:development"
}
},
"defaultConfiguration": "development"
}
}
}
},
"cli": {
"analytics": false
}
}

31
test/test-angular/package.json

@ -0,0 +1,31 @@
{
"name": "test-angular",
"private": true,
"version": "3.36.0",
"type": "module",
"scripts": {
"dev": "pnpm run clean && pnpm install && ng serve",
"build": "ng build",
"preview": "ng serve --configuration=production",
"clean": "rm -rf dist .angular"
},
"dependencies": {
"@angular/animations": "^21.0.6",
"@angular/common": "^21.0.6",
"@angular/compiler": "^21.0.6",
"@angular/core": "^21.0.6",
"@angular/platform-browser": "^21.0.6",
"@angular/platform-browser-dynamic": "^21.0.6",
"@tabler/icons-angular": "workspace:*",
"rxjs": "~7.8.2",
"tslib": "^2.8.1",
"zone.js": "~0.14.10"
},
"devDependencies": {
"@angular-devkit/build-angular": "^21.0.4",
"@angular/cli": "^21.0.4",
"@angular/compiler-cli": "^21.0.6",
"@types/node": "^20.11.0",
"typescript": "~5.9.3"
}
}

59
test/test-angular/src/app/app.component.html

@ -0,0 +1,59 @@
<div class="app">
<h2>Tabler Icons Angular Package</h2>
<div style="margin-top: 40px">
<h3>Different strokes:</h3>
<tabler-icon [icon]="iconAd" [size]="mySize()" [stroke]="0.5" />
<tabler-icon [icon]="iconAd" [size]="mySize()" [stroke]="1" />
<tabler-icon [icon]="iconAd" [size]="mySize()" [stroke]="1.5" />
<tabler-icon [icon]="iconAd" [size]="mySize()" [stroke]="2" />
</div>
<div style="margin-top: 20px">
<h3>Different sizes:</h3>
<tabler-icon [icon]="iconAd" [size]="24" />
<tabler-icon [icon]="iconAd" [size]="32" />
<tabler-icon [icon]="iconAd" [size]="48" />
<tabler-icon [icon]="iconAd" [size]="64" />
</div>
<div style="margin-top: 20px">
<h3>Different colors:</h3>
<tabler-icon [icon]="iconAd" color="blue" />
<tabler-icon [icon]="iconAd" color="red" />
<tabler-icon [icon]="iconAd" color="green" />
<tabler-icon [icon]="iconAd" color="purple" />
</div>
<div>
<h3>Buttons</h3>
<div class="buttons">
<button (click)="toggleActive()" style="cursor: pointer" type="button">
@if (active()) {
<tabler-icon [icon]="iconHome" [size]="32" />
} @else {
<tabler-icon [icon]="iconHomeOff" [size]="32" />
}
</button>
<button (click)="toggleIcon()" style="cursor: pointer" type="button">
<tabler-icon [icon]="toggledIcon()" [size]="32" />
</button>
<button (click)="toggleStroke()" style="cursor: pointer" type="button">
<tabler-icon
[icon]="iconHome"
[size]="32"
[stroke]="stroke()"
[svgAttributes]="{ 'aria-label': 'Home', role: 'button' }"
/>
</button>
</div>
</div>
<div style="margin-top: 20px">
<h3>Using string names (requires provider):</h3>
<tabler-icon icon="brand-tabler" svgClass="olive" />
<tabler-icon icon="brand-tabler-filled" />
<tabler-icon icon="brand-tabler" [stroke]="1" />
<tabler-icon icon="brand-tabler-filled" color="orange" />
</div>
</div>

38
test/test-angular/src/app/app.component.ts

@ -0,0 +1,38 @@
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import {
IconAd,
IconHome,
IconHomeOff,
TablerIcon,
TablerIconComponent
} from '@tabler/icons-angular';
@Component({
selector: 'app-root',
imports: [TablerIconComponent],
templateUrl: './app.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {
active = signal(false);
iconAd = IconAd;
iconHome = IconHome;
iconHomeOff = IconHomeOff;
toggledIcon = signal<TablerIcon>(IconHome);
mySize = signal(56);
stroke = signal(1);
toggleIcon() {
this.toggledIcon.update(value => value === IconHome ? IconHomeOff : IconHome);
}
toggleStroke() {
this.stroke.update(value => value === 2.5 ? 1 : value + 0.25);
}
toggleActive() {
this.active.update(value => !value);
}
}

BIN
test/test-angular/src/brand-angular.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

13
test/test-angular/src/index.html

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>icons-angular test project</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="brand-angular.png">
</head>
<body>
<app-root></app-root>
</body>
</html>

14
test/test-angular/src/main.ts

@ -0,0 +1,14 @@
import { bootstrapApplication } from '@angular/platform-browser';
import { IconBrandTabler, IconBrandTablerFilled, provideTablerIconConfig, provideTablerIcons } from '@tabler/icons-angular';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, {
providers: [
provideTablerIcons({ IconBrandTabler, IconBrandTablerFilled }),
provideTablerIconConfig({
size: 48,
stroke: 2,
color: '#066fd1'
})
]
}).catch(err => console.error(err));

67
test/test-angular/src/styles.css

@ -0,0 +1,67 @@
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color-scheme: light dark;
color: #000;
background-color: #e1e1e1;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
.buttons {
display: flex;
gap: 1em;
justify-content: center;
}
button {
font-weight: 500;
text-decoration: inherit;
background-color: transparent;
border: 1px solid black;
border-radius: 8px;
padding: 0.4em 0.6em;
}
button:hover {
background-color: #cacaca;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1, h3 {
font-size: 1.5em;
line-height: 1.1;
margin: 0.5em 0;
}
.app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.tabler-icon.olive {
stroke: olive;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
}

13
test/test-angular/tsconfig.app.json

@ -0,0 +1,13 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts"
],
"include": [
"src/**/*.d.ts"
]
}

33
test/test-angular/tsconfig.json

@ -0,0 +1,33 @@
{
"compileOnSave": false,
"compilerOptions": {
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
"skipLibCheck": true,
"sourceMap": true,
"declaration": false,
"experimentalDecorators": true,
"moduleResolution": "bundler",
"importHelpers": true,
"resolvePackageJsonExports": true,
"resolvePackageJsonImports": true,
"target": "ES2022",
"module": "ES2022",
"lib": [
"ES2022",
"dom"
]
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}
Loading…
Cancel
Save