How to add custom CSS defined by some user input in Angular4? - html

I want to give users of my application the possibility to set plain CSS styles and those style definitions should be applied immediately to some HTML elements.
For example, I have a textarea bound to a model in my component:
<textarea #customCss></<textarea>
In the same component, I defined a HTML element and for that HTML element, I want to give the user the possibility to assign plain CSS and allow the user to immediately preview the styled element. So basically, I would expect something like the following (which isn't working since the style property is readonly):
<div style="{{ customCss.value }}">CUSTOM STYLED DIV</div>
Therefore, when a user enters background:black;color:red; in the textarea, I would expect that the HTML for the styled element would be rendered as:
<div style="background:black;color:red;">CUSTOM STYLED DIV</div>
I don't see any chance to use the ng-style directive here directly since it would expect some JSON code and not plain CSS code.
So how can I tackle this problem in Angular4?

Probably the easiest way is to just bind to the attribute:
<div [attr.style]="customCss.value">CUSTOM STYLED DIV</div>

Niles advice got me back on the right track, thanks for that.
I already tried [attr.style]="..." before but without success but the problem was that the resulting CSS rendered to "unsafe" (at least in Safari), see this Github issue.
Therefore, I wrote my own Pipe using the DomSanitizer in order to get this work. Here is the code:
import { Pipe, PipeTransform } from '#angular/core';
import { DomSanitizer } from '#angular/platform-browser';
#Pipe({name: 'safeStyle'})
export class SafeStylePipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) {}
transform(style) {
return this.sanitizer.bypassSecurityTrustStyle(style);
}
}
Our custom pipe can then be used as usual with:
<div [attr.style]="customCss.value|safeStyle">CUSTOM STYLED DIV</div>

Related

Inject ordered list with innerHTML in Angular

I'm using Angular CLI v13.3.6 with Node v16.12.0 and I've a problem when I use innerHTML property.
I'm using Angular CLI v13.3.6 with Node v16.12.0.
In typescript file I've a variable with an ordered list like this:
let myText = "<ol><li>first</li><li>second</li></ol>";
I need to show this text in a disabled div, so this is the code that I'm using in the html file:
<div id="myId" class="myClass" [innerHTML]="myText" disabled></div>
The result is that the text is shown but the numbers not. The same issue is present when I use the unordered lists. How can I do?
For security reasons, Angular compiler does not accept any string to be injected as HTML. You can bypass this by using DOMSanitizer to "trust" the string and parse it as valid HTML.
You will have to import DomSanitizer and inject it in yopur constructor like:
import { DomSanitizer } from '#angular/platform-browser';
constructor(private readonly domSanitizer: DomSanitizer) { }
And then use DomSanitizer to trust your string to parse it as HTML. You can use like this:
this.domSanitizer.bypassSecurityTrustHtml(YOUR_STRING_HTML);
This has nothing to do with Sanitize html. It is CSS issue.
So to avoid overwritten CSS. You could try:
Creating a brand new angular app: ng new. This way no third party CSS involved
Open the app in Incognito/Anonymous mode, this will help prevent any browser extensions to interfere with your app

CSS of one component causing unwanted formatting in another

