Angular2: making an iframe src safe using a directive - javascript

I am trying to apply a sanitized src attribute to an iframe, directly it works fine, but when putting it all in an attribute directive, it refuses to play ball. Here is the directive code, and the error message that appears
import { OnInit, Directive, ElementRef, Input, Renderer } from '#angular/core';
import { DomSanitizer, SafeResourceUrl} from '#angular/platform-browser';
#Directive({
selector: '[resume]'
})
export class ResumeDirective implements OnInit {
#Input('resume') inputLink: string;
constructor(private _sanitizer: DomSanitizer, private el: ElementRef, private render: Renderer) {
}
ngOnInit(): void {
let _url: string = this.inputLink + '#zoom=100';
let resumeUrl: SafeResourceUrl = this._sanitizer.bypassSecurityTrustResourceUrl(_url);
// this.el.nativeElement.src = resumeUrl.toString(); // same result
this.render.setElementProperty(this.el.nativeElement, 'src', _url);
// using 'srcdoc' or setElementAttribute brings same results
}
}
I get error: SafeValue must use [property]=binding: /theurl/x.pdf#zoom=100 (see http://g.co/ng/security#xss)

You can try #HostBinding() - not sure if this will work though
#Directive({
selector: '[resume]'
})
export class ResumeDirective implements OnInit {
#Input('resume') inputLink: string;
constructor(private _sanitizer: DomSanitizer, private el: ElementRef, private render: Renderer) {
}
#HostBinding('src')
resumeUrl:any;
ngOnInit(): void {
let _url: string = this.inputLink + '#zoom=100';
this.resumeUrl = this._sanitizer.bypassSecurityTrustResourceUrl(_url);
}
}
this.render.setElementProperty doesn't care about sanitized values, it just calles to DOM APIs and passes the sanitized value as-is.

Related

get result in parent component after selectAll and unSelectAll event emitted from child (Angular)

