Are global variables accessible in Angular 2 html template directly? - html

So I put in app.settings like
public static get DateFormat(): string { return 'MM/DD/YYYY';}
and then in one of my html template of a component I want to do something like this.
<input [(ngModel)]="Holiday" [date-format]="AppSettings.DateFormat"/>
In component I have
import { AppSettings } from '../../../app.settings';
Right now it's not accessible like this in html template. Is there any way?

No, the scope for code in the template is the component instance. You need to assign the value to a field in the component, or add a getter or method that forwards to the global variable in order to be able to use it from the template.
import { AppSettings } from '../../../app.settings';
...
export class MyComponent {
get dateFormat() {
return AppSettings.DateFormat;
}
}
then you can use it like
<input [(ngModel)]="Holiday" [date-format]="dateFormat"/>

It seems hacky but you can use pipe. It saves you from repeating injection or variable binding for each component.
#Pipe({
name: 'settings',
})
export class GlobalVariablePipe implements PipeTransform {
transform(value: any): object {
return AppSettings;
}
}
Then, once imported in your module, you can simply use the pipe as follows:
{{(''|settings).DateFormat}}

To the best of my knowledge, no, and that's by design. Templates are hooked up in conjunction with components, that's how they derive their scope and thereby access to bindable data. It's possible it could be hacked around, but even if you figure it out, this is a path you should not follow.
I do this sort of thing with a class:
class AppConfig {}
AppConfig.Globals = {
TEST_VAL: 'hey now here is the value'
};
export { AppConfig }
And then use it like so:
import { Component } from '#angular/core';
import { AppConfig } from '../../app/app.config';
class HomeComponent {
constructor() {
this.test_val = AppConfig.Globals.TEST_VAL;
}
}
HomeComponent.annotations = [
new Component ( {
templateUrl: './views/home/home.component.html'
} )
];
export { HomeComponent }
In the template:
{{test_val}}
Works well for me.

