Apply CSS style based on JSON value - html

I am trying to apply some CSS to a particular section containing a certain JSON value.
Lets say I want to add a red background to a text containing the words "main article".
I have managed to hide the value by using a pipe like below
#Pipe({
name: 'exclusionfilter',
pure: false
})
#Injectable()
export class ExclusionFilterPipe implements PipeTransform {
transform(value: any) {
if((value!=null)&& (value.toLowerCase().indexOf("main article") != -1)){
return '';
}
else return value;
}
}
and the markup is like this
<h2 class="heading">{{ info.title | exclusionfilter }}</h2>
I want to apply some CSS to the h2 whenever the value is "main article".
I dont think that this approach is clean, but cant think of alternatives

Try to use dynamic css/class binding of angular :
<h2 class="heading" [ngClass]='{"redBackground" : info?.title == "main article"}'>{{ info.title | exclusionfilter }}</h2>
where redBackground is an css class having background Red.

You have to use ElementRef in that case. Find element from DOM and apply required style as below example: First find element who has text you required and then apply style
this.el.nativeElement.getElementsByClassName("k-grouping-header")[0].style.display = "block";
If you know at DOM level where to add style call it as below:
HTML:
<div [ngStyle]="setStyles()">
Typescript:
private setStyles(): any {
let styles = {
'font-size': '10px'
};
return styles;
}

Related

Angular innerHTML contents are jumbled. Only the last innerHTML is displayed correctly

