Angular 2, Adding calc() as inline style. Unsafe interpolation using parentheses - javascript

Angular 2 rc3
I am trying to dynamically add calc() to an element in a template. I have something like this.
template : `<div attr.style.width="{{width}}></div>"`
export myClass
{
#Input() myInputObject:any;
private width:string;
ngOnInit() { this.setWidth()}
private setWidth()
{
let percent = myInputObject.percent;
this.width = 'calc(' + percent + '% - 20px)';
}
}
If I use the parenthesis the ouput looks like this in the DOM.
<div style="unsafe"></div>
If I take out the parenthesis it works (sort of) It looks like this.
<div style="calc10% - 20px"></div>
This also doesn't work.
<div attr.style.width="calc({{width}} - 20px)">
Any help on how to add calc() to the template is much appreciated. Note I also tried replacing the parenthesis with ( and ). That also came back as "unsafe".
Example: (rc1)
I am using rc3 in my environment. But I was able to reproduce the same issue with RC1 in plunker. I am assuming this is a security thing. But there must be a way to add calc() to a style attribute. Maybe there is a better way than this?
https://plnkr.co/edit/hmx5dL72teOyBWCOL0Jm?p=preview

Calculated styles should be sanitized.
Here is the solution for you:
import {DomSanitizationService} from '#angular/platform-browser';
#Component({
selector: 'my-app'
template: `
<div [style.width]="width">
<h2>Hello {{name}}</h2>
</div>
`
})
export class App {
private width:string;
constructor(sanitizer: DomSanitizationService) {
this.name = 'World'
this.width = sanitizer.bypassSecurityTrustStyle("calc(10% - 20px)");
}
}

You can also try using ngStyle instead:
[ngStyle]="{'width': 'calc(' + percent + '% - 20px)'}"
And just bind 'percent' value to the input value.

Related

Creating directive like/dislike in Angular

I have something like this, like and dislike button,with font-awesome icons
<ng-container *ngFor="let answer of question.answers">
<p class="answers">{{answer.text}} <i class="fa fa-hand-o-left" (click)="likeDislike($event,answer.id,'fa-thumbs-up')"></i></p>
</ng-container>
And some function
likeDislike(event: any, answerId: string, haveClass: string) {
const hasClass = event.target.classList.contains(haveClass);
if (hasClass) {
this.renderer.removeClass(event.target, 'fa-thumbs-up');
this.renderer.addClass(event.target, 'fa-thumbs-down');
} else {
this.renderer.removeClass(event.target, 'fa-thumbs-down');
this.renderer.addClass(event.target, 'fa-thumbs-up');
}
}
I dont think this is good example, can somebody help me maybe to make a directive?
You could put it in a Component. The two-way binding is a nice extra.
A live demo
Check this stackblitz demo.
Call it like this
<app-fa-like [(liked)]='liked'></app-fa-like>
Component code
Note: You won't need the styles or the __, it's just in here for demo purposes. Font-awesome should take care of that in your app.
import {Component, Input, Output, EventEmitter} from '#angular/core';
#Component({
selector: 'app-fa-like',
template: `
<i
class='fa'
[class.fa-thumbs-up]='liked'
[class.fa-thumbs-down]='!liked'
(click)='toggle()'
>__</i>`,
styles: [`
.fa.fa-thumbs-up{background: green;}
.fa.fa-thumbs-down{background: red;}
`]
})
export class LikeComponent{
#Input('liked') liked = true;
#Output() likedChange: EventEmitter<boolean> = new EventEmitter();
toggle(): void {
this.liked = !this.liked;
this.likedChange.emit(this.liked);
}
}
since you are using angular, you have lots of options. I'm not sure any is better or worse than the others. Here's some ideas
Use ngClass
Use *ngIf
Use angular variable. something like this would work fine:
<i class="fa {{answer.faFont}}" (click)="toggleIcon(answer)"></I>
toggleIcon(answer:any) {
answer.faFontFlg = !answer.faFontFlg;
answer.faFont = (answer.faFontFlg)?'fa-thumbs-up':'fa-thumbs-down';
}

Use variable in style tag in angular template?