It seems an old topic. However here is my 2 cents. I used a service instead and it's working. I come up with something like this : <input [(ngModel)]="Holiday" [date-format]="appSettingService.DateFormat"/>. The inconvenient of this solution, you have DI the service.
Here is what I did :
appSettingService.ts
import { Injectable } from '#angular/core';
#Injectable({
})
export class AppSettingService{
constructor() { }
public DateFormat(): string { return 'MM/DD/YYYY';}
...
In your component :
...
constructor(public appSettingService : AppSettingService ) { }
...
And finally your in your template:
<input [(ngModel)]="Holiday" [date-format]="appSettingService.DateFormat"/>
I'm looking for a way to use the value without the suffixe like this:
<input [(ngModel)]="Holiday" [date-format]="DateFormat"/>

Step 1. Define global json globals.json
{
"LABEL": "Your Text"
}
Step 2. Define Injectable Globals class globals.ts and add to providers in app.module.ts
import { Injectable } from '#angular/core';
import jsonGlobals from './globals.json';
#Injectable()
export class Globals {
prop: any = jsonGlobals;
}
in app.module.ts
providers: [Globals]
Step 3. Inject in component's constructor
constructor(public globals: Globals) { }
Step 4. Add following compiler properties in tsconfig.json
"resolveJsonModule": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
Step 5. Use in HTML templates
{{globals.prop.LABEL}}

Related

Angular's (9.1.15) MatDialog .afterClosed().subscribe() returning undefined, despite sending data on .close(..) method

I'm working on a project making use of MatDialog on several instances.
In this particular example, I use two distinct modal dialogs in similar manner, the thing is one works properly as it should, and the other does not, returning undefined on the on .afterClosed().subscribe() data.
Code is as follows:
(only relevant lines as the code is quite extensive)
stocks.component.ts
(main stock record management component)
import { Component, OnInit, OnDestroy } from '#angular/core';
import { MatDialog } from '#angular/material/dialog';
import { NewModalComponent } from '../modal/new/new.component';
import { AddModalComponent } from '../modal/add/add.component';
...
export class StocksComponent implements OnInit, OnDestroy {
constructor(
private dialog: MatDialog,
...
) {}
...
click(what) {
switch (what) {
case 'new':
this.dialog.open(NewModalComponent, {
width: '440px',
height: '600px',
autoFocus: false,
}).afterClosed().subscribe(
(data) => {
console.log('modal-new > data :: ',data); // -- returns the passed object
}
);
break;
case 'add':
this.dialog.open(AddModalComponent, {
width: '95%',
height: '85%',
autoFocus: false,
data: {
...
},
}).afterClosed().subscribe(
(data) => {
console.log('modal-add > data :: ',data); // -- returns undefined
}
);
break;
}
}
}
new.component.ts
(simple modal to select the type of new stock)
import { Component, OnInit, OnDestroy } from '#angular/core';
import { MatDialogRef } from '#angular/material/dialog';
...
export class NewModalComponent implements OnInit, OnDestroy {
constructor(
private dialogRef: MatDialogRef<NewModalComponent>,
...
) {}
...
click(what, data) {
switch (what) {
case 'newtype':
this.dialogRef.close(data); // -- 'data' contains an object
break;
}
}
}
add.component.ts
(modal to add the products to the new stock)
import { Component, OnInit, OnDestroy, Inject } from '#angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '#angular/material/dialog';
...
export class AddModalComponent implements OnInit, OnDestroy {
constructor(
#Inject(MAT_DIALOG_DATA) public data,
private dialogRef: MatDialogRef<AddModalComponent>,
) {}
...
click(what) {
switch (what) {
case 'close':
this.dialogRef.close(this.products); // -- 'this.products' contains an object
break;
}
}
}
As you can see, both modals are summoned the same way and both make use of the same methods to subscribe to the data after closed, yet one successfully returns the data while the other does not. Am I missing something obvious..? ..or not so obvious?
Any help is appreciated, as I am in a rather tight schedule and stuck on this issue!!
Thank you all in advance.
After a bit of debuging, and practically line-per-line testing, I discovered the source of the issue.
In my add.component.ts I have multiple child components, one of them being list.component.ts to display a complete list of the added products, as they are searched and selected (within another separate child component).
The problem resided in that list child component containing the following code:
ngOnDestroy() {
this.subscriptions.forEach((element) => { element.unsubscribe(); });
this.dialog.closeAll();
}
I found out that the this.dialogRef.close(this.products); line of the add component was indeed working properly, but upon receiving the 'close' command to itself, I suppose it triggers the close of its child components and when closing the list that this.dialog.closeAll(); method got automatically triggered and would override / overlap the original .close(..), thus resulting in the undefined.
This is probably an easy one for many of you, but I have little more than a year of experience in angular and so, small details like these can sometimes escape my graps.. at least for a while.
Anyway, I'm hoping this might help someone, either new or experienced, as all my searches regarding this issue only let to questions completely unrelated.
Best regards.

DomSanitizer doesn't parse text to HTML

I am working on an Angular project and I am taking data from a database where it has data with HTML tags. To put it in the HTML I can do that through this code:
[innerHTML]="element.texto"
What Im looking for is to use it on the ts to put the text on a pdf. Im using DomSanitizer but the output is wrong and does not transform it on the HTML. This is the exaple code im using:
import { DomSanitizer } from "#angular/platform-browser";
///
constructor(
private sanitizer:DomSanitizer
) { }
dataTransformer() {
let textoHTML = "<p>Hello <b>World</b></p>"
alert(this.sanitizer.bypassSecurityTrustHtml(textoHTML)) //It shows: SafeValue must use [property]=binding: <p>Hello <b>World</b></p> (see http://g.co/ng/security#xss)
}
In the other hand, I have used the following code for parseing the code but with a wrong solution.
this.sanitizer.bypassSecurityTrustHtml(textoHTML)['changingThisBreaksApplicationSecurity'] //It shows: <p>Hello <b>World</b></p>
What im looking for is: Hello World
Thanks!
you can use pipe
[innerHTML]="element.texto | safeHtml "
import { Pipe, PipeTransform } from '#angular/core';
import { DomSanitizer, SafeHtml } from '#angular/platform-browser';
#Pipe({
name: 'safeHtml',
pure: false
})
export class SafeHtmlPipe implements PipeTransform {
constructor(protected sanitizer: DomSanitizer) {
}
transform(value: any): SafeHtml {
return this.sanitizer.bypassSecurityTrustHtml(value);
}
}
#erfan-farhadi has given a reusable code.
Use the following as quick fix
Change your ts file to something like this
import { DomSanitizer } from "#angular/platform-browser";
sanitizedValue: undefined; // member var to hold sanitized HTML
constructor(
private sanitizer:DomSanitizer
) { }
dataTransformer() {
let textoHTML = "<p>Hello <b>World</b></p>"
this.sanitizedValue = this.sanitizer.bypassSecurityTrustHtml(textoHTML));
}
/// ...
In html template use like following
[innerHTML]="sanitizedValue"

JSON service returning undefined property with Angular 7