Hi I am now working on angular to build a multiselect dropdown using ng-multiselect-dropdown(https://www.npmjs.com/package/ng-multiselect-dropdown).
I used parent-child component communication through event emitter:
in child.component.ts:
import {Component, EventEmitter, Input, OnInit, Output} from '#angular/core';
import {IDropdownSettings} from 'ng-multiselect-dropdown';
export interface IMultiDropdownConfig {
placeholder: string;
header: string;
dropdownSettings: IDropdownSettings;
}
#Component({
selector: 'app-multi-dropdown',
templateUrl: './multi-dropdown.component.html',
styleUrls: ['./multi-dropdown.component.scss']
})
export class MultiDropdownComponent implements OnInit {
#Input() dropdownItems: any[];
#Input() selectedItems;
#Input() header: string;
#Input() placeholder: string;
#Input() dropdownSettings: IDropdownSettings;
#Input() loading;
#Output() itemSelectEvent = new EventEmitter();
#Output() itemDeselectEvent = new EventEmitter();
#Output() selectAllEvent = new EventEmitter();
#Output() deselectAllEvent = new EventEmitter();
#Output() selectedItemsChange = new EventEmitter();
constructor() { }
ngOnInit(): void {
}
onSelectItem(event) {
this.selectedItemsChange.emit(this.selectedItems);
}
onDeselectItem(event) {
this.selectedItemsChange.emit(this.selectedItems);
}
onSelectAll(event) {
this.selectedItemsChange.emit(this.selectedItems);
}
onDeselectAll(event) {
this.selectedItemsChange.emit(this.selectedItems);
}
}
in child.component.html:
<div class="multi-dropdown-item">
<div class="multi-dropdown-header">{{header}}</div>
<div *ngIf="!this.loading" class="multi-dropdown-body">
<ng-multiselect-dropdown
[placeholder]="placeholder"
[data]="dropdownItems"
[(ngModel)]="selectedItems"
[settings]="dropdownSettings"
(onSelect)="onSelectItem($event)"
(onDeSelect)="onDeselectItem($event)"
(onSelectAll)="onSelectAll($event)"
(onDeSelectAll)="onDeselectAll($event)">
</ng-multiselect-dropdown>
</div>
</div>
Then in parent.component.html:
<app-multi-dropdown
[loading]="filterPropertiesMap.get(filterEntry).loading"
[dropdownItems]="filterPropertiesMap.get(filterEntry).items"
[(selectedItems)]="filterPropertiesMap.get(filterEntry).selectedItems"
[dropdownSettings]="filterPropertiesMap.get(filterEntry).setting"
[placeholder]="filterPropertiesMap.get(filterEntry).name"
[header]="filterPropertiesMap.get(filterEntry).name"
(itemSelectEvent)="onItemSelect($event)"
(itemDeselectEvent)="onItemDeselect($event)"
(selectAllEvent)="onSelectAll($event)"
(deselectAllEvent)="onDeselectAll($event)"
></app-multi-dropdown>
in parent.component.ts I didn't do anything but log:
onItemSelect($event) {
console.log("onItemSelect");
}
onItemDeselect($event) {
console.log("onItemDeselect");
}
onSelectAll($event) {
console.log("onSelectAll");
}
onDeselectAll($event) {
console.log("onDeselectAll");
}
in above code filterPropertiesMap defines settings.
You may see that what I am doing is in child component, I created eventemitters for select, deselect, and in the function I emitt this.selectedItems.
But I don't think this is a good way to implement this, and actually, it doesn't work well.
sometimes, when I select or deselect, it doesn't changed immediately.
So how to implement this? when I select deselect, selectAll, deselectAll. my parent component can react immediately and correctly.
Also the weird thing is: when I load the page, I will have some default values chose, for example 6,7,8,9. Then I select all and it still 6,7,8,9. and then after that I deselect all agin select all, the field will change to all(for example 1,2,3,4,5,6,7,8,9). Does event emitter has delay or will ignore some choices??
Edit:
I tried to extract all the necessary snippets of code to build a project here:
https://drive.google.com/file/d/1BlV2EtdwZhqqpkdiC0_mlaw_r3w6Bder/view?usp=sharing
I hope when I all the event(select, deselect, selectAll, deselectAll) can be emitted and received by parent component
sorry one mistake: the app-multi-dropdown tag in parent component should be app-child
You can use it as part of an Angular reactive form, and just emit every time it value changes.
The HTML of the child component could be something like this:
<form [formGroup]="myForm">
<ng-multiselect-dropdown
name="dropdownItems"
[settings]="dropdownSettings"
[placeholder]="placeholder"
[data]="dropdownItems"
formControlName="dropdownItems">
</ng-multiselect-dropdown>
</form>
And the .ts file:
import { IDropdownSettings } from 'ng-multiselect-dropdown';
import { FormGroup, FormBuilder } from '#angular/forms';
import { Component, OnInit, Input, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.scss']
})
export class ChildComponent implements OnInit {
#Input() placeholder: string;
#Input() defaultValues: any[];
#Input() dropdownItems: any[];
#Input() dropdownSettings: IDropdownSettings;
#Output() onDropdownChange = new EventEmitter();
myForm: FormGroup;
constructor(
private _fb: FormBuilder
) { }
ngOnInit(): void {
this.myForm = this._fb.group({
dropdownItems: ['']
});
// Set default values as real values
this.onDropdownChange.emit(this.defaultValues);
this.myForm.valueChanges.subscribe(val => {
this.onDropdownChange.emit(val.dropdownItems)
})
}
}
EDIT: Based on your last update:
With the code you updated and my peace of code, I build this functional project running on stackblitz: https://stackblitz.com/edit/angular-ivy-g7w3dz
Hope I got the logic properly
you haven't set up your emitter properly. do this:
#Output()selectAllEvent = new EventEmitter<any>();
onSelectAll(event) {
this.selectAllEvent.emit(this.selectedItems);
}
then on the parent:
<app-multi-dropdown
(selectAllEvent)="onSelectAll($event)"
></app-multi-dropdown>

Angular 4 - Share data with directive attribute