I am working on angular 5 application, and I have requirement of applying dynamic css in style tag in template.
I have tried some solutions but they are not working.
app.component.ts
customCss : any;
constructor(){}
ngOnInit(){
this.customCss['color'] = "red";
}
app.component.html
<div>
<span class="custom_css">This is angular 5 application</span>
</div>
<style>
.custom_css{
color: {{customCss.color}};
}
</style>
When I inspect the custom_css class in browser then in style it shows
.custom_css{
color: {{customCss.color}};
}
Any help is appreciated.
You can use [ngStyle] directive:
<span [ngStyle]="{'color': 'red'}">
This is angular 5 application
</span>
Or like so:
<span [ngStyle]="applyStyles()">
This is angular 5 application
</span>
And in component:
applyStyles() {
const styles = {'color' : 'red'};
return styles;
}
By the way if you set the color like this:
<div [style.color]="color"></div>
where color='var(--cssValue)' it would not work!
However, this works correctly:
<div [ngStyle]="{color: color}"></div>
The given answer works if you have few elements to change in a given component, if you need to change the full overall look and feel of your app based on user's choice (and on the fly), the only way i found so far is to override css in the javascript like the following:
this.stylesService.get().subscribe(({ customStyles }) => {
const style = document.createElement('style');
style.innerHTML =
`.picture {
background-image: url(${customStyles.backgroundUrl});
}
h1, h2 {
color: ${customStyles.primaryColor};
}`;
document.body.appendChild(style);
});
You can use [style.customClass]=“methodInComponent()”...
This will apply the class if the method in your component returns true.

Modify component with Renderer and ElementRef

1 ISSUE
I am trying to implement the following:
I have a container component ContainerComponent and child components ChildComponent. I want to modify the rendering and overall behaviour of the child components via the controlling ContainerComponent.
2 TECHNOLOGIES USED
Angular2, HTML, CSS, Javascript, Typescript, ES6
3 CODE
ContainerComponent.ts
export class ContainerComponent {
children: Array<Child>;
constructor(
private _el: ElementRef,
private _dcl: DynamicComponentLoader,
private _childService: ChildService) {
}
ngOnInit() {
let index = 0; // index of child component in container
this._childService.getChildren().then( // get the children models
(children) => {
this.children = children;
this.children.forEach((child, index) => {
this._dcl.loadIntoLocation(ChildComponent, this._el, 'dynamicChild')
.then(function(el){
el.instance.child = child; // assign child model to child component
el.instance.index = index;
});
});
}
);
}
}
ChildComponent.ts
export class ChildComponent {
child: Child;
index: number;
constructor(private _renderer: Renderer, private _el: ElementRef) {
}
ngOnInit() {
let delay = (this.index + 1) * 0.5; // calculate animation delay
this._renderer.setElementStyle(this._el, '-webkit-animation-delay', delay + 's !important');
this._renderer.setElementStyle(this._el, 'animation-delay', delay + 's !important');
}
}
4 CODE EXPLANATION
In the above code, the ContainerComponent dynamically inserts ChildComponents (granted, this could be done without the DynamicContentLoader).
The ChildComponents should dynamically add css properties, in this case, the animation delay once it is displayed. So based on the index of the child, the animation delay increases.
However the modifications from the renderer do not take effect, the css properties are not there at runtime.
I tried to reproduce your problem. In fact, I have problem to add styles like -webkit-animation-delay and animation-delay.
If I try with another style like color, it works fine and the style is taken into account at runtime.
ngOnInit() {
this._renderer.setElementStyle(this._el, 'color', 'yellow');
}
So it seems to be linked to animation styles... I see these links that could interest you:
How to set CSS3 transition using javascript?
http://www.javascriptkit.com/javatutors/setcss3properties.shtml
Otherwise it seems that there is some support for animation in Angular2 but it's not really documented... See this file: https://github.com/angular/angular/blob/master/modules/angular2/src/animate/animation.ts.
Hope it helps you,
Thierry
This seems to be a bug in angular2 itself. Adding !important to a style will result in an illegal value to the style and it is not applied to the element. The correct way in plain js is to use another parameter which implies if the style is important.
So the correct answer is to use:
this._renderer.setElementStyle(this._el, 'animation-delay', delay + 's'); //sans !important
and if you want to add !important you have to use:
this._el.nativeElement.style.setProperty('animation-delay', delay + 's', 'important');
The -webkit- prefix gets added (or removed) if necessary, so there is no need to add that as well
From here:
https://angular.io/docs/ts/latest/api/core/ElementRef-class.html
You should only use ElementRef as an absolute last resource. The whole idea of Angular 2 is that you don't have to mess with the dom at all. What you are trying to do can be acomplished very easy using a template:
import {NgStyle} from 'angular2/common';
import {Component} from "angular2/core";
#Component({
selector: 'app',
template: `
<div *ngFor="#child of children; #i = index">
<div [ngStyle]="{ 'z-index': i * multiplier,
'-webkit-animation-delay': i * multiplier + 's',
'animation-delay': i * multiplier + 's' }"> {{i}} - {{child}} </div>
</div>
`,
directives: [NgStyle]
})
export class AppComponent{
public children:string[] = [ "Larry", "Moe", "Curly" ];
public multiplier:number = 2;
}
Depending on the browser you might see those css properties or not, that's why I added the z-index which is more common and old so you can see you can render the css value dynamically using the index variable from ngFor inside a template.
I hope this helps !