I am still just learning in Angular, CSS, and HTML (all three are new), so have some patience with me please.
I received some code, and was given the task to fix some formatting.
Here is the problem:
When the page first loads, the page header has some padding. See picture below on the left.
However, when I navigate to another page, which has this code:
/* Removing padding and scroll bar from main page */
::ng-deep html > body > main#app-content {
overflow-y: hidden;
padding: 0;
}
and then navigate to any other component/page, the padding is gone and everything is moved all the way to left of the screen, which is very annoying. See picture below on the right. Note: someone told me that this is the code that is causing this situation, and I actually have no idea what it is actually doing (besides setting the padding and y-scroll).
This picture shows two components/pages before I access the page with the code above (shown on the left side in the picture), and then after I navigate to that page with the code above (shown on t eh right side in the picture). Note the green line is for reference to show how the padding is gone.
So I would like to have the original padding/formatting back when I navigate back to it after I access the page with the code above.
Also, can someone explain to me why it's doing this? And if possible, what does the code actually mean? Here are some specific questions:
How can I stop this from happening on another page?
What does "::ng-deep html > body > main#app-content" mean?
What does the greater sign do?
TLDR: Here is a stackblitz with an example of how to edit a global style using a service rather than ::ng-deep. https://stackblitz.com/edit/angular-ivy-p4pkdu?file=src/app/app.component.html
::ng-deep is an angular feature that promotes the following css to apply globally (everywhere in your application). It should really be avoided, as there is usually a better way to apply global styles. This feature is actually being deprecated, I'll put an alternative at the end of this answer.
html > body > main#app-content is just a CSS selector. In this case we are selecting the main element with id app-content, which has body as a parent, which has html as a parent. Here is a good reference for CSS syntax: https://www.w3schools.com/cssref/css_selectors.asp.
So we are applying these css styles to an html element of type main and with id app-content, the style is applied globally, so it will still persist after the encapsulating component is destroyed.
A better alternative to ::ng-deep is to use a service to edit global styles. First off, any global styles should be stored or imported into the global styles file, usually called styles.css in an angular project. If you only need the style in one component, you can put this css in the respective component css file instead. We declare it as a class so we can add it to an element dynamically.
In styles.css
.noPaddingOrScrollbar {
overflow-y: hidden;
padding: 0;
}
Then generate a service with the cli using ng g service <serviceName>. For example, to generate a service named globalStyleService in a folder called services we do ng g service services/global-style. We'll add a boolean to our service to indicate whether we want the style applied or not.
One caveat is that we need to use setTimeout to set the boolean, to avoid the dreaded NG0100: Expression has changed after it was checked error. setTimeout will default to a timeout of zero, but will still delay the code execution until after Angular finishes a round of change detection.
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root',
})
export class GlobalStyleService {
private _noPaddingOrScrollbar = false;
set noPaddingOrScrollbar(value: boolean) {
//Delay setting until after change detection finishes
setTimeout(() => (this._noPaddingOrScrollbar = value));
}
get noPaddingOrScrollbar() {
return this._noPaddingOrScrollbar;
}
constructor() {}
}
Now you need to find in what component this main#app-content element is actually located. It'll be in the html file of one of the parent components. You can then inject the service into this parent component ts file, and set the class dynamically in the component's html file.
Parent component ts file
export class ParentComponent {
constructor(public globalStyle: GlobalStyleService) {}
...
}
We use the angular directive [class.className]="boolean" to set the class dynamically.
Parent component html file
...
<main
id="app-content"
[class.noPaddingOrScrollbar]="globalStyle.noPaddingOrScrollbar"
></main>
...
Now you can add or remove this class from anywhere in your application. So in the component containing the hacky css, we inject the service, add the style during ngOnInit and remove it during ngOnDestroy. Of course remove the ::ng-deep statement from the css file as well.
Child component ts file
export class MyComponent implements OnInit, OnDestroy {
constructor(private globalStyle: GlobalStyleService) {}
ngOnInit(): void {
this.globalStyle.noPaddingOrScrollbar = true;
}
ngOnDestroy(): void {
this.globalStyle.noPaddingOrScrollbar = false;
}
...
}

Use styling for ng-content from template component

I've created a template case component which I'm intending to use for multiple cases. To use the component as a template, I used ng-content select="".
It works fine, but not completely as desired. For example:
I have a div with a background image, its style is being configured inside of the template component:
<div class="front-image min-vh-100 min-vw-100" [style.transform]="'scale(' + scale + ')'">
</div>
To make this usable as a template, I replaced the given code with: <ng-content select=".front-image"></ng-content> and used the template inside of another component like this:
<app-case-template *ngIf="cases[3] as case">
<div class="front-image min-vh-100 min-vw-100" [ngStyle]="{ 'background-image': 'url(' + case.image + ')'}"
[style.transform]="'scale(' + scale + ')'">
</div>
</app-case-template>
How can I achieve the template to always get its styling from the template component - right now I had to declare its styling inside of the new component to get it working. Additionally [style.transform] stopped working.
Is there something like a bypass?
You may be going about this the wrong way. I'll try to outline a good way to do it. First, have a directory for your templates:
templates
- template.component.css (generic styling)
- template.component.html (your markup)
- template.component.ts (your ts/js)
Set up your template.ts for use:
import {Component} from '#angular/core';
#Component({
selector: 'template-component',
templateUrl: 'template.component.html'
styleUrls: ['./template.component.css']
})
export class TemplateComponent {
}
Register it in your component.module.ts (or whatever you may have called it):
...
import {TemplateComonent} from './templates/template.component';
...
const exportedComponents = [
SvgIconComponent
]
If needed, have your shared.module.ts export it:
import {ComponentsModule} from './components/components.module';
...
exports: [ComponentsModule]
Now you can use it like it's a custom HTML tag (your selector):
<template-component class="template"> </template-component>
Now it will always grab the css styling from itself, but you can stick a custom class in that tag as I have done, or call it like this from your main component's style page:
template-component {
margin: 10px;
background-color: grey;
}
There's a lot you can do including setting #Input() tags in your template that can be called from your HTML tag. You can also use Angular bindings to dynamically enter values or text into it. Many options here.
Note: instead of calling them templates, consider calling them shared components, or shared modals (if so). Have a directory called shared > components and have a custom-name directory with the like in there for organization.

