I am pretty new to Angular 2 and I am trying to display bunch of album covers. The path to images are returned by the server as a part of an Album object. I ran into the usual sanitizing issue.
I read about the solution here: Angular 2, 2.0.0-rc.2, Cannot apply inline css3 transform with style directive
here: Angular2 - WARNING: sanitizing unsafe style value url(SafeValue must use [property]=binding:
and here: https://angular.io/docs/ts/latest/guide/security.html#!#bypass-security-apis
However since the overall project structure is not mentioned in either of these cases, I am having a little bit of trouble figuring out where (which file?) and how I should be adding the bypass security rule. I tried every possibility I could think of but all of them throws an error. Logically, it should be added to the component generating the template, however it might be that it doesn't have direct access to the Album object.
The code base was formed by following the tutorial at www.angular.io and making the necessary changes.
Project Structure:
project
| index.html
| style.css
|--app
| main.ts
| app.component.ts
| app.component.spec.ts
| app.module.ts
| app.routing.module.ts
| albums.component.ts
| album-dashboard.component.ts
| album.service.ts
| album.ts
| album-detail.component.ts
| dashboard.component.html
| album.component.html
| album.detail.component.html
Relevant HTML and TS files are below. If any other file from above is relevant to the question I would be happy to update
dashboard.component.html: The html where the albums are being displayed. Please don't suggest using some information is gonna sit on the top and usually that is harder to deal with actual imgs. I am aware style = "..." is not the correct syntax, changing it to what is shown in the threads create a lot of errors I wasn't able to trace back
<div class="grid grid-pad">
<a *ngFor="let album of albums" [routerLink]="['/detail', album.id]" class="col-1-4">
<div class="module album" style="background-image: url('{{album.path}}');">
<!-- <h4>{{album.name}}</h4> -->
</div>
</a>
</div>
album.ts
export class Album {
id: number;
name: string;
artist: string;
path: string;
}
album-dashboard.component.ts: Please notice that this is a component for a collection of album. As a side question the Threads I have linked to above either add a static rule or dynamically add a rule for the current instance, does this imply I have to iterate through each instance in the collection and add them one by one ?
import { Component, OnInit } from '#angular/core';
import { Album } from './album';
import { AlbumService } from './album.service';
#Component({
moduleId: module.id,
selector: 'album-dashboard',
templateUrl: 'dashboard.component.html',
styleUrls: [ 'dashboard.component.css' ]
})
export class DashboardComponent implements OnInit {
albums: Album[] = [];
constructor(private albumService: AlbumService) { };
ngOnInit(): void {
this.albumService.getAlbums()
.then(albums => this.albums = albums);//.slice(1, 4));
}
}
app.component.ts
import { Component } from '#angular/core';
#Component({
moduleId: module.id,
selector: 'my-app',
template: `
<nav>
<a routerLink="/dashboard" routerLinkActive="active">Dashboard</a>
<a routerLink="/albums" routerLinkActive="active">Albums</a>
</nav>
<router-outlet></router-outlet>
`,
styleUrls: ['app.component.css'],
})
export class AppComponent {
title = 'Production Crew';
}
Any help is appreciated!
Fixed. For anyone having similar issues,
please see: Angular2 dynamic background images
and: In RC.1 some styles can't be added using binding syntax
Turns out style.background-image has been 'black-listed' against xss attacks however style.background and ngStyle properties will work.
I don't have an explanation on why these two provide the same result but are not considered not secure yet. Will update if I can get an explanation
Related
I have Font Awesome in my Angular 14 app implemented as described here: https://www.npmjs.com/package/#fortawesome/angular-fontawesome . I use <fa-icon [icon]="faIconName"> semantics. I also use few languages on my website and I load translations through json files. In templates I use semantics like <span [innerHTML]="here_i_have_key_to_translate | translate | safeHtml" where I use translate service and pipe which let me show in template forbidden code like fa-icon (default this is earased from json html, just like 'style' attribute). How can I use Font Awesome icons in json? Below there is example of json, code.
language json from assets\i18n:
"key_1":"<p><b>example text</b><br /><fa-icon [icon]=\"faPhoneSquare\"></fa-icon> +43 123 123 923</p>",
"key_2":"<p><fa-icon [icon]=\"faPhoneSquare\"></fa-icon> +44 123 123 123</p>",
"key_3":"<p><fa-icon [icon]=\"faPhoneSquare\"></fa-icon> +43 123 123 123</p>",
template:
<section>
<h4 [translate]="'header_1'"></h4>
<p>
<fa-icon [icon]="faEnvelope" class="me-2"></fa-icon> Some static text with icon on left which displays fine
</p>
</section>
<section [innerHTML]="'key_1' | translate | safeHtml"></section>
<section [innerHTML]="'key_2' | translate | safeHtml"></section>
<section [innerHTML]="'key_3' | translate | safeHtml"></section>
typescript:
import { Component } from '#angular/core';
import { TranslationAppService } from '../../../common/services';
import { faPhoneSquare, faEnvelope } from '#fortawesome/free-solid-svg-icons';
#Component({
selector: 'app-contact',
templateUrl: './contact.component.html',
styleUrls: ['./contact.component.scss']
})
export class ContactComponent {
faPhoneSquare = faPhoneSquare;
faEnvelope = faEnvelope;
}
This isn't how Angular is supposed to work.
Angular is in charge of handling your HTML. You are not supposed to write HTML templates that you send to Angular. It simply does not work because it's not supposed to work like that.
Just write your HTML in your HTML files and only provide the data you need from your API (in this case the phone number it seems).
I have created proper Ingredient object in Typesript, also proper "model" for the object. Though ngfor directive is not working. I am getting this error "NG0303: Can't bind to 'ngforOf' since it isn't a known property of 'a'" on inspecting in browser.
My model code
export class Ingredient {
constructor(public name: string, public amount: number){}
}
My TypeScript code
import { Component, OnInit } from '#angular/core';
import { Ingredient } from '../shared/ingredient.model';
#Component({
selector: 'app-shopping-list',
templateUrl: './shopping-list.component.html',
styleUrls: ['./shopping-list.component.css']
})
export class ShoppingListComponent implements OnInit {
ingredients: Ingredient[] = [
new Ingredient ("Apple", 5),
new Ingredient ("Tomato", 5)
];
constructor() { }
ngOnInit(): void {
}
}
My HTML code
<div class="row">
<div class="col-xs-10">
<app-shopping-edit></app-shopping-edit>
<hr>
<ul class = "list-group">
<a class = "list-group-item"
style = "cursor : pointer"
*ngfor = "let ingredient of ingredients">
{{ ingredient.name }}({{ ingredient.amount}})
</a>
</ul>
</div>
</div>
Page is loading properly, but ingredients are not loading. On inspecting in browser this error "NG0303: Can't bind to 'ngforOf' since it isn't a known property of 'a'" is coming.
Can someone please help.
Try moving the *ngFor inside the ul tag.
Edit: You have a typo.. It's *ngFor="", not *ngfor="".
If you are inside AppModule check if you have BroswerModule in the imports array there.
If you are in some different module then check if CommonModule is a part of that module's array. CommonModule provides us with core Angular features like *ngIf or *ngFor in your specific case.
Also watch out, you typed *ngfor instead *ngFor.
I want to switch between two classes (light and dark) at TAG Body.
What I did? I created a service:
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class ThemeService {
body = document.body;
constructor() { }
changeLight() {
this.body.classList.replace('light', 'dark');
}
changeDark() {
this.body.classList.replace('dark', 'light');
}
}
It is working as expected but I know that this code does not use best practices.
What is the correct way to change between these two classes?
Edit: Added a service to the stackblitz, but again, there are many ways to do this. This is just a starting point.
While the "right way" is subjective, you have some options to make it "Angular-y"
Component:
import { Component, Inject } from '#angular/core';
import { DOCUMENT } from '#angular/common';
// Create a type that accepts either the string 'light' or 'dark' only
type Theme = 'light' | 'dark';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
// Default to 'light' theme
currentTheme: Theme = 'light';
// Inject document which is safe when used with server-side rendering
constructor(#Inject(DOCUMENT) private document: Document) {
// Add the current (light) theme as a default
this.document.body.classList.add(this.currentTheme);
}
// Swap them out, and keep track of the new theme
switchTheme(newTheme: Theme): void {
this.document.body.classList.replace(this.currentTheme, newTheme)
this.currentTheme = newTheme;
}
}
HTML:
<p>
Current theme: {{ currentTheme }}
<button (click)="switchTheme('light')">Light mode</button>
<button (click)="switchTheme('dark')">Dark mode</button>
</p>
Many ways to do this, but one benefit of defining the types is if you provide a bad value, such as:
<p>
Current theme: {{ currentTheme }}
<button (click)="switchTheme('light')">Light mode</button>
<button (click)="switchTheme('dark')">Dark mode</button>
<button (click)="switchTheme('noop')">Invalid</button>
</p>
You'll get an error:
Argument of type '"noop"' is not assignable to parameter of type 'Theme'.
StackBlitz
I am using #HostBinding('class') to inject classes into the host element. The classes to be injected are generated based on user-supplied parameters. The problem I ran into and I could not find anyone else experiencing is that the classes are applied in an order different from the way I expected them.
For example, having a component defined below:
import {Component, HostBinding, Input} from '#angular/core';
#Component({
selector: '[icon]',
template: `
<ng-content></ng-content>
`
})
export class SuiIconComponent {
#Input() iconType = '';
#HostBinding('class')
get classes(): string {
return [this.iconType, 'icon'].join((' '));
}
}
When I apply the component like shown below:
<div icon iconType="car"></div>
And inspect, I see <div class="icon car"></div> instead of the appropriately formatted <div class="car icon"></div>.
I have tried reversing the array before joining but that did not help either.
Is there any way I get the classes to get rendered in the proper order?
Edit: I realized the classes are being rearranged in alphabetic order.
I'm not sure why angular changes the order, but you can solve your problem with little bit of change in your template.
#Component({
selector: 'icon',
template: `
<div [ngClass]="iconType + ' icon'">
<ng-content></ng-content>
</div>
`
})
export class SuiIconComponent {
#Input() iconType = '';
}
and use it as follows
<icon iconType="car">
Some content here
</icon>
I am attempting to create a reusable angular2 component that accepts an array of URLs to html files on my server and creates a content window with tabs to switch between "chapters", effectively swapping out the html and css inside the content window. I have tried all sorts of things including iframes but those don't work, the angular 1 ng-include work-arounds that I can find on StackOverflow but they have all since been deprecated, and the closest I've got is building a component that you can #Input html and it interpolates the content but style won't apply and angular strips out any style or script tags. Here is what I have tried.
In my parent component class:
htmlInput: string = "<h1>Why Does Angular make this so hard?</h1>";
cssInput: string = "h1 { color:red; }"
Parent Component HTML:
<app-html [html]='htmlInput' [css]='cssInput'></app-html>
My HTML Component:
import { Component, Input, OnInit } from '#angular/core';
#Component({
selector: 'app-html',
template: '<div [innerHtml]=html></div>', //This works but no style
//template: '{{html}}', //This displays the actual markup on page
styles: ['{{css}}'] //This does nothing
//styles: ['h1 { color: red; }']//Also nothing
})
export class HtmlComponent implements OnInit {
#Input() html: string = "";
#Input() css: string = "";
ngOnInit() {
}
}
The result of this code is
Why Does Angular make this so hard?
But no red color. Maybe style is applied before the innerHtml is added to DOM? I don't know but just putting {{html}} results in displaying the actual markup with the h1 tags visible.
The reason I want to do it this way is that I have a bunch of HTML pages already created sitting in a folder on my server from before I angularized my site that all share a single style sheet. I'd like to just be able to flip through them like pages in a book without reloading the page and since there are so many and I'm likely to add more all the time, I'd really rather not create routing for every single one. (I already have routing for basic site navigation.)
Does anybody have a better suggestion for how to embed styled HTML into a page dynamically in the most recent version of Angular 2? At the time of this post we are in 2.0.0-beta.17.
OR... I already figured I may be approaching this issue from the entirely wrong angle. There must be a reason Angular is making this so difficult and deprecating all the solutions people have come up with so If anyone has a suggestion about how I could achieve the same results in a more angular friendly way I'd love to hear that too.
Thank you.
Edit:
I was able to fix my issue by creating a pipe which sanatizes the html before adding it to an iframe.
import { Pipe, PipeTransform } from '#angular/core';
import { DomSanitizer } from '#angular/platform-browser';
#Pipe({ name: 'safe' })
export class SafePipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) {}
transform(url: string) {
return this.sanitizer.bypassSecurityTrustResourceUrl(url);
}
}
And then you can just pass your html into the iframe.
<iframe width="100%" height="1000" frameBorder="0" [src]="url | safe"></iframe>
This is useful to me since I have some old pages that use all sorts of jquery and style etc. This works as a quick fix to have them show up.
Angular2 rewrites the styles added to a component by including the dynamically added attributes like _ngcontent-yle-18 into the CSS selectors.
Angular2 uses this to emulate shadow DOM style encapsulation. These attributes are not added to dynamically added HTML (for example with innerHTML).
Workarounds
add styles to index.html because these styles are not rewritten by Angular2
set ViewEncapsulation.None because then Angular doesn't add the encapsulation emulation attributes
use /deep/ to make Angular2 ignore the encapsulation emulation attributes
See also Angular 2 - innerHTML styling
You should wrap your css into an object and use ngStyle to bind it to your component rather than the styles attribute, because styles does not support data binding.
Example:
htmlInput: string = "<h1>Why Does Angular make this so hard?</h1>";
cssInput: string = "{h1 { color:red; }}"
Parent Component HTML:
<app-html [html]='htmlInput' [css]='cssInput'></app-html>
Your HTML Component:
import { Component, Input, OnInit } from '#angular/core';
#Component({
selector: 'app-html',
template: '<div [innerHtml]="html" [ngStyle]="css"></div>',
styles: []
})
export class HtmlComponent implements OnInit {
#Input() html: string = "";
#Input() css: string = "";
ngOnInit() {
}
}