Import variables from .ts into .scss angular - javascript

I am developing an angular component and I would like to import into my scss file variables (such as colors) from my ts file and I am going throught some issues.
I have seen some examples with node-sass and webpack but are not very clear to me.
Thanks

One option is CSS Variables.
This is not a SASS variable that is available in preprocessing, but rather something available in the browser during runtime. Therefore, you can get/set it with javascript, and the CSS style will update based on the variable value.
For instance, let's say your component allows you to set the text color through a javascript variable textColor:
CSS:
p { color: var(--text-color); }
JS:
element.style.setProperty("--text-color", textColor);
And if you want the flexibility/maintainability of variables in your SCSS -- you can have the variables point to the JS/CSS variables.
SCSS:
// _vars.scss
$text-color: var(--text-color);
// _styles.scss
p { color: $text-color }
Make sure to verify that this feature has the level of browser support your app needs.

have you tried ngStyle
<some-element [ngStyle]="{'color': styleExp}">...</some-element>
and then in your .ts
styleExp = 'red'
you can read more on it on the official docs
https://angular.io/api/common/NgStyle

It is not possible to import variables to scss files from the ts files. Instead, you can use angular angular properties ngStyle and ngClass

constructor(private elem: ElementRef){
this.colorValue = "yellow";
this.elem.nativeElement.style.setProperty('--text-color', colorValue);
}
then in the css or scss you can use --text-color variable
p { color: var(--text-color); }

I know this is old, but I would like to give an answer that people might be interest:
in example.component.ts
#Component({
selector: 'example',
templateUrl: './example.component.html',
styleUrls: ['./example.component.scss'],
})
export class ExampleComponent implements OnInit {
...
#HostBinding('style.--nameOfVar') nameOfVar = 'red';
...
}
in example.component.scss (or css)
.example {
color: var(--nameOfVar);
}

Related

Databinding in Angular, understanding in detail

I am new to Angular and I just started learning it recently. I came across the concept of Databinding in Angular. I was able to understand the syntax and stuff but there were some questions that I couldn't find an answer for. These are the queries I had:
When we export a class from the component TS file, we can use the class properties in HTML file. For eg: Databinding a class property to a HTML element works. But how does this HTML element know the class or the class attribute? How does the HTML file have access to it?
Why exactly are we exporting a class for a component to be used? Is the component a class too? If yes, then wehen we use the component are we calling that class and this leads to rendering the HTML and CSS mentioned in the component?
Please let me know.
Answering your question in details requires having an in-depth knowledge about how Angular internally works, but here's a starting point:
I've generated a component using angular CLI:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-example',
templateUrl: './example.component.html',
styleUrls: ['./example.component.scss']
})
export class ExampleComponent implements OnInit {
public myProperty: number = 0;
constructor() { }
ngOnInit(): void {
}
}
So:
Is the component a class too?
Yes, as you can see from the labels "export class", your component is first of all a regular JS class, which implements the OnInit interface, has an empty constructor and defines a public variable.
If yes, then when we use the component are we calling that class?
Exactly: Angular does a bit of magic (see the next point of the answer), so whenever finds a html tag named <app-example></app-example>, it creates an ExampleComponent instance and replaces that tag with the html you defined on example.component.html
and this leads to rendering the HTML and CSS mentioned in the component?
The magic happens just above the class definition: Angular heavily relies on Typescript Decorators, which are an (still) experimental feature of Typescript. Decorators allows you (or Angular in our case) to alter the behaviour of a class, for example by intercepting methods call, property changes (did you just say databinding?) and constructor parameters (and this is how Angular's dependency injection works).
In the #Component decorators, which is linked to the below ExampleComponent class, you're defining three things:
the selector, or tag name that Angular will search in the DOM and replace with your component's html
Where to find your component's html, which will be linked to each of your ExampleComponent instance
Stylesheets for your component's html
So, when a property on your component changes (for example myProperty), Angular intercepts that change thanks to the #Component decorators you've defined (to understand how, do a little search about decorators), and will re-render his html. Inserting that property value on a paragraph like <p>{{myProperty}}</p> is just a matter of string replacement.
So, now you have the answer to your first question:
But how does this HTML element know the class or the class attribute? How does the HTML file have access to it?
It's not the html that knows which component it belongs, it's the component (or Angular that handles that component) that knows which html has to render and which css needs to apply.
Why exactly are we exporting a class for a component to be used?
This is simply to let Angular know that we have defined a component. Exporting something from a .ts file makes it available on the rest of the project, and particularly on the AppModule file, where you will find your ExampleComponent among the declarations array:
#NgModule({
declarations: [
AppComponent,
ExampleComponent
],
// Something else

Next.js production build doesn't apply css variables

I have the next postcss config
module.exports = {
plugins: [
[
'postcss-preset-env',
{
browsers: 'last 2 versions, IE 11, not dead',
preserve: false,
features: {
'custom-media-queries': true,
'custom-properties': true,
'nesting-rules': true
},
importFrom: ['src/styles/variables.css']
}
]
]
}
And this file with css variables
#custom-media --desktop screen and (min-width: 768px);
#custom-media --mobile screen and (max-width: 767px);
:root {
--montserrat: montserrat, sans-serif;
--sfProDisplay: sf pro display, sans-serif;
--helvetica: helvetica, sans-serif;
--blue: #315efb;
--middleBlue: #2c54e2;
--darkBlue: #274bc8;
--lightBlue: #e0ebff;
--green: #21a038;
--grey: #62687f;
--darkGray: #343b4c;
--blueGray: #8d96b2;
--cloudGray: #f3f4f7;
--cloudGray7: #afb6c9;
--darkCarbone: #1f2431;
--paleYellow: #fffde5;
--red: #ff564e;
}
After i built my project via next build && next export, colors are not displayed correctly. For example, color: var(--blueGray), instead color: #8d96b2. Has anybody idea what's wrong?
I believe you should import your CSS variables directly in your styles section and not in config. but I might have got you wrong
Global CSS needs to be included in the /pages/_app file.
From the Adding a Global Stylesheet
documentation:
To add a (global) stylesheet to your application, import the CSS file within
pages/_app.js.
(...) In development, expressing stylesheets this way allows your
styles to be hot reloaded as you edit them—-meaning you can keep
application state.
In production, all CSS files will be automatically concatenated into a
single minified .css file.
Make sure you import the file where the CSS variables are defined in your custom _app.
import '../styles/globals.css'
import '../styles/variables.css'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}
export default MyApp;
This may not be worth the bounty but from NextJS docs.
CSS variables are not compiled because it is not possible to safely do
so. If you must use variables, consider using something like Sass
variables which are compiled away by Sass.

