How to applay CSS .class from innerHTML - css

I have pipe:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'spaning',
pure: false
})
export class SpanPipe implements PipeTransform
{
transform(value: string): string
{
return "<span class='mark'>xxx</div>"+value;
}
}
And use it like this:
<div [innerHTML]="movie.title| spaning"></div>
How to style .mark class in css? I want that xxx become red. I do not interested in workaround, class must be added in pipe, as above.
Answer is somehow related to
Angular 2 - innerHTML styling, but I can't find solution by myself.
If I just add style to my component where I use this pipe:
.mark{
color: red;
}
I get:
"WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss)."

[innerHTML] can not be used without DOMSanitizer provider or it will throw security error. You can use DOMSanitizer provider in your custom pipe to sanitize your HTML as shown below,
import { Pipe, PipeTransform } from '#angular/core';
import { DomSanitizer, SafeHtml } from '#angular/platform-browser'
#Pipe({
name: 'spaning',
pure: false
})
export class SpanPipe implements PipeTransform
{
constructor(private sanitized: DomSanitizer) {}
transform(value: string,color:any): SafeHtml{
return this.sanitized.bypassSecurityTrustHtml("<span class='mark' [ngStyle]="{'color':color}">xxx</div>"+value);
}
}
HTML
<div [innerHTML]="movie.title| spaning :'red'"></div>

https://plnkr.co/edit/p0hsn57WT9FfO6E6lRjL?p=info <- plunkr
Turn the view encapsulation mode for your component to 'None' for the hard-coded class to be work in the component
import { ViewEncapsulation } from '#angular/core'
in the decorator
selector: 'your-component',
encapsulation: ViewEncapsulation.None,
then sanitize the HTML in your pipe before returning it
export class SpanPipe implements PipeTransform
{
constructor(private sanitized: DomSanitizer) {}
transform(value: string): any {
return this.sanitized.bypassSecurityTrustHtml("<span class='mark'>xxx</div>"+value);
}
}

EDIT:
Sorry for that.. When you're inserting new html tags you must use DOMSanitizer.
I'm attaching plunker to show how to use it properly
https://plnkr.co/edit/vBnF9hPSpw46053FQ08G?p=preview
You can use ngStyle.
Pipes transform function get two parameters: 'value' and 'args':
export interface PipeTransform {
transform(value: any, ...args: any[]): any;
}
So you can pass your pipe arguments. In this case I'm passing the string 'red' (Witch could easily be a variable..) and use it inside the transform function.
.html:
<div [innerHTML]="movie.title| spaning :'red'"></div>
.ts
import { Pipe, PipeTransform } from '#angular/core';
import { DomSanitizer } from '#angular/platform-browser';
#Pipe({
name: 'spaning',
pure: false
})
export class SpanPipe implements PipeTransform
{
transform(value: string,color:any): string
{
return this.sanitizer.bypassSecurityTrustHtml("<span class='mark' style=color:"+color+">xxx "+value+"</div>");
}
}

Related

Determining Action on a Specific Selector Angular Directives

