Reputation: 311
I'm very new to the world of Angular (loving it so far). We have an Angular 1 (JS) app that we plan to convert to the latest Angular 2 (8.3.9) version. One thing that was done in the old app, was make use of the $scope object, and then set the CSS stylesheet link in the root index.html dynamically based on a query parameter in the requesting URL.
Using ngStyle or ngClass to update indiviudal elements in a document it cool, but,
How do we handle changing the entire style sheets on loading the app in Angular 2?
Everything is encapsulated inside the component, and styles specified in the angular.json file are built into the "deployable.js" file. Is it possible to change it at runtime?
This link appears to be the closest example: Generate dynamic css based on variables angular , but still doesn't quite solve the problem at the root index.html file.
The Current OLD Version
Url Arrives:
http://someserver:port/app?css=http://linktoCSs/cssFile.css
css query param is pulled into a global variable and stored on scope called css_url, In index.html (starting page)
<link rel="stylesheet" ng-href="{{css_url}}">
and the app starts using whatever style sheet this link provides.
The NEW Angular 2 (version 8.3.9)
We want to mimic the above behavior that we were able to achieve in the JS version. Pull a URL from a QueryParam pointing to some CSS file, and that CSS URL can be injected into the main index.html to change the entire apps styles in one go, or dynamically access the style-sheet in code to update everything in one go.
In short, we want 1 app that can be styled by a CSS file, based off a queryparam.
All thoughts and comments will be greatly appreciated.
Upvotes: 8
Views: 11647
Reputation: 311
After a lot of digging around, finally found the solution I was looking for. And it was so straight forward, hope this helps others that might be needing to do the same thing..
Get the css path from query params and then Inject the Document into a TS file... append the link to the head of the document.
I did this in a Router Guard using canActivate. I got the query param from the routerstatesnpshot like so:
Inject the DOCUMENT( don't forget the import) in the constructor
http://server.com/xxx&broker=1&client=2&css=http://cssServer.com/my_dynamic_stylesheet.css
import { DOCUMENT } from '@angular/common';
@Inject(DOCUMENT) private document: Document
this.setDynamicStyle(state.root.queryParamMap.get('css'));
setDynamicStyle(cssURL: string) {
const head = this.document.getElementsByTagName('head')[0];
const style = this.document.createElement('link');
style.id = 'css-styling';
style.rel = 'stylesheet';
style.href = `${cssURL}`;
head.appendChild(style);
}
Thanks to: https://juristr.com/blog/2019/08/dynamically-load-css-angular-cli/
Upvotes: 12
Reputation: 311
This appears to be a fairly common issue, updating styling dynamically, and I can't find anything where people have attempted to override the entire CSS file/s for an Anuglar 2 app, post compilation.
What I ended up doing (for now)...
On GitHub link: https://github.com/angular/angular/issues/9343 Thanks to: peteboere
Created a directive like
import {Directive, ElementRef, Input} from '@angular/core';
@Directive({
selector: '[cssProps]',
})
export class CSSPropsDirective {
@Input() cssProps: any;
constructor(private element: ElementRef) {}
ngOnChanges({cssProps})
{
if (cssProps && cssProps.currentValue)
{
const {style} = this.element.nativeElement;
for (const [k, v] of Object.entries(cssProps.currentValue))
{
style.setProperty(k, v);
}
}
}
}
Which unfortunately means placing the cssProps on EACH and EVERY element in my documents that needs styling... like:
<div class="panel-heading" [cssProps]="cssStyling">
Then I created a simple Service to fetch styling in a JSON format like,
{--backgroundColor: "black"}
in the css file per component I used custom css properties, to handle these.
background: var(--backgroundColor)
--backgroundColor: #008000;
NOTE TO SELF, doing this at app-root level, might mean not having to apply styles per component css file..., but we will still need to apply the directive to each element we want to style dynamically. ViewEncapsulation will need to be NONE across lower components.
Then, on an httpClient call every 10 seconds (for now) like:
import { HttpClient } from '@angular/common/http';
import { Subject, Subscription } from 'rxjs';
import { Injectable } from '@angular/core';
@Injectable({providedIn: 'root'})
export class StyleSerivce {
styleChanged = new Subject<any>();
interval = new Subscription();
styles:any;
constructor(private httpClient: HttpClient) { }
retrieveStyleFrom() {
setInterval(() => {
this.httpClient.get('https://serverName/stylesss.json')
.subscribe(data => {
console.log(data);
this.styles = data;
this.styleChanged.next(this.styles);
})
}, 10000);
}
}
I subscribed to the styleChange in the respective components like:
this.styleSub = this.styleService.styleChanged.subscribe((styleNow) => {
this.cssStyling = styleNow;
})
and with cssStyling bound to my directive, as I update the DB where the JSON is stored, I can update the screen dynamically... (after the 10 second elapse)
This works, but the pain of setting this on each tag to make it dynamically updatable from DB/JSON updates is hectic.
I would not call this a solution to the initial problem, but a painful workaround.
Upvotes: 1