Generate appropriate Angular Element dynamically without bloating the build size?

Summary:
When createCustomElement() is called multiple times inside a switch case, all of the components will be included in the build, instead of just the one that will actually be used. This increases the build size with duplicate code.
Details
I have an Angular 11 app with a multi-site architecture. There is a project in the angular.json for each site, so they can be built independently and generate their own dist bundles based on the appropriate environment.ts file that contains a "siteCode".
For one of the big components in my site -- let's call it myWidget -- I also export it as a generic web component (a.k.a "Angular Element", a.k.a. "custom element") for other sites to consume. So I have myWidget in a sub-project of the main app, and it also has its own projects listed in angular.json. Meaning I can run a build that should contain just myWidget for a given site (along with the core Angular framework, obviously).
app.module.ts of myWidget sub-project (simplified):
import { MyWidgetSite1Component } from './my-widget/site-widgets/my-widget-site1.component';
import { MyWidgetSite2Component } from './my-widget/site-widgets/my-widget-site2.component';
import { MyWidgetSite3Component } from './my-widget/site-widgets/my-widget-site3.component';
#NgModule({
declarations: [AppComponent],
imports: [MyWidgetModule]
})
export class AppModule {
constructor(private injector: Injector) {
//Create generic web component version of myWidget. Use correct child version per site.
switch (environment.siteCode) {
case "site1": {
const myWidgetCustomElement = createCustomElement(MyWidgetSite1Component , { injector: this.injector });
customElements.define('site1-myWidget', myWidgetCustomElement);
break;
}
case "site2": {
const myWidgetCustomElement = createCustomElement(MyWidgetSite2Component , { injector: this.injector });
customElements.define('site2-myWidget', myWidgetCustomElement);
break;
}
case "site3": {
const myWidgetCustomElement = createCustomElement(MyWidgetSite3Component , { injector: this.injector });
customElements.define('site3-myWidget', myWidgetCustomElement);
break;
}
}
}
}
Problem: it includes all three of those components in the build, instead of just the one that will be used for that site (verified with webpack bundle analyzer).
Futher background
The three site-specific myWidget components here all inherit from a common base component where all the real logic is, so they are nearly-identical. I'm doing this so I can load the appropriate CSS files for that site and bundle them inside the exported MyWidget web component as component-specific CSS. It uses shadowDom encapsulation and this way the web component is completely sealed off from the parent page that it's inserted into. So the components in question look like this:
my-widget-site1.component.ts
#Component({
selector: 'my-widget-site1',
templateUrl: '../my-widget/my-widget.component.html', //Shares base myWidget template
//First file does nothing but import appropriate site's stylesheets from main project. It would
//have been better to directly include them here, but that resulted in odd build errors.
styleUrls: [
'./my-widget-site1.component.scss',
'../my-widget/my-widget.component.scss'],
encapsulation: ViewEncapsulation.ShadowDom
})
export class MyWidgetSite1Component extends MyWidgetComponent implements OnInit {
//Do not add any code here
}
my-widget-site1.component.scss
#import '../../../../../../src/app/sites/site1/theme.scss';
#import '../../../../../../src/styles.scss';
#import '../../../../../../src/app/sites/site1/styles.scss';
Conclusion
I can think of a few general ideas to solve this:
1) Some trick to load the desired component dynamically instead of in a switch case?
I haven't been able to find anything. It seems as long as I have to import the component, it will be included in the build.
2) Avoid having multiple versions of the component per site entirely?
I would love to do this, but I don't know how to get around the CSS problem. The appropriate CSS files for a given site need to be bundled into this component at build time so they are encapsulated in the shadow-root of the web component and not built as a separate CSS file that gets imported into the global scope of the consuming page. That means I can't just list them in the "styles" section of the project build in angular.json
I tried to do a dynamic #import statement on the scss but that doesn't seem possible.
Can you script something into the build process somehow to choose the right scss files at build time? I would have no idea where to start with something like that.
I figured out an interesting solution to this.
I can get rid of the need for multiple component files and effectively pull off a dynamic #import by using a 'shortcut' to point at the necessary scss files instead of the full path:
#import "theme";
#import "styles";
#import "site-styles";
You can configure the folders it will find the specified files in via angular.json:
"stylePreprocessorOptions": {
"includePaths": [
"src/app/sites/site1/", //theme.scss and site-styles.scss are here
"src/" //styles.scss is here
]
}
So now I can use one component that always has the same imports, but at build time it will actually use the right file for the site being built.
Info about the shortcut syntax: https://www.digitalocean.com/community/tutorials/angular-shortcut-to-importing-styles-files-in-components