Im trying to create a directive with two selectors in Angular 8. The logic is common between the two types of selectors except for one of the selectors should add an additional css class.
Heres what I'm attempting to do. To Call
<my-label type='error'>foo</my-label>
<my-label-circle type='error'>bar</my-label-circle>
I would like to reuse the my-label directive since the circle is just another css styling on it. Though i would not like to add it to my types input.
#Directive({
selector: '[my-label], [my-label-circle]'
});
class MyLabel {
#Input() type: LabelType = Default;
}
The HostBinding function on class will then just use the input to construct the element. How would I extend that functionality to also use the selector that was given. So for example on the HostBinding function would look like
if(selector == 'my-label-circle')
return 'label label-circle ${type}';
else
return 'label ${type}';
How do I get access to the selector used. Or is there a better way to look at it.
You can add one more optional Input parameter Shape and with the help of Renderer2 Service can add any css.
import { Directive, Renderer, ElementRef } from '#angular/core';
#Directive({
selector: '[my-label]'
})
export class ExploreRendererDirective {
private nativeElement : Node;
#Input() shape: 'default'| 'circle' = 'default';
#Input() type: LabelType = Default;
constructor( private renderer : Renderer2, private element : ElementRef )
{
this.nativeElement = element.nativeElement;
if (this.shape === 'circle') {
// Add css classes for circle.
this.renderer.setAttribute(nativeElement, 'class', 'your-class-here');
}
}
}
// for default
<label my-label type='error'>foo</label>
// for circle
<label my-label type='error' [shape]="'circle'">foo</label>
Or the second solution is two create two directives .
import { Directive, Renderer, ElementRef } from '#angular/core';
#Directive({
selector: '[my-label]'
})
export class ExploreRendererDirective {
private nativeElement : Node;
#Input() type: LabelType = Default;
constructor( private renderer : Renderer2, private element : ElementRef
)
{
this.nativeElement = element.nativeElement;
}
}
import { Directive, Renderer, ElementRef } from '#angular/core';
#Directive({
selector: '[label-circle]'
})
export class ExploreRendererDirective {
private nativeElement : Node;
#Input() type: LabelType = Default;
constructor( private renderer : Renderer2, private element : ElementRef
)
{
this.nativeElement = element.nativeElement;
// Add css classes for circle.
this.renderer.setAttribute(nativeElement, 'class', 'your-class-here');
}
}
// for default
<label my-label type='error' label-circle>foo</label>
Also you can use inheritance to enhance the solution.
Hope It will help you !!

Return firebase values from a service to a component angular 6

I'm creating an application with angular 6 and firebase using angularfire2, I chose to use the firestore where I have a collection called pages like in the image:
basically I created a service - "PagesService" where I have a function that returns the data of the page that I sent. I'm trying to use getPage to return the values to my component, and assign them to the form, nothing else I tried worked, only returns an "observable" that I can not work, does anyone have an idea of what I can do?
Full code, service:
import { Injectable } from '#angular/core';
import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from 'angularfire2/firestore';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
#Injectable()
export class PagesService {
private pagesCollection: AngularFirestoreCollection<any>;
private page: AngularFirestoreDocument<any>;
constructor(private afs: AngularFirestore) {
this.pagesCollection = afs.collection('pages');
}
getPage(pageName: string) {
return this.afs.doc<any>('pages/${pageName}').valueChanges();
}
addPages(pageName: string, pageForm: any) {
this.pagesCollection.doc(pageName).set(pageForm.value);
}
}
My component:
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup } from '#angular/forms';
import { Observable } from 'rxjs';
import { PagesService } from '../../services/pages.service';
#Component({
selector: 'app-quem-somos',
templateUrl: './quem-somos.component.html',
styleUrls: ['./quem-somos.component.scss']
})
export class QuemSomosComponent implements OnInit {
pageForm: FormGroup;
pageName: string = "wo-we-are";
page: any;
constructor(private pagesService: PagesService, private fb: FormBuilder) { }
ngOnInit() {
this.page = this.pagesService.getPage(this.pageName);
console.log(this.page);
this.pageForm = this.fb.group({
title: '',
content: ''
});
}
save() {
this.pagesService.addPages(this.pageName, this.pageForm);
}
}
obs: Sorry my english
If I have understand you right, When you say "Observable that I cannot work" is mean that you cannot access his data when you are trying to assign its values in the form?
In this case (I assume that your service is working as expected), just subscribe to it and populate the form after your values are ready to use. for example:
ngOnInit() {
this.pagesService.getPage(this.pageName).subscribe(v => {
// Here your data is ready, so you can send it to a function and populate the form as you need.
this.populateForm(v);
});
// Here I just construct the FormGroup, so your application can rendered.
this.pageForm = this.fb.group({
title: '',
content: ''
});
}
And add this function to do the task:
populateForm = (data) => {
console.log(data); // Just log it in console, and see if its the data that you seek for
}
Instead of console.log() you can populate your form or do what ever you need to.
Good Luck !
--EDIT--
I just noticed now, In your service:
getPage(pageName: string) {
return this.afs.doc<any>('pages/${pageName}').valueChanges();
}
You call the doc with ' ' instead of ``, so In fact, you are not using Template Strings. So your call is wrong and not fetch with the right path.
Change it to:
return this.afs.doc<any>(`pages/${pageName}`).valueChanges();

