I am running a project in Angular 9.1.13, on Centos 7.
I have a component with static HTML code, as follows:
<ul id="myUL">
<li><span class="caret">Top 1</span>
<ul class="nested">
<li>Sub 1</li>
<li>Sub 2</li>
</ul>
</li>
</ul>
My CSS looks as follows:
ul, #myUL {
list-style-type: none;
}
#myUL {
margin: 0;
padding: 0;
}
.caret {
cursor: pointer;
user-select: none;
}
.caret::before {
content: "25B6";
display: inline-block;
margin-right: 6px;
}
.caret-down::before {
transform: rotate(90deg);
}
.nested {
display: none;
}
.active {
display: block;
}
And my component executes this code in the ngOnInit()
method:
...
const toggler = document.getElementsByClassName('caret');
let i;
for (i = 0; i < toggler.length; i++) {
toggler[i].addEventListener('click', function() {
this.parentElement.querySelector('.nested').classList.toggle('active');
this.classList.toggle('caret-down');
});
}
...
With things as described, everything works just fine. I can click that red icon, and the sublist can be shown or hidden:
The problem comes however if I have to generate the content of the list dynamically.
In order to do so, the HTML looks as follows:
<ul id="myUL"></ul> <!-- Deliberately empty unordered list -->
On the ngOnInit()
method above, and before the code I had already mentioned, I prepend this other chunk of code:
...
const myUL = document.getElementById('myUL');
myUL.innerHTML =
'<li><span class="caret">Top 1</span><ul class="nested"><li>Sub 1</li><li>Sub 2</li></ul></li>';
// The rest of the code mentioned above
...
With that code, things don’t look the same. The sublist is not collapsible anymore, styles are not applied… although surprisingly, click events are actually attached to the caret
elements:
So, the question is… how can I achieve the same result as in the first screenshot, but dynamically generating the list content.
3
Answers
You should use
ng-deep
and disable scope styles for elements, because you are trying to create these elemenets by yourselfYou can generate the list dynamically in the html using angular. I would setup my a caret object / list in my component and then take advantage of *ngFor and [ngClass] to achieve what you’re trying to do. Something like:
component.ts
component.html
Disclaimer: this answer only applies if you are required to inject html as a string. For example if you receive the string from an api. Otherwise you should be using Angular directives to generate the content.
This is because Angular uses View Encapsulation. It does this by generating a unique identifier and adding it as an attribute to all of your html elements in the component, something like
_ngcontent-nxs-c94=""
. Then it appends[_ngcontent-nxs-c94]
to all of your css declarations inside the component. Inspect your elements and you’ll see what I mean.If you’re injecting html after the component has been compiled, that html will obviously not have the attribute and your css will not affect it. You’re not meant to inject html like this, since Angular provides all of the tools to generate content dynamically. But if it’s absolutely required then you just need to add the CSS globally (ie. it does not get the unique attribute appended). Any file in the
styles
array located inangular.json
will be compiled as a global style, by defaultstyles.css
is the only file here.So just add your css to a file that that is in the
styles
array. Either instyles.css
or create your own (you can make it right next to your original css file). But this brings us back to the days before View Encapsulation, so we need to make sure the selectors are unique. That’s pretty simple since you can just wrap all of the css in your component’s html tag. Keep in mind this will affect any other nested components so you may need to add some more unique class names.In a global style file
So this lets you apply global styles to a component while still maintaining view encapsulation within your original css file. The other option is to disable view encapsulation for the component, but I wouldn’t recommend, since there may be css that you do want encapsulated.
Disabling View Encapsulation (not recommended)
That’ll compile
./one.component.scss
globally, just the same as if it were in thestyles
array. If you do this, make sure the css isn’t going to affect other components.