I'm trying to make a tooltip directive/component, but everything i tried, i cannot use interpolation in my tooltip to use variables from a repeat.
My home markup looks like this:
<md-card class='col-md-3 image-gallery' *ngFor="let advertiser of AdvertiserService.advertisers;let i = index" [#fadeIn]>
<md-card-content
[tooltip]="template" [advertiser]="advertiser">
//some other markup
</md-card-content>
</md-card>
My tooltip directive looks like this:
import { ComponentFactoryResolver, ComponentRef, Directive, ElementRef,
HostListener, Injector, Output, Input, ReflectiveInjector, Renderer2,
TemplateRef, Type, ViewContainerRef, ViewRef } from '#angular/core';
import { TooltipComponent } from './tooltip.component';
import { AdvertiserClass } from './../advertiser/advertiser-class';
#Directive({
selector: '[tooltip]'
})
export class TooltipDirective {
// We can pass string, template or component
#Input('tooltip') content: string | TemplateRef<any> | Type<any>;
#Input('advertiser') advertiser: AdvertiserClass;
private componentRef: ComponentRef<TooltipComponent>;
constructor(private element: ElementRef,
private renderer: Renderer2,
private injector: Injector,
private resolver: ComponentFactoryResolver,
private vcr: ViewContainerRef) {
}
#HostListener('mouseenter')
mouseenter() {
//console.log(this.advertiser);
if (this.componentRef) return;
const factory =
this.resolver.resolveComponentFactory(TooltipComponent);
const injector = ReflectiveInjector.resolveAndCreate([
{
provide: 'tooltipConfig',
useValue: {
host: this.element.nativeElement
}
}
]);
this.componentRef = this.vcr.createComponent(factory, 0, injector,
this.generateNgContent());
}
generateNgContent() {
if (typeof this.content === 'string') {
const element = this.renderer.createText(this.content);
return [[element]];
}
if (this.content instanceof TemplateRef) {
const viewRef = this.content.createEmbeddedView({});
return [viewRef.rootNodes];
}
// Else it's a component
const factory = this.resolver.resolveComponentFactory(this.content);
const viewRef = factory.create(this.injector);
return [[viewRef.location.nativeElement]];
}
#HostListener('mouseout')
mouseout() {
this.destroy();
}
destroy() {
this.componentRef && this.componentRef.destroy();
this.componentRef = null;
}
ngOnDestroy() {
this.destroy();
}
}
And my tooltip component looks like this:
import { Component, Directive, ElementRef, Inject, OnInit, ViewChild, Input
} from '#angular/core';
import { AdvertiserClass } from './../advertiser/advertiser-class';
#Directive({
selector: '.tooltip-container'
})
export class TooltipContainerDirective {
}
#Component({
template: `
<div class="tooltip-container" [ngStyle]="{top: top}">
{{advertiser | json}}
</div>
`,
styles: [
`
.tooltip-container {
background-color: black;
color: #fff;
display: inline-block;
padding: 0.5em;
position: absolute;
}
`
]
})
export class TooltipComponent implements OnInit {
#Input('advertiser') advertiser: AdvertiserClass;
top: string;
#ViewChild(TooltipContainerDirective, { read: ElementRef }) private
tooltipContainer;
constructor( #Inject('tooltipConfig') private config) {
}
ngOnInit() {
const { top } = this.config.host.getBoundingClientRect();
const { height } =
this.tooltipContainer.nativeElement.getBoundingClientRect();
this.top = `${top - height}px`;
}
}
How could i use the {{advertisers}} interpotalion in the code that would works?
I have tried every variant of this, but i couldnt make pass the repeated data to the tooltip components template.
As is, your tooltip directive knows about the advertiser, but the TooltipComponent, who's data is used to generate the view, does not. What you need to do is pass the advertiser from the directive to the TooltipComponent when the directive creates it. I would probably do that in the 'tooltipconfig' object that you're creating and injecting into the TooltipComponent.
const injector = ReflectiveInjector.resolveAndCreate([
{
provide: 'tooltipConfig',
useValue: {
host: this.element.nativeElement,
advertiser: this.advertiser
}
}
]);
Then in the ToolTipComponent you can pull that value out of the config object in the constructor to make it available to the template
constructor( #Inject('tooltipConfig') private config) {
this.advertiser = config.advertiser;
}
or you could make your config object public in the constructor and bind to {{config.advertiser}} in the template.