Changing fxFlex value in the Component with "setAttribute('fxFlex', '25%')" not Working in Angular 6

I'm using flexLayout module (see more in https://github.com/angular/flex-layout also in https://alligator.io/angular/flex-layout/) to build responsive div in my Angular application and I want to set the value of fxFlex attribute from my Angular Component.
code in the component.ts:
const nav = document.getElementById('nav2'); nav.setAttribute('fxFlex', '5%');
code in the html page:
<div fxLayout>
<div id="nav2" fxFlex="25%" class="col-md-2.5 bg-white p-1">hello</div>
<div id="nav1">word</div></div>
the code should normally should change the size Layout to 5%, but it's not the case, when i inspect the page I found that the attribute has been change but the layout still the same, oddly when i change it manually in the html code to 5%, i get the result that i wanted.
I'm using Angular 6, and Typescript 3.1.1
Please, If there is any suggestion, do not hesitate.
Thank You !
the code should normally should change the size Layout to 5%
No it should not.
Angular directives are ussed in Typescript, or in a pre-build context.
When you write
document.getElementById('nav2'); nav.setAttribute('fxFlex', '5%')
You instruct the Javascript, that has been compiled from Typescript (so in a post-build context) to get the element and add an Angular directive to your attribute.
The thing is, the code is already compiled, so you can't add typescript to Javascript, otherwise you would have to compile it again (and I'm not even sure you can do that).
I suggest you learn about angular features and how it works before using it. Also, consider posting a Minimal, Complete, and Verifiable example if you want someone to offer a solution adapted to your case.
In angular, you need to use the renderer2 to setAttribute dynamically.
Example:
HTML
<div #myDiv id="nav2" fxFlex="25%" class="col-md-2.5 bg-white p-1"></div>
component.ts
import {ElementRef,Renderer2} from '#angular/core';
...
export class MyComponent implements AfterViewInit {
#ViewChild('myDiv') el:ElementRef; // get the refernce of dom element.
constructor(private rd: Renderer2) {}
ngAfterViewInit() {
// Update dom after view initialized.
this.renderer2.setAttribute(this.el.nativeElement, "fxFlex", "5%");
}
}

Does an Angular 2 '#Component' decorator always need an element name selector?

In this example, from the official Angular 2 docs, the decorator looks like this:
#Component({
selector: 'my-app',
template: '<h1>My First Angular App</h1>'
})
Example: would prefer not not have my HTML code littered with non-standard elements, and would prefer something like (NB: ng-angular is only an example I would like to see):
import { Component } from '#angular/core';
#Component({
template: '<h1>Wait! Bloody wait some more.</h1>'
})
export class ListComponent { }
and used something like this:
<div ng-component="List"</div>
Or is the a Component decorator like this used only when you want to create a new HTML element, and then stick to a plain Listcontroller for the div in my example above?
A selector is not always needed eg. you have a top component of a module that is loaded by router and displayed in
selector is needed for any other type of component. otherwise angular wouldn't know what component it should render.
I haven't heard about attribute "ng-component"
[EDIT]
kit effectively answered correctly in his/her first comment:
You have to create an element that would enclose your template however it doesn't have to be a new HTML element because selector can be a element, [attribute] or class, eg.
<div test>
could be an element for component with selector: '[test]'
A component is a new HTML element, something like <my-component>Hello</my-component>.
I think what you want is a directive.
An Attribute directive changes the appearance or behavior of a DOM element.
So you can do something like <div makeItBlue>Blue stuff</div>
Just to elaborate: The selector can be a standard CSS-selector, so your HTML can be non-angular-centric.
Example:
#Component({
selector: 'div.player-list',
...
})
export class PlayerList {
}
will match <div class="player-list and-possibly-some-other-classes">...</div> (i.e. replacing the div with your angular template)