How to compile component styles dynamically in Angular 6 - css

I'm trying work out a way to use jss in an angular 6 project to allow dynamic styling of components.
The issue I'm running into is that the dynamic styles are always less specific than the predefined styles, because the dynamic styles are missing the attribute selector from the view encapsulation system.
I can easily get the raw CSS output from jss, but I haven't been able to find a way to run this through the angular compiler to have the selectors modified to include the attribute selector.
Ideally I'd like to be able to bind a <style> tag in the template to a cssText property of the component, but this doesn't seem possible.
import {Component, OnInit} from '#angular/core';
import * as color from 'color';
import jss from 'jss';
#Component({
selector: 'app-example',
template: `
<p [ngClass]="cssClasses">TEST TEST</p>
`,
styleUrls: ['./example.component.scss']
})
export class ExampleComponent implements OnInit {
cssClasses: { [name: string]: boolean } = {};
constructor() {
}
ngOnInit() {
const {classes} = jss.createStyleSheet({
dynamicClass: {
color: color('blue').hex(),
}
}).attach();
this.cssClasses[classes.dynamicClass] = true;
}
}
example.component.scss
p {
color: 'red'
}
If there a way of invoking the angular CSS compiler on an arbitrary piece of CSS, with the context of a particular component?
Or another way to achieve what I'm describing above?
Note: I'm aware that I can bind and apply inline styles to elements, but this doesn't meet my requirements - in particular you cannot target pseudo selectors, or do media queries etc using this mechanism.
I could probably work around this by not using the scss file at all and defining all default styles through the jss mechanism however I would prefer to retain the ability to use the normal style system so that the jss is only used where needed. Also I think I would still run into selectivity issues when styling 3rd party components using jss.

Related

Can I use css modules in react class based components?

I have to implement css modules for some older React code. But when I try to import CSS modules, I canĀ“t use this class in another files.
Do I have to refactor the code to React Components? Or is there another easier solution?
Code example:
import styles from "styles.module.css"
var Greeting = createReactClass({
render: function() {
return <h1>Hello</h1>;
}
});
Yes, module CSS is also supported in class-based components.
In the module CSS you have to use CSS as an object,
For Example, You have a CSS class for an alert component
.alert{
color:red
}
then you can use it in components like this:-
import styles from "styles.module.css"
var Alert = createReactClass({
render: function() {
return <h1 className={styles.alert}>Hello</h1>;
}
});

Can I load into an array all the CSS classes into an array listed in the styleUrls property of an Angular Component

In my Angular component I wish to load one of the CSS files used in my app so I can collect the contained CSS classes, put them into an array and loop through them in my component's HTML template. I know from previous experience that in JavaScript/TypeScript I can load all of my CSS stylesheets into JavaScript/TypeScript like so:
Array.prototype.forEach.call(document.styleSheets, (styleSheetList: any) => {
console.log(styleSheetList.cssRules); // do something
});
This is okay but a little inefficient and I shouldn't really call "document" in my Angular component file as I have heard this is considered an anti-pattern. So could I add the specific CSS file to/through my component's styleUrls property then somehow expose it in my component and loop through the classes there?
Something like this:
#Component({
selector: 'css-list',
templateUrl: './css-list.component.html',
styleUrls: ['./css-listcomponent.scss', 'the-file-i-want-to-iterate-through.css']
})
export class CssListComponent implements OnInit {
// so let's load the style sheet
ngOnInit() {
Array.prototype.forEach.call(this.styleUrls[1].cssRules, (cssRules: any) => {
console.log(cssRules); // do something
});
}
}

How to create and style svelte 3 custom elements with nested components?