Multiple instance of angular 4 directive called from a component mesed up the input values

I have a component in angular 4 that is called three times. In template metadata I have a div with a directive with some bindings like this.
#import {gServ} from '../gServ.service';
#Component: ({
selector: 'sr-comp',
template: `<div gDirective [cOptions]="dataChart">`
})
export class SGComponent implements OnInit {
#Input('report') public report: IReportInstance;
cOptions:any;
constructor(private gServ: gServ) {
}
ngOnInit(){
this.cOptions = {};
this.cOptions = this.gServ.objectMerge(this.gServ.defaultOpt, this.report.opt);
//this.report.opt is binded to a component when is instantiated.
//this.gServ.objectMerge is a function that merge the two objects
}
}
this.cOptions change for every instance of the component, then in the directive I have this:
import { Directive, ElementRef, HostListener, Input, OnInit } from '#angular/core';
#Directive({
selector: '[gDirective]'
})
export class SGDirective implements OnInit {
public _element: any;
#Input() public cOptions: string;
constructor(public element: ElementRef) {
this._element = this.element.nativeElement;
}
ngOnInit() {
console.log(this.cOptions);
}
}
The problem is that console.log(this.cOptions); always print the same object, even when component set cOptions with diferent values in ngOnInit method of the compnent.
Do you have some idea what is wrong?
Your component property binding [cOptions]="dataChart" doesn't look good, reason being your dataChart is not even defined. it should be like [DIRECTIVE_PROPERTY]="COMPONENT_PROPERTY" and your COMPONENT_PROPERTY is not even defined in SGComponent component class.
Your component class should be something like this:
#import {gServ} from '../gServ.service';
#Component: ({
selector: 'sr-comp',
template: `<div gDirective [cOptions]="Options">`
})
export class SGComponent implements OnInit {
#Input('report') public report: IReportInstance;
Options:any;
constructor(private gServ: gServ) {
}
ngOnInit(){
this.Options = {};
this.Options = this.gServ.objectMerge(this.gServ.defaultOpt, this.report.opt);
}
}
#Ashwani points out a valid problem with your code. The way your template is wiring things up, nothing will ever be passed to the SGDirective input.
Another potential problem you could be running into has to do with the gServ code. If gServ is a singleton (which is probably the case) and it is returning the same object to each of the SGComponents, then all the SGDirectives will have the same value. A simple way to test this is to put {{Options | json}} in the SGComponent template.
To create a new instance of the gServ service for each SGComponent you can add a providers array to the #Component metadata. It would look like this:
import {gServ} from '../gServ.service';
#Component({
selector: 'sr-comp',
template: `{{Options | json}}<div gDirective [cOptions]="Options"></div>`
providers: [gServ],
})
export class SGComponent implements OnInit {
#Input('report') public report: IReportInstance;
Options:any;
constructor(private gServ: gServ) {
}
ngOnInit(){
this.Options = this.gServ.objectMerge(this.gServ.defaultOpt, this.report.opt);
}
}
You have probably the same return/value at this.gServ.objectMerge) (you can test it wihtout calling the service, and passing each one one different objet make by you)
#import {gServ} from '../gServ.service';
#Component: ({
selector: 'sr-comp',
template: `<div gDirective [cOptions]="dataChart">`
})
export class SGComponent implements OnInit {
//#Input('report') public report: IReportInstance;
cOptions:any;
constructor(private gServ: gServ) {
}
ngOnInit(){
this.cOptions = {nicolas: 'nicolas1'}; //change this in the next component that use the directive
}
}
If that is the case, your problem is that gServ is provide at the same rootComponent. with angular, service provider at the same rootComponent are singleton.
And use the same type in your directive and your component!!

How can i bind a directive to a function in angular2?