Angular 6 http not working with single JSON item

Trying to get Wordpress data into my Angular 6 component.
When I return a single post via Wordpress REST API it produces the right data (http://w3stage.com/tricap/wp-json/wp/v2/properties/174), but the data is not making it through to my template.
service:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Observable } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class WordpressService {
constructor(private http: HttpClient) { }
getProperty(id): Observable<any[]> {
return this.http.get<any[]>('http://w3stage.com/tricap/wp-json/wp/v2/properties/'+id);
}
}
component:
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { Location } from '#angular/common';
import { Observable } from 'rxjs';
import { WordpressService } from '../wordpress.service';
#Component({
selector: 'app-property-detail',
templateUrl: './property-detail.component.html',
styleUrls: ['./property-detail.component.scss']
})
export class PropertyDetailComponent implements OnInit {
property: Observable<any[]>;
constructor(
private route: ActivatedRoute,
private location: Location,
private wp: WordpressService
) {
this.getProperty();
}
getProperty(): void {
const id = +this.route.snapshot.paramMap.get('id');
this.property = this.wp.getProperty(id);
console.log(this.property);
}
ngOnInit(): void {
}
}
template:
{{ property.title.rendered }}
This generates the following error:
ERROR TypeError: Cannot read property 'rendered' of undefined
at Object.eval [as updateRenderer] (PropertyDetailComponent.html:8)
at Object.debugUpdateRenderer [as updateRenderer] (core.js:10782)
at checkAndUpdateView (core.js:10158)
at callViewAction (core.js:10394)
at execComponentViewsAction (core.js:10336)
at checkAndUpdateView (core.js:10159)
at callViewAction (core.js:10394)
at execEmbeddedViewsAction (core.js:10357)
at checkAndUpdateView (core.js:10154)
at callViewAction (core.js:10394)
However, when I adapt the code to return a bunch of posts from wordpress, I can get the data to work just fine in conjunction with an *ngFor loop. When I try an *ngFor loop with the single post result, same thing - no go.
You need to use safe navigation operator or *ngIf inorder to handle the delay of response from your asynchronous request,
change your template as,
{{ property?.title?.rendered }}
also you need to subscribe to the observable,
this.wp.getProperty(id).subscribe(data => {
this.property = data;
});

Angular Dynamic Components - Add Class and other attributes