I'm trying to create custom-element (web component) in svelte 3. I can't find any way to style nested components from css. Svelte removes styles before injecting them to <style> inside of ShadowDOM.
The problem is that I want to have nested components in my root element.
For example:
RootComponent (svelte custom-element)
(imports) FooComponent
(imports) BarComponent
As stated here: svelte-custom-element
All the components imported to custom-element must have compiler option set to <svelte:options tag="component-name" />.
With this option set nested components works as expected and are injected into root's element ShadowDOM. The problem is that styles defined in nested components are not being injected.
The workaround for this problem would be to inject them into root's element <style> as global styles within ShadowDom.
(Un)fortunately svelte automatically removes all unused styles during compilation when custom elements not yet exist.
My goal is to create web component with svelte and then use it outside of svelte as native web-component.
Here is REPL
Custom elements do not really work on REPL as Conduitry wrote:
The compiler options in the REPL don't actually affect the code that >is run, just the code that is displayed. So enabling customElement >doesn't mean you are building and running a web component
So it's more like a code example than working one.
I would like to know if there is another way to create svelte custom-element with nested component and proper styling.
Is there a way to disable removing of unused css?
https://imgur.com/a/zZia566
from <div class="nested"> starts Nested component imported from Nested.svelte.
<style> element should have .nested class injected but it is removed by svelte compiler.
This is because when customElement option is on, each style in a component is injected into the shadowRoot of the custom element.
class YourComponent extends SvelteElement {
constructor(options) {
super();
this.shadowRoot.innerHTML = `<style>.foo{color:red;}</style>`;
// continues
Thus, in order to make style appear, you must use svelte component as custom element, not as svelte component.
Your App.svelte should be like below.
<script>
import Foo from './Foo.svelte'
import Bar from './Bar.svelte'
</script>
<svelte:options tag="web-component" />
<foo-component/>
<bar-component/>
However, this neither solve the problems related with custom element.
:global selector is not transformed into actual global selector.
Every nested component will produce shadowRoot, whereas mostly you will want only top-level one.
Check out some issues below from svelte repository related to custom elements.
nested component in custom element does not inherit style #2605
:global(...) not working in custom elements #2969
It seems like svelte does not fully support style cascading in custom element yet, should be handled in future.
Checked in svelte v3.12.1.
Thanks to brunoalano for sending me this: svelte-custom-element-template. It solves the styling problem with custom build script.
You just have to prevent the compiler from removing unused CSS
Let's say we have a custom element : App.svelte
App.svelte imports a normal svelte component : ./components/Message.svelte
But when you do this, any styles inside Message.svelte will disappear.
Solution
Move all content in the <style> tag of Message.svelte into the <style> tag of App.svelte
Add this to script of App.svelte
let cssKeep: string = "";
Add this to body of App.svelte
<span style="display: none;" class={cssKeep}><span class={cssKeep} /> </span>
This will prevent the compiler from removing any styles
Example
src/components/Message.svelte
<script lang="ts">
export let content: string;
</script>
<p class="red"> {content} </p>
src/App.svelte
<svelte:options tag="my-element" />
<script lang="ts">
import Message from "./components/Message.svelte";
let cssKeep: string = "";
</script>
<Message content="hello" />
<span style="display: none;" class={cssKeep}><span class={cssKeep} /> </span>
<style>
.red {
color: red;
}
</style>
vite.config.ts
import { defineConfig } from 'vite'
import { svelte } from '#sveltejs/vite-plugin-svelte'
export default defineConfig({
build: {
lib: {
entry: './src/main.ts',
name: 'MyElement'
},
},
plugins: [
svelte(
{
compilerOptions: {
css: true,
},
exclude: "./src/App.svelte",
emitCss: true,
}
),
svelte(
{
compilerOptions: {
customElement: true,
css: true,
},
exclude: "./src/components/**",
emitCss: true,
}
),
],
})
// guide: https://www.thisdot.co/blog/web-components-with-svelte
This may answer:
How to export web component in Svelte with nested Svelte components
Use Svelte components with custom elements
Styles missing when Svelte component imported into custom web component / custom element

Angular 6 not applying scss styles

I have a component page and corresponding style sheet, however the classes in the component.scss dosen't apply to the page. There are no errors, I am still wondering why?
This is my product-detailpage.component.html
<div>
<h1>Product Detail Page</h1>
</div>
This is the .ts file
import { Component, OnInit } from '#angular/core';
import {ActivatedRoute} from '#angular/router';
import {ProductdetailService} from '../productdetail.service';
#Component({
selector: 'app-product-detailpage',
templateUrl: './product-detailpage.component.html',
styleUrls: ['./product-detailpage.component.scss']
})
export class ProductDetailpageComponent implements OnInit {
constructor(private route: ActivatedRoute, private productData: ProductdetailService) {
this.route.params.subscribe(params => console.log(params));
}
ngOnInit() {
}
}
This is the .scss file
body{color:Red !important}
app-product-detailpage{
h1{color:red !important}
}
However one thing I noticed was if I make changes to the global styles.css it works fine. just to check I changed the body color to green and it works.
My angular app is configured to work with scss. what could be the reason? can some one suggest?
Your SCSS won't work for your HTML file product-detailpage.component.html.
The reason is Angular uses shadow DOM for components. That means the tags <body> and <app-product-detailpage> are nowhere to be found in your component.
As per the documentation, The style specified in the component can only be applied to its template, which excludes the component.
This is the reason why your styles are not working on the component from component's style.scss but are working fine from global style sheet.
One way of doing it is to use :host pseudo selector as per this documentation which allows to add styles on the container in which component is placed.
The documentation says -
The :host selector is the only way to target the host element. You can't reach the host element from inside the component with other selectors because it's not part of the component's own template. The host element is in a parent component's template.
Because default css encapsulation in Angular is Emulated(ViewEncapsulation.Emulated) so Angular will render out like below:
input[_ngcontent-c0] {
border-radius: 5px;
}
So if you want set style to the currently component, you can use Native option.
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
encapsulation : ViewEncapsulation.Native
})
It will render like as:
input {
border-radius: 5px;
}
But finally I suggest you use global scss file to define style of <web component>.