I have created a simple offClick directive in angular2, which works, like so.
import { Directive, Host, Input, Output, EventEmitter, ElementRef } from '#angular/core';
#Directive({
selector: '[offClick]',
host: {
'(document:click)': 'onClick($event)',
}
})
export class OffClickDirective {
#Input('offClick') a;
#Output('off')
offClicked = new EventEmitter();
constructor(
private elementRef: ElementRef) {
}
onClick($event) {
$event.stopPropagation();
if (!this.elementRef.nativeElement.contains($event.target))
this.offClicked.emit({});
}
}
But to add this to a HTML element, i have to do something like this.
<div [offClick] (off)="onOffClick($event)"></div>
Is there anyway that i can change this directive, so i can use it like this on a HTML element
<div (offClick)="onOffClick($event)"></div>
OR
I basically dont want to have to declare a tag for the directive and then another to catch the event....
Thanks in advance
This should do what you want
#Directive({
selector: '[offClick]',
host: {
'(document:click)': 'onClick($event)',
}
})
export class OffClickDirective {
#Output('offClick')
offClicked = new EventEmitter();
constructor(
private elementRef: ElementRef) {
}
onClick($event) {
$event.stopPropagation();
if (!this.elementRef.nativeElement.contains($event.target))
this.offClicked.emit({});
}
}
<div (offClick)="onOffClick($event)"></div>

Angular 2-RC4 Dynamic Content Loading

Currently, I'm trying incorporating Leaflet into Angular 2-RC4. I have faced with the following problem of dynamically loading html into popupContent of leaflet markers.
In the parent class I have the following code:
export class BeaconLayerComponent implements OnInit {
constructor(private resolver: ComponentResolver, private viewRef: ViewContainerRef) {}
createMultipleDynamicInstance(markers) {
markers.foreach((marker) => {
createDynamicInstance(marker);
}
}
createDynamicInstance() {
this.resolver.resolveComponent(BeaconPopupComponent).then((factory:ComponentFactory<BeaconPopupComponent>) => {
let popupComponentRef: ComponentRef<BeaconPopupComponent>;
popupComponentRef = this.viewRef.createComponent(factory);
popupComponentRef.instance.name = beaconJSON.name;
popupComponentRef.instance.afterViewCheckedEventEmitter.subscribe((popupInnerHTML: string) => {
//make use of the innerHTML
// ... <= Create the leaflet marker with innerHTML as popupContent
//After retrieving the HTML, destroy the element
popupComponentRef.destroy();
})
});
Child component:
import {Component, AfterContentInit, EventEmitter, AfterViewInit, AfterViewChecked, ElementRef} from "#angular/core";
#Component({
selector: 'beaconPopup',
template: `
<div>{{name}}</div>
`
})
export class BeaconPopupComponent implements AfterViewChecked {
constructor(private elementRef: ElementRef) {}
name:string;
public afterViewCheckedEventEmitter = new EventEmitter();
ngAfterViewChecked() {
console.log("ngAfterViewChecked()");
this.afterViewCheckedEventEmitter.emit(this.elementRef.nativeElement.innerHTML);
}
}
When I run the html I get these errors:
2016-07-19 00:02:29.375 platform-browser.umd.js:1900 Error: Expression has changed after it was checked. Previous value: '[object Object]'. Current value: 'Happening Beacon'
at ExpressionChangedAfterItHasBeenCheckedException.BaseException [as constructor]
I'm trying to avoid getting the DOM element via JQuery
getDynamicElement(name)
{
str = "<div><div>" + name + "<div></div>"
return $(str).get[0]
}
Is there a better way to do it in Angular 2?
These are two tricks inside your code, 1 dynamical load, 2 how to use jQuery($)
Here is how I create dynamica component from string
compileToComponent(template1: string): Promise<ComponentFactory<any>> {
const metadata = new ComponentMetadata({
template: template1,
directives: ROUTER_DIRECTIVES
});
let decoratedCmp = Component(metadata)(class DynamicComponent { });
return this.resolver.resolveComponent(decoratedCmp);
}
For more details check here
https://github.com/Longfld/DynamicRouter_rc4/blob/master/app/MyRouterLink.ts
or demo here http://plnkr.co/edit/diZgQ8wttafb4xE6ZUFv?p=preview
For rc5 see here :http://plnkr.co/edit/nm5m7N?p=preview

Categories