This should be the simplest thing. I have a component that calls a service that imports a local JSON directly (see Import JSON directly with Angular 7)
It reads the JSON contents fine, but the pages property is undefined. I think I set everything up based on the StackBlitz in that link, there doesn't seem to be much to it. There isn't any HTML yet, this is all just via the console. It's in app.component.html.
Reading local json files json.service.ts:14
[{…}]0: {name: "Home"}length: 1__proto__: Array(0) json.service.ts:15
undefined home.component.ts:31
json.service.ts:
import { Injectable } from '#angular/core';
import SampleJson from '../assets/SampleJson.json';
export interface JsonInterface {
name: any;
}
#Injectable()
export class JsonService {
ngOnInit() {
console.log('Reading local json files');
console.log(SampleJson);
}
}
home.component.ts:
import { JsonService, JsonInterface } from '../json.service';
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
constructor(service: JsonService) {
service.ngOnInit();
};
#Input() pages: JsonInterface;
ngOnInit() {
console.log(this.pages);
}
}
Sample.json
{ "name":"Home" }
If I understand your log correctly, it works as expected:
constructor(service: JsonService) {
service.ngOnInit();
};
You request the service and you get an instance. Then you call ngOnInit:
ngOnInit() {
console.log('Reading local json files');
console.log(SampleJson);
}
Now it logs the "reading…" and the content of your json file.
ngOnInit() {
console.log(this.pages);
}
Then you log this.pages which is empty. You never filled it. You never did anything with your service or the data loaded in your service.
I think what you want is something like this
export class JsonService {
getPages() { return SampleJson; }
}
and in your component:
constructor(private service: JsonService) {}
ngOnInit() {
this.pages = this.service.getPages();
console.log(this.pages);
}
The sample code is not tested but I think you've got the point.
The problem is with pages. You have inly declared 'pages' as 'JsonInterface' which is only the type of 'pages' but never initialized with any value so it is undefined.. you need to write a function in Service as the above answer by #Christoph .
I hope you understand why this error occured and If you are not inputting a value to 'pages' from html you don't need to declare it as '#Input'.

Angular 5 DomSanitizer with Hyperlinks