In my Angular project (version 8) I am creating a list of static HTML from database and rendering it in parent HTML. Only the last div having innerHTML is rendered correctly, all the preceding divs having child html is not rendered correctly. The contents are jumbled. Basically the child html's style is not honored except for the last child html.
I am using sanitize html pipe for the div.
The angular component onInit queries DB in a loop. Each get call returns HTML text which is appended to an array of strings. The HTML text is basically PDF to HTML converted file. Each of the HTML file has its own style tag.
My guess is that only the last innerHTML's style is applied to all the preceding child innerHTML hence the jumbled contents (unless my guess is incorrect)
Any suggestion to solve the issue ?
HTML
<div *ngFor="let qBank of tsqm.selectedQuestions; let i = index">
<div class="page">
<div [innerHTML]="questionDataFromHtml[i] |
sanitizeHtml"></div>
</div>
</div>
Sanitize HTML:
#Pipe({ name: 'sanitizeHtml'})
export class SanitizeHtmlPipe implements PipeTransform {
constructor(private _sanitizer: DomSanitizer) { }
transform(value: string): SafeHtml {
return this._sanitizer.bypassSecurityTrustHtml(value);
}
}
Component:
ngOnInit(){
this.questionset = this.storage.get(quesId);
//pseudo code
forEach(item in this.questionset){
this.getHTMLfromDB(item)
}
}
getHTMLfromDB(question: QuestionBank) {
this.Service.getQuestionHtmlFile(question.questionFilePath).subscribe(res =>
{
this.questionDataFromHtml.push(res.text());
question.questionData.questionDataFromHtml = res.text();
});
Correct display. Question1 and Question2 are same
Correct display
Incorrect display. Question1 and Question2 are different
Incorrect display
Stackblitz:
stackblitz
The issue is all the css styling is overridden and the final values are applied.
Use id/class to apply the style to specific component.
I've made changes to your stackblitz example. Check here
In hello.component.ts
Applied red color to the text using text-red id.
export class HelloComponent {
#Input() name: string;
html1 =
"<html><head><style> #text-blue {color:blue;}</style></head><body><h2 id='text-blue'>Inner HTML1 in red</h2></body></html>";
html2 =
"<html><head><style> #text-red {color:red;}</style></head><body><h2 id='text-red'>Inner HTML2 in blue</h2></body></html>";
}
I solved this issue by using iFrame tag and srcdoc attribute. The backend service will return html text to angular. After DOM sanitizing the html documents are displayed in the iFrames.

Angular/Typescript Text with routerLink

Updated Question for more Clarity:
Need to display some texts and links as innerHTML(data from service/DB) in the Angular HTML and when user clicks, it should go to Typescript and programmatically navigates by router.navigate
Also, How to add DomSanitizer from #ViewChild/ElementRef
Added all example in below code
Here is the updated stackblitz code
As shown in screenshot from angular.io some texts and some links
Sorry, I didn't realize you answered my comment. Angular routing is not secondary, if you don't use Angular modules you'll end up with just an HTML/CSS/Typescript application. you need at least the RouterModule for Angular to be able to use routing and hence, do what it's supposed to with the DOM.
First:
You are not importing RouterModule
solution:
imports: [
BrowserModule,
FormsModule,
RouterModule.forRoot([]) // this one
]
Second:
You can't bind Angular events through innerHTML property
fix:
Make use of #ViewChild directive to change your innerHTML property and manually bind to the click event, so change in your app.component.html from
<div id="box" [innerHTML]="shouldbedivcontent" ></div>
to
<div #box id="box"></div>
Now, in your app.component.ts, add a property to hold a reference to that "box" element so you can later make some changes to the dom with it:
#ViewChild('box') container: ElementRef;
Implement AfterViewInit, that hook is where you will be able to actually handle your container, if you try using it for example in OnInit you'd get undefined because that component's html is not in the dom yet.
export class AppComponent implements AfterViewInit {
and
ngAfterViewInit() {
this.container.nativeElement.innerHTML = this.shouldbedivcontent;
this.container.nativeElement.addEventListener('click',
() => this.goto('bar')
);
}
change shouldbedivcontent property from:
'1) this is a click
<a (click)="goto("bar")">Click</a><br>
2)this is with routerlink
<a routerLink="" (click)="goto("bar")">Click</a><br>
3)This only works with href
bar and test'
to
'1) this is a click
<a id="link_1">Click</a><br>
2)this is with routerlink
<a [routerLink]="" (click)="goto(\'bar\')">Click</a><br>
3)This only works with href
bar and test'
And even so you'd still not get the default anchor style unless you apply some styling yourself.
Third
You are not HTML sanitizing, which could be dangerous. read more here
MY SUGGESTION:
Seems like a lot to do for you and a lot to read for someone else working alongside you for something you could easily do like in the example below!
Move your html to your app.component.html:
<div id="box">
1) this is a click
<a (click)="goto('bar')">Click</a><br>
2)this is with routerlink
<a routerLink="" (click)="goto('bar')">Click</a><br>
3)This only works with href
bar and test
</div>
<p>Below is actual content</p>
You'll notice that everything works now, except the anchor without routerLink or href, because that's not a link.
EDIT:
Looking at the new stackblitz, i suggest a change of approach, binding to innerHTML is ok when working with plain text or even some simple html but not a great choice to bind events or routing logic.
Angular's Renderer2 provides with a bunch of methods to dyncamically add elements to the DOM. With that on the table, you just need a little effort to take that simple html you get from your backend and turn it into something like (paste this property in your code to test it along the rest of the code provided below):
public jsonHTML = [
{
tagName: '',
text: 'some text with click ',
attributes: {
}
},
{
tagName: 'a',
text: 'bar',
attributes: {
value: 'bar' // goto parameter
}
},
{
tagName: '',
text: ' some more text with click ',
attributes: {
}
},
{
tagName: 'a',
text: 'foo',
attributes: {
value: 'foo' // goto parameter
}
}
]
Once you have it, it's way easier to create all of those elements dynamically:
this is for the code in your Q1:
Inject Renderer2 with private r2: Renderer2
And replace the Q1 related code in AfterViewInit hook to:
const parent = this.r2.createElement('div'); // container div to our stuff
this.jsonHTML.forEach((element) => {
const attributes = Object.keys(element.attributes);
const el = element.tagName && this.r2.createElement(element.tagName);
const text = this.r2.createText(element.text);
if (!el) { // when there's no tag to create we just create text directly into the div.
this.r2.appendChild(
parent,
text
);
} else { // otherwise we create it inside <a></a>
this.r2.appendChild(
el,
text
);
this.r2.appendChild(
parent,
el
);
}
if (attributes.length > 0) {
attributes.forEach((name) => {
if (el) {
this.r2.setAttribute(el, name, element.attributes[name]); // just the value attribute for now
if (name === 'value') {
this.r2.listen(el, 'click', () => {
this.goto(element.attributes[name]); // event binding with property "value" as parameter to navigate to
})
}
} else {
throw new Error('no html tag specified as element...');
}
})
}
})
this.r2.appendChild(this.container.nativeElement, parent); // div added to the DOM
No html sanitizer needed and no need to use routerLink either just inject Router and navigate to the route you want! Make improvements to the code t make it fit your needs, it should be at least a good starting point
Good Luck!
You have a css problem.
looks like a link
<a [routerLink]="something"></a> looks like a link, because if you inspect the HTML it actually gets an href property added because of routerLink
<a (click)="goTo()"></a> does NOT look like a link, because there is no href
Chrome and Safari default user agents css will not style <a> without an href (haven't confirmed Firefox but I'm sure its likely). Same thing for frameworks like bootstrap.
Updated stackblitz with CSS moved to global, not app.css
https://stackblitz.com/edit/angular-ivy-kkgmkc?embed=1&file=src/styles.css
This will style all links as the default blue, or -webkit-link if that browser supports it. It should be in your global.css file if you want it to work through the whole app.
a {
color: rgb(0, 0, 238);
color: -webkit-link;
cursor: pointer;
text-decoration: underline;
}
this works perfectly for me :D
#Directive({
selector: "[linkify]",
})
// * Apply Angular Routing behavior, PreventDefault behavior
export class CustomLinkDirective {
#Input()
appStyle: boolean = true;
constructor(
private router: Router,
private ref: ElementRef,
#Inject(PLATFORM_ID) private platformId: Object
) {}
#HostListener("click", ["$event"])
onClick(e: any) {
e.preventDefault();
const href = e.target.getAttribute("href");
href && this.router.navigate([href]);
}
ngAfterViewInit() {
if (isPlatformBrowser(this.platformId)) {
this.ref.nativeElement.querySelectorAll("a").forEach((a: HTMLElement) => {
const href = a.getAttribute("href");
href &&
this.appStyle &&
a.classList.add("text-indigo-600", "hover:text-indigo-500");
});
}
}
}
HOW I USE IT
<p linkify
class="mt-3 text-lg text-gray-500 include-link"
[innerHtml]="apiSectionText"
></p>
result

