Skip to content

Commit e880da0

Browse files
committed
fix(icon): cIcon directive [name] binding does not refresh icon for Angular 17, refactor, closes #203, thanks @ProDInfo
1 parent e2b99d7 commit e880da0

File tree

7 files changed

+183
-129
lines changed

7 files changed

+183
-129
lines changed

projects/coreui-icons-angular/LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2023 creativeLabs Łukasz Holeczek
3+
Copyright (c) 2024 creativeLabs Łukasz Holeczek
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

projects/coreui-icons-angular/README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ For directive description visit [https://coreui.io/angular/docs/](https://coreui
5656
### Installation
5757

5858
```shell
59-
npm install @coreui/icons
60-
npm install @coreui/icons-angular
59+
npm install @coreui/icons@3
60+
npm install @coreui/icons-angular@4
6161
```
6262

6363
### Usage
@@ -169,5 +169,5 @@ Thanks to all the backers and sponsors! Support this project by [becoming a back
169169
170170
## Copyright and license
171171
172-
Copyright 2023 creativeLabs Łukasz Holeczek. Code released under the [MIT License](https://github.com/coreui/coreui-angular/blob/main/LICENSE). Docs released under [Creative Commons](https://creativecommons.org/licenses/by/3.0/).
172+
Copyright 2024 creativeLabs Łukasz Holeczek. Code released under the [MIT License](https://github.com/coreui/coreui-angular/blob/main/LICENSE). Docs released under [Creative Commons](https://creativecommons.org/licenses/by/3.0/).
173173

projects/coreui-icons-angular/package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@
2727
"peerDependencies": {
2828
"@angular/common": "^17.3.0",
2929
"@angular/core": "^17.3.0",
30-
"@angular/platform-browser": "^17.3.0",
31-
"@coreui/icons": "^2.1.0 || ^3.0.0"
30+
"@angular/platform-browser": "^17.3.0"
3231
},
3332
"dependencies": {
3433
"tslib": "^2.3.0"
Loading
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,122 @@
1-
import { NgClass, NgIf } from '@angular/common';
2-
import { AfterViewInit, Component, ElementRef, Input, Renderer2, ViewChild } from '@angular/core';
3-
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
1+
import { NgClass } from '@angular/common';
2+
import {
3+
AfterViewInit,
4+
Component,
5+
computed,
6+
ElementRef,
7+
inject,
8+
Input,
9+
Renderer2,
10+
signal,
11+
ViewChild
12+
} from '@angular/core';
13+
import { DomSanitizer } from '@angular/platform-browser';
414

515
import { HtmlAttributesDirective } from '../shared/html-attr.directive';
616
import { IconSetService } from '../icon-set';
717
import { IconSize, IIcon } from './icon.interface';
8-
import { toCamelCase } from './icon.utils';
18+
import { transformName } from './icon.utils';
919

1020
@Component({
21+
exportAs: 'cIconComponent',
22+
imports: [NgClass, HtmlAttributesDirective],
1123
selector: 'c-icon',
12-
templateUrl: './icon.component.svg',
13-
styleUrls: ['./icon.component.scss'],
1424
standalone: true,
15-
imports: [NgClass, NgIf, HtmlAttributesDirective],
25+
styleUrls: ['./icon.component.scss'],
26+
templateUrl: './icon.component.svg',
1627
// eslint-disable-next-line @angular-eslint/no-host-metadata-property
1728
host: { ngSkipHydration: 'true' }
1829
})
1930
export class IconComponent implements IIcon, AfterViewInit {
2031

32+
readonly #renderer = inject(Renderer2);
33+
readonly #elementRef = inject(ElementRef);
34+
readonly #sanitizer = inject(DomSanitizer);
35+
readonly #iconSet = inject(IconSetService);
36+
37+
constructor() {
38+
this.#renderer.setStyle(this.#elementRef.nativeElement, 'display', 'none');
39+
}
40+
41+
@Input()
42+
set content(value: string | string[] | any[]) {
43+
this.#content.set(value);
44+
};
45+
46+
readonly #content = signal<string | string[] | any[]>('');
47+
2148
@Input() attributes: any = { role: 'img' };
22-
@Input() content?: string | string[] | any[];
49+
@Input() customClasses?: string | string[] | Set<string> | { [klass: string]: any };
2350
@Input() size: IconSize = '';
2451
@Input() title?: string;
2552
@Input() use = '';
26-
@Input() customClasses?: string | string[] | Set<string> | { [klass: string]: any } = '';
27-
@Input() width?: string;
2853
@Input() height?: string;
54+
@Input() width?: string;
55+
56+
@Input({ transform: transformName })
57+
set name(value: string) {
58+
this.#name.set(value);
59+
};
2960

30-
@Input({ transform: (value: string) => value && value.includes('-') ? toCamelCase(value) : value }) name!: string;
61+
get name() {
62+
return this.#name();
63+
}
64+
65+
readonly #name = signal('');
3166

3267
@Input()
3368
set viewBox(viewBox: string) {
3469
this._viewBox = viewBox;
3570
}
3671

3772
get viewBox(): string {
38-
return this._viewBox ?? this.scale;
73+
return this._viewBox ?? this.scale();
3974
}
4075

4176
private _viewBox!: string;
4277

4378
@ViewChild('svgElement', { read: ElementRef }) svgElementRef!: ElementRef;
4479

45-
get innerHtml(): SafeHtml {
46-
const code = Array.isArray(this.code) ? this.code[1] || this.code[0] : this.code ?? '';
47-
// todo proper sanitize
48-
// const sanitized = this.sanitizer.sanitize(SecurityContext.HTML, code);
49-
return this.sanitizer.bypassSecurityTrustHtml((this.titleCode + code) ?? '');
50-
}
51-
52-
constructor(
53-
private renderer: Renderer2,
54-
private elementRef: ElementRef,
55-
private sanitizer: DomSanitizer,
56-
private iconSet: IconSetService
57-
) {
58-
this.renderer.setStyle(this.elementRef.nativeElement, 'display', 'none');
59-
}
60-
6180
ngAfterViewInit(): void {
62-
this.elementRef.nativeElement.classList.forEach((item: string) => {
63-
this.renderer.addClass(this.svgElementRef.nativeElement, item);
81+
this.#elementRef.nativeElement.classList.forEach((item: string) => {
82+
this.#renderer.addClass(this.svgElementRef.nativeElement, item);
6483
});
65-
const parentElement = this.renderer.parentNode(this.elementRef.nativeElement);
84+
const parentElement = this.#renderer.parentNode(this.#elementRef.nativeElement);
6685
const svgElement = this.svgElementRef.nativeElement;
67-
this.renderer.insertBefore(parentElement, svgElement, this.elementRef.nativeElement);
68-
this.renderer.removeChild(parentElement, this.elementRef.nativeElement);
86+
this.#renderer.insertBefore(parentElement, svgElement, this.#elementRef.nativeElement);
87+
this.#renderer.removeChild(parentElement, this.#elementRef.nativeElement);
6988
}
7089

90+
readonly innerHtml = computed(() => {
91+
const code = Array.isArray(this.code()) ? (this.code()[1] ?? this.code()[0] ?? '') : this.code() || '';
92+
// todo proper sanitize
93+
// const sanitized = this.sanitizer.sanitize(SecurityContext.HTML, code);
94+
return this.#sanitizer.bypassSecurityTrustHtml((this.titleCode + code) || '');
95+
});
96+
7197
get titleCode(): string {
7298
return this.title ? `<title>${this.title}</title>` : '';
7399
}
74100

75-
get code(): string | string[] | undefined {
76-
if (this.content) {
77-
return this.content;
101+
readonly code = computed(() => {
102+
if (this.#content()) {
103+
return this.#content();
78104
}
79-
if (this.iconSet && this.name) {
80-
return this.iconSet.getIcon(this.name);
105+
if (this.#iconSet && this.#name()) {
106+
return this.#iconSet.getIcon(this.#name());
81107
}
82-
if (this.name && !this.iconSet?.icons[this.name]) {
83-
console.warn(`c-icon component: icon name '${this.name}' does not exist for IconSet service. ` +
108+
if (this.#name() && !this.#iconSet?.icons[this.#name()]) {
109+
console.warn(`c-icon component: icon name '${this.#name()}' does not exist for IconSet service. ` +
84110
`To use icon by 'name' prop you need to add it to IconSet service. \n`,
85-
this.name
111+
this.#name()
86112
);
87113
}
88-
return undefined;
89-
}
114+
return '';
115+
});
90116

91-
get scale(): string {
92-
return Array.isArray(this.code) && this.code.length > 1 ? `0 0 ${this.code[0]}` : '0 0 64 64';
93-
}
117+
readonly scale = computed(() => {
118+
return Array.isArray(this.code()) && this.code().length > 1 ? `0 0 ${this.code()[0]}` : '0 0 64 64';
119+
});
94120

95121
get computedSize(): Exclude<IconSize, 'custom'> | undefined {
96122
const addCustom = !this.size && (this.width || this.height);
@@ -102,10 +128,7 @@ export class IconComponent implements IIcon, AfterViewInit {
102128
icon: true,
103129
[`icon-${this.computedSize}`]: !!this.computedSize
104130
};
105-
return !this.customClasses ? classes : this.customClasses;
131+
return this.customClasses ?? classes;
106132
}
107133

108-
toCamelCase(str: string): string {
109-
return toCamelCase(str);
110-
}
111134
}

0 commit comments

Comments
 (0)