How to combine JSX component with dangerouslySetInnerHTML

I'm displaying text that was stored in the database. The data is coming from firebase as a string (with newline breaks included). To make it display as HTML, I originally did the following:
<p className="term-definition"
dangerouslySetInnerHTML={{__html: (definition.definition) ? definition.definition.replace(/(?:\r\n|\r|\n)/g, '<br />') : ''}}></p>
This worked great. However there's one additional feature. Users can type [word] and that word will become linked. In order to accomplish this, I created the following function:
parseDefinitionText(text){
text = text.replace(/(?:\r\n|\r|\n)/g, '<br />');
text = text.replace(/\[([A-Za-z0-9'\-\s]+)\]/, function(match, word){
// Convert it to a permalink
return (<Link to={'/terms/' + this.permalink(word) + '/1'}>{word}</Link>);
}.bind(this));
return text;
},
I left out the this.permalink method as it's not relevant. As you can see, I'm attempting to return a <Link> component that was imported from react-router.However since it's raw HTML, dangerouslySetInnerHTML no longer works properly.
So I'm kind of stuck at this point. What can I do to both format the inner text and also create a link?
You could split the text into an array of Links + strings like so:
import {Link} from 'react-router';
const paragraphWithLinks = ({markdown}) => {
const linkRegex = /\[([\w\s-']+)\]/g;
const children = _.chain(
markdown.split(linkRegex) // get the text between links
).zip(
markdown.match(linkRegex).map( // get the links
word => <Link to={`/terms/${permalink(word)}/1`}>{word}</Link> // and convert them
)
).flatten().thru( // merge them
v => v.slice(0, -1) // remove the last element (undefined b/c arrays are different sizes)
).value();
return <p className='term-definition'>{children}</p>;
};
The best thing about this approach is removing the need to use dangerouslySetInnerHTML. Using it is generally an extremely bad idea as you're potentially creating an XSS vulnerability. That may enable hackers to, for example, steal login credentials from your users.
In most cases you do not need to use dangerouslySetHTML. The obvious exception is for integration w/ a 3rd party library, which should still be considered carefully.
I ran into a similar situation, however the accepted solution wasn't a viable option for me.
I got this working with react-dom in a fairly crude way. I set the component up to listen for click events and if the click had the class of react-router-link. When this happened, if the item has a data-url property set it uses browserHistory.push. I'm currently using an isomorphic app, and these click events don't make sense for the server generation, so I only set these events conditionally.
Here's the code I used:
import React from 'react';
import _ from 'lodash';
import { browserHistory } from 'react-router'
export default class PostBody extends React.Component {
componentDidMount() {
if(! global.__SERVER__) {
this.listener = this.handleClick.bind(this);
window.addEventListener('click', this.listener);
}
}
componentDidUnmount() {
if(! global.__SERVER__) {
window.removeEventListener("scroll", this.listener);
}
}
handleClick(e) {
if(_.includes(e.target.classList, "react-router-link")) {
window.removeEventListener("click", this.listener);
browserHistory.push(e.target.getAttribute("data-url"));
}
}
render() {
function createMarkup(html) { return {__html: html}; };
return (
<div className="col-xs-10 col-xs-offset-1 col-md-6 col-md-offset-3 col-lg-8 col-lg-offset-2 post-body">
<div dangerouslySetInnerHTML={createMarkup(this.props.postBody)} />
</div>
);
}
}
Hope this helps out!

Auto growing textarea in ionic

I am trying to add an autogrowing textarea to my app but for some reason it is not working. The module that I am using is https://github.com/tagged/autogrow (it was recommneded on the ionic forum)
The answer above does not shrink - here is an improved version:
https://codepen.io/benshope/pen/xOPvpm
angular.module('app').directive('expandingTextarea', function () {
return {
restrict: 'A',
controller: function ($scope, $element, $attrs, $timeout) {
$element.css('min-height', '0');
$element.css('resize', 'none');
$element.css('overflow-y', 'hidden');
setHeight(0);
$timeout(setHeightToScrollHeight);
function setHeight(height) {
$element.css('height', height + 'px');
$element.css('max-height', height + 'px');
}
function setHeightToScrollHeight() {
setHeight(0);
var scrollHeight = angular.element($element)[0]
.scrollHeight;
if (scrollHeight !== undefined) {
setHeight(scrollHeight);
}
}
$scope.$watch(function () {
return angular.element($element)[0].value;
}, setHeightToScrollHeight);
}
};
});
This will transform all your textareas to grow/shrink.
Hope that helps!
I wrote a very simple directive that works with Ionic 2 and ion-textarea. Here it is:
import { Directive, HostListener, ElementRef } from "#angular/core";
#Directive({
selector: "ion-textarea[autoresize]" // Attribute selector
})
export class Autoresize {
#HostListener("input", ["$event.target"])
onInput(textArea: HTMLTextAreaElement): void {
this.adjust();
}
constructor(public element: ElementRef) {
}
ngOnInit(): void {
this.adjust();
}
adjust(): void {
let ta = this.element.nativeElement.querySelector("textarea");
ta.style.overflow = "hidden";
ta.style.height = "auto";
ta.style.height = ta.scrollHeight + "px";
}
}
Here is a gist: https://gist.github.com/maxt3r/2485356e91a1969bdb6cf54902e61165
EDIT: Look at the gist for other suggestions from other people.
I found a much more better way to do this without using any other third party library or directive.
$scope.updateEditor = function() {
var element = document.getElementById("page_content");
element.style.height = element.scrollHeight + "px";
};
Then simply adding ng-keypress="updateEditor()" to the textarea would do the job.
<textarea ng-keypress="updateEditor()" ng-model="bar"> </textarea>
I Hope this helps others who might face this problem in the future.
Update: Here is a codepen for this: http://codepen.io/kpourdeilami/pen/KDepk
Update 2: Use the snippet provided by #benshope
Update 3: If you're on Ionic/Angular 2, use the answer provided by "Max Al Farakh"
Try Angular-Elastic. It is an angular directive built to auto-expand a textarea. Use bower to install it.
bower install angular-elastic
add it to your project, then you can use it as an attribute
<textarea msd-elastic ng-model="foo"> </textarea>
or as class
<textarea class="msd-elastic" ng-model="bar"> </textarea>
From Ionic 4.4 it's built-in, see the autoGrow property:
TextArea#Properties
<ion-textarea auto-grow="true" rows="1"></ion-textarea>
Do you mean vertically auto-growing? I tried this:
<textarea ng-model='doc.description'
rows='{{doc.description.length/50 + 1}}'
cols='50'></textarea>
Kinda hackish, but after having determined an expected column length, lets define the row length based on the length of the inputed text. It starts growing vertically when I start typing! (no scrolling/out of view text).
With ionic-5 , there is an option called auto-grow, set it to true in your view.
In css, set min-height, max-height, to control the text grow.
ion-textarea {
min-height: 100px;
max-height: 200px;
}
Also, after the above fix, if you get some odd behaviour with placeholder text, add below inside the ion-textarea
::ng-deep textarea {
min-height: 100px;
}
If it can serve someone, I changed a little bit benshope's solution since I needed the textarea to grow even when user do a carriage return.
So instead of listening to the changes on the input value (which didn't always fire when doing a carriage return) I listent the input event on the textarea.
(function () {
'use strict';
angular
.module('app')
.directive('expandingTextarea', expandingTextarea);
function expandingTextarea() {
return {
restrict: 'A',
controller: function ($scope, $element, $attrs, $timeout) {
$element.css('min-height', '0');
$element.css('resize', 'none');
$element.css('overflow-y', 'hidden');
setHeight(0);
$timeout(setHeightToScrollHeight);
function setHeight(height) {
$element.css('height', height + 'px');
$element.css('max-height', height + 'px');
}
function setHeightToScrollHeight() {
console.log('set height');
setHeight(0);
var scrollHeight = angular.element($element)[0]
.scrollHeight;
if (scrollHeight !== undefined) {
setHeight(scrollHeight);
}
}
angular.element($element)[0].addEventListener("input", setHeightToScrollHeight);
}
};
}})();
juste install :
bower install angular-elastic or
npm install angular-elastic;
then import the elastic.js file in your index.html like this
<script src="js/elastic.js" type="text/javascript"></script>
the after that inject it in you angular module like this:
angular.module('yourApp', ['monospaced.elastic']);
the after that in your html file, in your footer-bar do like this:
<ion-footer-bar style="height: auto; overflow: visible !important"><textarea rows="1" msd-elastic ng-model="myMsg">
</textarea>

Categories