Angular2 dynamic style - css

What about dynamic styling.
Imagine you're deploying a unique plaftorm for multiple clients. (SAAS platform)
You deploy ONE webapp folder but these webapp must apply a different theme (color) for each client (exemple configuration : loading colors by domain name).
In angular 2, there is scss but the scss is a compiled language -> so you need to compile each time for each client to compile N webapps. It takes time and it's difficult to maintain.
So the only solution i see:
- compile scss at runtime (via remote server call, fe:jsass, or js) when loading app and inject the generated css file in head section.
but i think it's an anti-pattern with the (s)css file for angular2 components.
Moreover the generated file will contains components style that will apply on entire page instead even if component is not initialized.
Have you got any framework or other solution ?

You can achieve this creating a Service that return the colors of some structures that you want to change and inject it into the Components
Example inside the component html:
<div [style.background-color]="themeService.getNavbarColor()"></div>
And do some logic to get the pattern for each user when App starts and insert into the ThemeService.
Full Example:
import { Component } from '#angular/core'
#Component ({
selector: 'my-component',
templateUrl: './my-component.component.html',
})
export class MyComponent {
constructor( private themeService: ThemeService){}
}
#Injectable()
export class ThemeService {
backgroundColor: string
getNavbarBackgroundColor: string() {
return this.backgroundColor;
}
someLogicToGetTheme() {
//do stuff
}
ngOnInit() {
this.someLogicToGetTheme()
}
}

Related

Vue 3. Vite, createApp() and include CSS from single file components within iframe

Bit of a tricky one and a unique use case.
I have a Vue 3 app, developing and building using Vite.
i am instantiating a second Vue 3 app instance inside an iframe, relevant code pasted below. As i need unpolluted components and styles available for the second app in the iframe.
import { h, ref, createApp, onMounted, onBeforeUnmount, onBeforeUpdate } from "vue"
export default {
setup(props, { attrs, slots, emit, expose }) {
const iframeRef = ref(null)
const iframeBody = ref(null)
const iframeHead = ref(null)
const iframeStyle = ref(null)
let app = null
let emitSelection;
onMounted(() => {
iframeBody.value = iframeRef.value.contentDocument.body
iframeHead.value = iframeRef.value.contentDocument.head
const el = document.createElement("div")
iframeBody.value.appendChild(el)
app = createApp(MySecondAppLayout, props)
app.use(MyUILibrary);
app.mount(el)
})
return () => h("iframe", { ref: iframeRef })
}
Everything is working great, the second app instance imports it's own components and mount and behave correctly within the iframe. However, the SCSS/CSS styles are not included within the iframe context.
Is what i am attempting to do impossible? Or is there a tag i can inject into the iframe to include the styles from the imported components?
Alternatively should i/can i get vite output the style sheets from all of the single file components as a text file so i can paste it in manually at the top of the iframe?
I was able to repackage my component library using vite, and then using this plugin: https://www.npmjs.com/package/vite-plugin-libcss i could import the css manually into the iframe.

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 compile component styles dynamically in Angular 6

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.

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']
})

Refactoring <link> tag in template html to styleUrls

I'm trying to refactor a Component, which links stylesheets in it's html template into using the styleUrls instead.
The linked template has an include alike;
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
I've refactored this into;
styleUrls: [
'https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css'
],
encapsulation: ViewEncapsulation.None
I can confirm the stylesheet is loaded, it is however loaded as an inline style instead of via the link tag, as such references as;
src:url('../fonts/fontawesome-webfont.eot?v=4.4.0');
Do not point to;
https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/fonts/fontawesome-webfont.eot?v=4.4.0
But rather to;
http://localhost:9000/fonts/fontawesome-webfont.eot?v=4.4.0
How would I go about refactoring the html template to using styleUrls, while keeping the relative references?
The only way I was able to find around this was mentioned here in subsequent comments.
You have to create a custom UrlResolver. Here is a test on the Angular2 Material repo where they implement a custom url resolver. Relevant sections below.
import {TestUrlResolver} from './test_url_resolver';
import {..., bind} from 'angular2/core';
bind(UrlResolver)
.toValue(new TestUrlResolver());
Here is the TypeScript version of the TestUrlResolver (the other version is in Dart):
import {UrlResolver} from 'angular2/src/core/compiler/url_resolver';
export class TestUrlResolver extends UrlResolver {
constructor() {
super();
}
resolve(baseUrl: string, url: string): string {
// The standard UrlResolver looks for "package:" templateUrls in
// node_modules, however in our repo we host material widgets at the root.
if (url.startsWith('package:angular2_material/')) {
return '/base/dist/js/dev/es5/' + url.substring(8);
}
return super.resolve(baseUrl, url);
}
}

Resources