Load different style in a component based on routing

I am trying to load css styles in a component based on the URL parameters. Basically the user will load the page like SOME_URL/{screenSize}/{type}. This shall always load the same component, but with different CSS styling. I am already using a router and have the parameters set - how can I dynamically load the CSS files? Is that possible at all?
Here is some code - basically the goal is not to load the static CSS file defined in the example, but to load something like screen.component.21-5.a.css
#Component({
selector: 'app-screen',
templateUrl: './screen.component.html',
styleUrls: ['./screen.component.css']
})
export class ScreenComponent implements OnInit {
public screenType = 'a';
public screenSize = '21-5';
ngOnInit() {}
constructor(private mediaService: MediaService, private route: ActivatedRoute) {
this.parameterSubscription = route.params.subscribe((params) => {
if (params.size) {
this.screenSize = params.size;
}
if (params.type) {
this.screenType = params.type;
}
});
}
...
}
You cannot do that, as Angular requires the style declarations to be statically analyzable. This is done for the AOT compilation. All of your templates and styles are getting compiled to JavaScript when you run ng build --prod, and the styles are imported ahead of time. So, if you could reload styles using some conditionals, that code would no longer be statically analyzable (the screenSize variable can be known only during runtime, so which style should we import when building ahead of time?), thus resulting in the impossibility of AOT compilation.
(no, there is no way of compiling both styles and then importing them in runtime - that would mean we could understand what output will the function produce, which is virtually impossible - see halting problem)
But you can (and should) use ngClass to toggle between css classes depending on the screen size.
You can use ngClass
in your html
<div class="gradient " [ngClass]="{'class-21-5': screenSize ==='21-5', 'class-a': screenType==='a'}">
...
</div>
You can put the different classes in different files and import it on the component if you want.
#Component({
selector: 'app-screen',
templateUrl: './screen.component.html',
styleUrls: ['./screen.component.css', './my-other-style.css']
})

Resources