I am using a WYSIWYG editor (CKEditor) and trying to render the content with Angular 5.
What I am trying to figure out is the proper way to use DomSanitizer in Angular 5. The problem I am facing is that Hyperlinks are not working (are not "clickable") in the resulting sanitized HTML.
I am using the following Typescript code to return a safeHtml content:
public getSafeContent(): SafeHtml {
return this.sanitizer.bypassSecurityTrustHtml(this.page.content);
}
and using it in my template this way:
<div [innerHTML]="getSafeContent()"></div>
This will render the HTML with all inline styles intact, but hyperlinks won't work.
I tried doing this instead:
public getSafeContent(): SafeHtml {
return this.sanitizer.sanitize(SecurityContext.HTML, this.page.content);
}
Which results in that Hyperlinks actually works, but inlines styles are not.
Is there a way to get both styles and hyperlinks to work with sanitized content?
Update
This is what the page looks like in Chrome dev tools:
<div _ngcontent-c22="" class="row">
<div _ngcontent-c22="" class="col-lg-12">
<div _ngcontent-c22="">
<p>google</p>
</div>
</div>
</div>
and the google link is not clickable.
bypassSecurityTrustHtml allows <script> tags in the content. For URLs you need bypassSecurityTrustUrl. See here: https://angular.io/api/platform-browser/DomSanitizer#bypassSecurityTrustUrl.
I've never tried stacking the bypassXXX methods, so I don't know if you can do something like this bypassSecurityTrustUrl(bypassSecurityTrustHtml(myContent)) but I would guess probably not since each method takes a string but returns an object (of type SafeHtml/SafeUrl), so it can't be used as the input to the stacked function call which expects a string.
So, you may need to parse the contents, pass each URL into the bypassSecurityTrustUrl and then combine everything back together again.
Update
I just looked at the sanitize method. I haven't tried this, but something like this might work:
this.sanitizer.sanitize(SecurityContext.HTML, this.sanitizer.bypassSecurityTrustUrl(myContent));
since sanitize can take a SafeValue as an input. The inner bypassSecurityTrustUrl sanitizes the URLs and returns a SafeUrl, which is unwrapped by the outer sanitize and used as input to make it HTML safe. Like I said, I haven't tried it, but it looks good in theory...
in .ts pipe for 'URL' sanitizer
import { Component, Pipe, PipeTransform } from '#angular/core';
import { DomSanitizer } from '#angular/platform-browser';
#Pipe({ name: 'sanitizeUrl' })
export class SafeUrlPipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) {}
transform(url) {
return this.sanitizer.bypassSecurityTrustResourceUrl(url);
}
}
in .html
<div [innerHTML]="Content | sanitizeUrl| sanitizeHtml">
</div>
pipe for 'HTML' sanitizer
import { Component, Pipe, PipeTransform } from '#angular/core';
import { DomSanitizer } from '#angular/platform-browser';
#Pipe({
name: 'sanitizeHtml'
})
export class SafeHtmlPipe implements PipeTransform {
constructor(private sanitized: DomSanitizer) {}
transform(value) {
return this.sanitized.bypassSecurityTrustHtml(value);
}
}
Please consider the above solution. this will apply both pipes without disturbing any style and link click event at same time
I used DOMPurify library to sanitize the DOM. DOMPurify is a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG.
I created angular PureTextPipe pipe to sanitize the raw content
import { Pipe, PipeTransform } from '#angular/core';
import DOMPurify from 'dompurify';
#Pipe({
name: 'pureText',
pure: true
})
export class PureTextPipe implements PipeTransform {
transform(str: any): any {
let res = str;
if (str === null || str === undefined || str === '' || str === 0) {
res = '--';
}
return DOMPurify.sanitize(res);
}
}
Now to use this pipe you just need to add it to HTML like:
<div [innerHTML]="yourRawData | pureText"></div>
Documentation of DOMPurify at
https://www.npmjs.com/package/dompurify
import DOMPurify from 'dompurify';
var clean = DOMPurify.sanitize(dirty);
I have found a working solution to this. With this both the URL and the style would work. It is again with the use of "bypassSecurityTrustHtml". It's surprising how using this in html or TS does not make the link work, but using this in a seperate file as a pipe makes this work like a charm.
Here is what I did
Create a Custom Pipe
.safe.dom.pipe
import { Component, Pipe, PipeTransform } from '#angular/core';
import { DomSanitizer, SafeHtml, SafeResourceUrl, SafeScript, SafeStyle, SafeUrl } from '#angular/platform-browser';
#Pipe({
name: 'safeDom'
})
export class SafeDomPipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) {}
public transform(value: any, type: string): SafeHtml | SafeStyle | SafeScript | SafeUrl | SafeResourceUrl {
switch (type) {
case 'html': return this.sanitizer.bypassSecurityTrustHtml(value);
case 'style': return this.sanitizer.bypassSecurityTrustStyle(value);
case 'script': return this.sanitizer.bypassSecurityTrustScript(value);
case 'url': return this.sanitizer.bypassSecurityTrustUrl(value);
case 'resourceUrl': return this.sanitizer.bypassSecurityTrustResourceUrl(value);
default: throw new Error(`Invalid safe type specified: ${type}`);
}
}
}`
In Case you have a common module in your project that is included where you require to implement it, you can declare and export this package from there. Else just declare this pipe in the module you want to implement this.
import { SafeDomPipe } from './pipes/safe.dom.pipe';
#NgModule({
imports: [
.....
],
exports: [
....
SafeDomPipe
....
],
declarations: [
....
SafeDomPipe
....
])
Now in Html you can directly use this pipe.
<div [innerHtml]="data | safeDom: 'html' "> </div>
This would allow both link and style.
If you're data content is consisting of HTML with Hyperlinks. But we want to sanitize with hyperlink working. This worked for me:
HTML
<div [innerHTML]="getSafeContent()"></div>
TS:
public getSafeContent(): SafeHtml {
return this.sanitizer.sanitize(SecurityContext.HTML, this.page.content);
}
This works for me:
Component:
content = '<b>Hello World</b><p style=\'font-size:14pt\'>
<a href=\'http://www.google.com\'>Go to Google</a></p>Test123';
public getSafeContent(): SafeHtml {
return this.sanitizer.bypassSecurityTrustHtml(this.content);
}
HTML:
<div [innerHTML]="getSafeContent()"></div>
Link works and inline styles are intact

Insert component into html dynamically

I'm trying to insert a angular-material component inside a piece of html dynamically. The way i think, i won't be able to use ViewContainerRef.
Here's how it needs to work:
I'll retrieve a string from the database (it can be any material component, such as inputs, buttons, etc... Something like this:
let stringFromDB = "<md-spinner></md-spinner>"
I would need to pass this string to my html's div (which is my "container"). So i tryied:
#Component({
selector: 'report',
template: `<div [innerHtml]="stringFromDB"></div>`
})
export class ReportComponent{
stringFromDB : string = null;
constructor(){
this.stringFromDB =this.someService.getTemplateStringFromDatabase();
}
}
I can pass a simple <p></p>.
But not a component like md-spinner. Any thoughts on how to accomplish this?
In angular 4 you can use ngComponentOutlet.
Template:
<ng-container *ngComponentOutlet="dynamicComponent; ngModuleFactory: dynamicModule;"></ng-container>
Build dynamic module and component with your template string:
import { Compiler } from '#angular/core';
build() {
this.dynamicComponent = this.createDynamicComponent(this.stringFromDB);
this.dynamicModule = this._compiler.compileModuleSync(this.createDynamicModule(this.dynamicComponent));
}
createDynamicModule(componentType: any) {
#NgModule({
imports: [ ],
declarations: [
componentType
],
entryComponents: [componentType]
})
class RuntimeModule { }
return RuntimeModule;
}
createDynamicComponent(template: string) {
#Component({
selector: 'dynamic-component',
template: template ? template : '<div></div>'
})
class DynamicComponent {
constructor() {
}
}
return DynamicComponent;
}