How to change the import statement based on variable in ( Style.scss ) file

I have this import statement in my project
#import "./assets/sass/style.angular.scss";
How i can change the path based on a variable. I want to make it change when user change the language of the project to arabic use this path
#import "./assets/sass/style.angular.RTL.css";
This is not a built-in functionality of angular, but you could always add a class to your app.component file based on the language, or a data tag. Something like this:
#Component({
templateUrl: 'app.component.html',
selector: 'app-component
})
export class AppComponent {
#HostBinding('attr.data-lang') language: string = "en";
}
then in your style.angular.RTL.css file you would prefix your selectors with [data-lang="arabic" to have it kick in whenever the language variable was set to arabic.
If you are in need of if-else inside the .scss file, you can check this approach
// Define a mixin to import what file needs to be imported
#mixin importRightScssFile($firstUrl: true) {
#if $firstUrl {
#import('path/to/your/first-url.scss');
} #lse {
#import('path/to/your/second-url.scss');
}
}
// Whenever you want you can simply use this **mixin** to load whatever URL you want like this:
.first-url-class {
#include importRightScssFile($firstUrl: true);
}
.second-url-class {
#include importRightScssFile($firstUrl: false);
}

SCSS external Link to styling component in LitElement

The purpose of the web components is to encapsulate all HTML/CSS/JS in one place, but I must use general SCSS files, that have a lot of variables such as color, font size and a lot of other stuff. It's a design system and it's always changing. Is there a plugin or a way to solve this problem?
If the purpose of those Sass files is to only expose some variables then probably the smartest thing to do is converting them to CSS custom properties: they're the recommended and most effective way to theme WebComponents.
#use 'sass:meta';
#use 'variables-file-1'; // Import the variable files
#use 'variables-file-2'; // as Sass modules
// ...
#mixin export-vars($module-name) {
// Use module-variables() to extract a name-to-value map of the
// variables declared in the module identified by $module-name
#each $name, $value in meta.module-variables($module-name) {
--#{$name}: #{$value}; // Define an equivalent CSS custom prop
}
}
:root {
#include export-vars(variables-file-1);
#include export-vars(variables-file-2);
// ...
}
You can then import the compiled CSS (a normal <link> in index.html is fine in most cases since CSS custom properties cross shadow DOM boundaries) and use the variables in your components:
import { LitElement, html, css, customElement } from 'lit-element';
#customElement('my-component')
export class MyComponent extends LitElement {
static styles = css`
:host {
color: var(--my-var, <fallback-value>);
}
`;
}
With this said, if you need to share styles between LitElements, that is possible as well.

Categories

Resources