I am using the following code for creating the dynamic components
import {
Component, OnInit, ViewContainerRef, ViewChild, ViewChildren,
ReflectiveInjector, ComponentFactoryResolver, ViewEncapsulation, QueryList, Input, AfterViewInit
} from '#angular/core';
import { Router, ActivatedRoute } from '#angular/router';
import { forEach } from '#angular/router/src/utils/collection';
import { IComponent } from 'app/app.icomponent';
#Component({
encapsulation: ViewEncapsulation.None,
selector: 'dynamic-component',
entryComponents: [HomeComponent, HighlevelSignalComponent],
template: `
<div #dynamicDiv [ngClass]="classFromMenu" >
<ng-template #dynamicComponentContainer></ng-template>
</div>
`,
styleUrls: [
'./dynamic-content.component.css'
],
})
export class DynamicComponent implements IComponent, OnInit, AfterViewInit {
classFromMenu: any;
#ViewChild('dynamicComponentContainer', { read: ViewContainerRef }) dynamicComponentContainer: ViewContainerRef;
constructor(private resolver: ComponentFactoryResolver, private route: Router,
private activatedRoute: ActivatedRoute, ) {
}
.......
buildComponent(passedData) {
// orderAndObjs has the data for creating the component
this.orderAndObjs.forEach(obj => {
var componentFactory = this.resolver.resolveComponentFactory(obj.component);
var compRef = this.dynamicComponentContainer.createComponent(componentFactory);
// compRef is the component that is created.
//Assuming the component that i am trying to create is <dynamic-component>.
//I want to add either a class or any other attribute like this
//<dynamic-component class="flex">
});
}
}
}
The dynamic-component is created perfectly fine and everything is working as expected. But the only issue is I want to add a class for dynamic-component so that it can be
<dynamic-component class="dynamicClass">
Any help is appreciated :(
Hmm.. I usually add it to the selector of component that is supposed to be an entryComponent ...
selector: 'dynamic-component.someclass',
^^^^^^^^^^^
to add attribute use attribute selector:
selector: 'dynamic-component[myattr=value]',
I call it hidden feature of entryComponents
but its declarative approach and can't be changed at runtime(indeed we can change it)
In Angular 5/6, using Renderer2 from #angular/core, you can do something like below:
constructor(private resolver: ComponentFactoryResolver, private route: Router,
private activatedRoute: ActivatedRoute, private renderer2: Renderer2) {
}
buildComponent(passedData) {
this.orderAndObjs.forEach(obj => {
var componentFactory = this.resolver.resolveComponentFactory(obj.component);
var compRef = this.dynamicComponentContainer.createComponent(componentFactory);
this.renderer2.addClass(compRef.location.nativeElement, 'flex');
});
}
High-level DOM operations are performed with Renderer2 provider. Considering that it was injected, it is:
this.renderer2.addClass(compRef.location.nativeElement, 'dynamicClass');
It should be noticed that depending on how dynamic element is attached to DOM, this may be unnecessary complication.
Considering that dynamicComponentContainer is real DOM element and not <ng-template>, the view of dynamic component can be directly mounted to the container, thus eliminating <dynamic-component> wrapper element:
Given the container:
<div class="dynamicClass" #dynamicComponentContainer></div>
It will be:
var compRef = componentFactory.create(
this.injector,
[],
this.dynamicComponentContainer.element.nativeElement
);

Binding is not working in Angular2 template

errorType is not showing when calling with Alert.showAlert("success","someMsg");from another Component but it's working when initializing at the declaration of errorType itself.
component :
import {Component} from 'angular2/core';
#Component({
selector: 'alert-component',
templateUrl: 'app/alert.template.html'
})
export class Alert {
public static errorType:string;
public static messageAlrt:string;
public static showAlert(type:string, message:string): void{
Alert.errorType=type;
}
}
template :
<div id="messageAlert" >
<strong>{{errorType}}:</strong> this is the error message at top of the page
</div>
Really apreciate your help in resolving this problem that errrorType value is not getting bound to erroType
It's because you use static fields. When using {{errorType}}, a non static property of the component is used.
I would refactor your component this way:
import {Component} from 'angular2/core';
#Component({
selector: 'alert-component',
templateUrl: 'app/alert.template.html'
})
export class Alert {
public errorType:string;
public messageAlrt:string;
}
When you want to display your alert, I would add it dynamically:
#Component({
(...)
template: `<div #target></div>`
})
export class SomeComponent {
#ViewChild('target', {read: ViewContainerRef}) target;
showAlert(type:string, message:string) {
this.resolver.resolveComponent(Alert).then(
(factory:ComponentFactory<any>) => {
this.cmpRef = this.target.createComponent(factory);
this.cmpRef.type = type;
}
);
}
See this great Günter's answer:
Angular 2 dynamic tabs with user-click chosen components

Resources