#ContentChildren not picking up items inside custom component

I'm trying to use #ContentChildren to pick up all items with the #buttonItem tag.
#ContentChildren('buttonItem', { descendants: true })
This works when we have the ref item directly in the parent component.
<!-- #ContentChildren returns child item -->
<parent-component>
<button #buttonItem></button>
<parent-component>
But, if the element with the #buttonItem ref is wrapped in a custom component, that does not get picked by the #ContentChildren even when I set the {descendants: true} option.
<!-- #ContentChildren returns empty -->
<parent-component>
<child-component-with-button-ref></child-component-with-button-ref>
<parent-component>
I have created a simple StackBlitz example demonstrating this.
Doesn't appear to be a timeline for a resolution of this item via github... I also found a comment stating you cannot query across an ng-content boundary.
https://github.com/angular/angular/issues/14320#issuecomment-278228336
Below is possible workaround to get the elements to bubble up from the OptionPickerComponent.
in OptionPickerComponent count #listItem there and emit the array AfterContentInit
#Output() grandchildElements = new EventEmitter();
#ViewChildren('listItem') _items
ngAfterContentInit() {
setTimeout(() => {
this.grandchildElements.emit(this._items)
})
}
Set template reference #picker, register to (grandchildElements) event and set the $event to picker.grandchildElements
<app-option-picker #picker [optionList]="[1, 2, 3]" (grandchildElements)="picker.grandchildElements = $event" popup-content>
Create Input on PopupComponent to accept values from picker.grandchildElements
#Input('grandchildElements') grandchildElements: any
In app.component.html accept picker.grandchildElements to the input
<app-popup [grandchildElements]="picker.grandchildElements">
popup.component set console.log for open and close
open() {
if (this.grandchildElements) {
console.log(this.grandchildElements);
}
else {
console.log(this.childItems);
}
close() {
if (this.grandchildElements) {
console.log(this.grandchildElements);
}
else {
console.log(this.childItems);
}
popup.component change your ContentChildren back to listItem
#ContentChildren('listItem', { descendants: true }) childItems: Element;
popup.component.html set header expression
<h3>Child Items: {{grandchildElements ? grandchildElements.length : childItems.length}}</h3>
Stackblitz
https://stackblitz.com/edit/angular-popup-child-selection-issue-bjhjds?embed=1&file=src/app/option-picker/option-picker.component.ts
I had the same issue. We are using Kendo Components for angular. It is required to define Columns as ContentChilds of the Grid component. When I wanted to wrap it into a custom component and tried to provide additional columns via ng-content it simply didn't work.
I managed to get it working by resetting the QueryList of the grid component AfterViewInit of the custom wrapping component.
#ViewChild(GridComponent, { static: true })
public grid: GridComponent;
#ContentChildren(ColumnBase)
columns: QueryList<ColumnBase>;
ngAfterViewInit(): void {
this.grid.columns.reset([...this.grid.columns.toArray(), ...this.columns.toArray()]);
this.grid.columnList = new ColumnList(this.grid.columns);
}
One option is re-binding to the content child.
In the template where you are adding the content child you want picked up:
<outer-component>
<content-child [someBinding]="true" (onEvent)="someMethod($event)">
e.g. inner text content
</content-child>
</outer-component>
And inside of the example fictional <outer-component>:
#Component()
class OuterComponent {
#ContentChildren(ContentChild) contentChildren: QueryList<ContentChild>;
}
and the template for <outer-component> adding the <content-child> component, re-binding to it:
<inner-template>
<content-child
*ngFor="let child of contentChildren?.toArray()"
[someBinding]="child.someBinding"
(onEvent)="child.onEvent.emit($event)"
>
<!--Here you'll have to get the inner text somehow-->
</content-child>
</inner-template>
Getting that inner text could be impossible depending on your case. If you have full control over the fictional <content-child> component you could expose access to the element ref:
#Component()
class ContentChildComponent {
constructor(public element: ElementRef<HTMLElement>)
}
And then when you're rebinding to it, you can add the [innerHTML] binding:
<content-child
*ngFor="let child of contentChildren?.toArray()"
[someBinding]="child.someBinding"
(onEvent)="child.onEvent.emit($event)"
[innerHTML]="child.element.nativeElement.innerHTML"
></content-child>
You may have to sanitize the input to [innerHTML] however.

Reactjs: How to put HTML into primaryText of a list item?

I would like to have a span inside the ListItem like this:
<ListItem
primaryText={"<span class='inner'>Some important info</span>" + item.title}
/>
When this is rendered, I don't get an HTML span element, but a text <span class='inner'>Some important info</span>Title of the list item. How to make HTML render as HTML?
Remove "" around the span, because when you use " it will get converted into string, it will not be treated as html tag.
Write it like this this:
primaryText={<div>
<span className='inner'>Some important info</span>
{item.title}
</div>}
Note: class is reserved keyword so, to apply css classes use className.
EDIT: Ignore me, just saw you needed it specifically for a ListItem
If you need to render HTML within an element, you can use the dangerouslySetInnerHTML prop (but it comes with some risks, and the name suggests):
function createMarkup() {
return {__html: 'First ยท Second'};
}
function MyComponent() {
return <div dangerouslySetInnerHTML={createMarkup()} />;
}
Docs here:
https://facebook.github.io/react/docs/dom-elements.html#dangerouslysetinnerhtml
Based on the info given, you should move the span inside the ListItem component and deal with it there rather than passing in the props.
<ListItem
primaryText={ 'Some important info' }
title={ item.title }
/>
//List Item Component
import React from 'react'
const ListItem = ( props ) => {
return (
<li>
<span className='inner'>{ props.primaryText }</span>{ ` ${props.title}` }
</li>
)
}
export default ListItem

What is the React equivalent of an Angular directive that only works on attributes?

For example you could have a directive in angular like so:
angular.module('app')
.directive('classy', function() {
return {
restrict: 'A',
link: function($scope, $el) {
$el.addClass('stay-classy');
}
}
}
And implement like so:
<div classy></div>
There doesn't seem to be an equivalent in React that I've seen after reading through most the docs and googling. I was hoping for something like:
...
render: function() {
return (
<MyComponent classy></MyComponent>
);
}
Is there something like that possible that I've been missing? Is there a different yet functionally similar equivalent? Or maybe this question just shows that I'm missing some part of the "React way" and I shouldn't ever want to do this. Thanks!
It will be helpful to consider what Angular and React are each doing "behind the scenes."
In your Angular example, when you write <div classy/></div> you're saying "render a DIV element and then attach to it the behaviors defined by the classy directive.
In your React example, when you write <MyComponent classy></MyComponent>, you're saying, "create an instance of MyComponent and pass it the props { classy: true }. The transpiler (Babel or whathaveyou) will turn it into the following JavaScript:
React.createElement(MyComponent, { classy: true });
So the answer to your question is that you can't write <MyComponent classy></MyComponent> because MyComponent component doesn't know what to do with the classy prop. In React, you might write something like this instead:
class ClassyDiv extends React.Component {
render() {
const { className, ...rest } = this.props;
return <div className={`${className || ''} stay-classy`} {...rest}/>;
}
}
This works because we know the React.DOM.div component (like most DOM components) knows what to do with the className prop.
Since React 0.14 we can express something like this more simply, as a "pure" stateless functional component, i.e. a function that accepts props and returns the rendered result:
function AlsoClassyDiv(props) {
const { className, ...rest } = props;
return <div className={`${className || ''} stay-classy`} {...rest}/>;
};
You can see both approaches in action in the below snippet.
class ClassyDiv extends React.Component {
render() {
const { className, ...rest } = this.props;
return <div className={`${className || ''} stay-classy`} {...rest}/>;
}
}
function AlsoClassyDiv({ className, ...props }) {
return <div className={`${className || ''} stay-classy`} {...props}/>;
};
ReactDOM.render(
<div id="container">
<div>Regular div</div>
<ClassyDiv>ClassyDiv!</ClassyDiv>
<AlsoClassyDiv>AlsoClassyDiv!</AlsoClassyDiv>
</div>,
document.body
);
.stay-classy { font: bold 3em Helvetica; text-shadow: 4px 4px 2px #aaa; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
One way you could implement similar behavior is using React class mixins
A great example of a useful directive in angular is
Target
The smoothScroll directive would intercept the click event then use window scroll or jquery scrollTo to apply all manner of animation.
Anywhere in the html one could then simply use the directive powered class name.
This sort of thing is not available in React. To do it in React you would have to create a special link component to use instead of:
<a> like ASmooth....
I was looking to find a way to reproduce the directive system for applying style or play with the component.
You can create a component that play with children and then render them :
function TextCenter(props) {
// Iterates over children and clone it with custom props
const children = React.Children.map(
props.children,
(child) => React.cloneElement(child, { className: 'text-center' }
)
// Render the children
return <>{children}</>;
}
function MyComponent() {
return (
<TextCenter>
<div>
<h1>Hello centered world</h1>
<p>Yessss</p>
</div>
</TextCenter>
)
}
Here is a more powerfull example for responsive text alignement :
interface Props extends Breakpoints<'start' | 'center' | 'end'>{}
export const TextAlign: FunctionComponent<Props> = (props) => {
const className = generateClassName('text', props);
const children = React.Children.map(props.children, child => React.cloneElement(child as ReactElement, { className }))
return (
<>
{children}
</>
)
}
export const MyComponent: FunctionComponent<Props> = (props) => {
return (
<div>
<TextCenter xs="center" md="start">
<h1>I am centered on mobile but not on desktop</h1>
</TextCenter>
</div>
)
}
There are two problems with this solution, when the children is a component, it must also have the prop className and it also makes the HTML less clean as it adds a level in hierarchy.
Look my friend i didn't get you well but long story short, angularJS directives is actually a component. So the idea behind angularJs directive is to create component that has its own scope data and it's own method to operate on it. I was thinking the same way you did and found your post here and i couldn't find an answer for that. But thanks for working experience, i thought about it and know how to do it.
I wanted to add an edit button for each link item in a list to toggle the edit form for each one only so each ListItem should be a stand alone component, that way i have standalone state for each one and i toggle it on & off.