I am trying to display some information which is fetched from an external API on a web page by using Angular's string interpolation.
When no information is fetched or hasn't "arrived" yet, I want to display 'N/A'.
I have tried the following approach, however I get an error saying:
'Can't read property 'name' of undefined' on line 2 of the following html code.
How can I show N/A while waiting for the response to be defined?
app.component.html:
<div id="api-info">
{{ !!this.apiInfo.name ? this.apiInfo.name : 'N/A' }}
</div>
app.component.ts:
import { ApiInfo } from 'src/app/apiInfo.model'
import { ApiService } from 'src/app/api.service'
(...)
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, OnDestroy {
apiInfo: ApiInfo;
apiInfoSub: Subscription;
constructor(apiService: ApiService) {}
ngOnInit() {
// apiService.apiInfo is a Subject<ApiInfo> where the API response is stored
this.apiInfoSub = this.apiService.apiInfo.subscribe(
(response) => {
this.apiInfo = response;
}
);
}
ngOnDestroy() {
this.apiInfoSub.unsubscribe();
}
}
apiInfo.model.ts:
export class ApiInfo {
public name: string,
public id: number,
constructor(name: string, id: number) {
this.name = name;
this.id = id;
}
}
I suggest to not subscribe within the component. Better use the async pipe instead and check as follows...
<div id="api-info" *ngIf="(apiInfo$ | async as apiInfo); else pending">
{{ apiInfo.name }}
</div>
<ng-template #pending>
n/a
</ng-template>
Which also allows you to style the n/a differently quite easy
It is better to use async pipe, instead of subscribing and unsubscribing to stream manually. That makes the code cleaner and in the html use expression:
{{(stream$|async)?.name || 'N/A'}}
Here is code sample: https://stackblitz.com/edit/angular-nknwuq
please change your app.component.html to below:
<div id="api-info">
{{ apiInfo?.name ? apiInfo.name : 'N/A' }}
</div>
this should resolve the issue.
apiInfo is undefined at start. The subscribe does not resolve immediately, so the apiInfo = response is set after some time. Maybe use <div id="api-info" *ngIf="apiInfo">
Or initialize on declaration: apiInfo: ApiInfo = <ApiInfo>{};
Related
This is my component's typescript code:
import { Component, Input, OnInit } from '#angular/core';
import { Store } from '#ngrx/store';
import { Observable } from 'rxjs';
#Component({
selector: 'app-counter-output',
templateUrl: './counter-output.component.html',
styleUrls: ['./counter-output.component.css']
})
export class CounterOutputComponent implements OnInit {
counter!: number;
counter$!: any;
constructor(private store: Store<{ counter: { counter: number } }>) {
}
ngOnInit(): void {
this.store.select('counter').subscribe((data) => {
this.counter = data.counter;
console.log(data);
});
this.counter$ = this.store.select('counter');
console.log(this.counter$);
}
}
This is its HTML template:
<div>This is the counter:{{(counter$|async).counter}}</div>
<div>This is the counter:{{counter}}</div>
There is an error in line no 1 in the HTML file.
When I am subscribing in the typescript file I am able to get the value, but when I am using it as an observable it is showing an error.
Set a type to counter$.
export interface Counter {
counter: number;
}
....
counter$: Observable<Counter>;
Of course, you have to be sure that the selector returns Counter as data.
EDIT: If you insist to use any (which beats the purpose of using Typescript), you can use the $any() type cast function to suppress this error.
<div>This is the counter:{{ ($any(counter$|async)).counter }}</div>
Have you tried to actually define your counter$ property as an Observable?
Why declare it as any if you already know what type of data is?
export interface myInterfaceData {
counter: number;
}
counter$!: Observable<myInterfaceData>;
<ng-container *ngIf="{ myCounter: counter$ | async } as observable">
<div>
This is the counter: {{ observable.myCounter?.counter }}
</div>
</ng-container>
I have been working on React for a year. Now, I am writing angular. How can I create a piece of html code in ts.file?
In react, I do it that way:
const example = (item: string): React.ReactNode => {
return <p> something.... {item} </p>
}
I want to do same thing in Angular8+
I know some way to do it. For example:
const example2= (name: string): string => {
return `
<div>
<p>heyyy ${name}</p>
</div>
`;
};
Are there any other ways to do it?
In Angular, there are a couple of ways to do this. If you need to generate HTML in the typescript and then interpolate it into the template, you can use a combination of the DomSanitizer and the innerHTML attribute into other elements (for example a span).
Below would be an example of what I suggested above:
hello-world.component.ts:
#Component({
selector: "hello-world",
templateUrl: "./hello-world.component.html",
styleUrls: ["./hello-world.component.scss"]
})
export class HelloWorld {
innerHTML: string = `<p>Hello, world!</p>`;
}
sanitze.pipe.ts:
#Pipe({
name='sanitize'
})
export class SanitizePipe {
constructor(private sanitizer: DomSanitizer) { }
transform(value: string): SafeHtml {
return this.sanitizer.bypassSecurityTrustHtml(value);
}
}
hello-world.component.html:
<div [innerHTML]="innerHTML | sanitize"</div>
I have a string returned from an API endpoint as below:
If you died tomorrow, your wife <span class='person' data-relationship='Relationship.Wife' data-avatarid='1212'>Alice</span> will lose everything.
So to display this as HTML I'm using the innterHTML property:
<p *ngFor="let con1Died of consequences?.client1Died" [innerHTML]="con1Died"></p>
But this is outputted to the browser with the data attributes stripped out as:
<p _ngcontent-smy-c63="">If you died tomorrow, your wife <span class="person">Alice</span> will lose everything.</p>
How can I output this WITH the data attributes? Is there a way to this?
EDIT: So I tried the HTML sanitation technique from below but the CSS still isn't applied:
this.reportsService.getConsequences().subscribe(res => {
// Load the consequences to angular
this.consequences = res;
this.client1Died = new Array();
this.consequences.client1Died.forEach(element => {
const safehtml = this.sanitized.bypassSecurityTrustHtml(element);
this.client1Died.push(safehtml);
});
console.log(this.client1Died);
});
Create a pipe to sanitize the Html:
#Pipe({ name: 'safeHtml'})
export class SafeHtmlPipe implements PipeTransform {
constructor(private sanitized: DomSanitizer) {}
transform(value) {
return this.sanitized.bypassSecurityTrustHtml(value);
}
}
#Component({
selector: 'my-app',
template: `<div [innerHTML]="content | safeHtml"></div>`,
})
I have asked this question before, but I think I might have narrowed down the possibilities of errors, so I am asking again while being more specific.
I am trying to implement this post request to a local API in an angular app, but it only returns null. The API is working fin on its own, most of the code seems to be working in my app, but there is obviously something missing which makes it not work.
It seems that my code is calling the api successfully because if I test it using a string instead of an object (which the API is expecting), I get an error message in the console where I launched the API in parallel with npm start
So here is my code where I prompt the user to enter a string, and the code where I convert the string into an object and try to pass it into my post request.
1/app.component.html
import { Component } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { HttpHeaders } from '#angular/common/http';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'Enter your String bellow:';
result;
constructor(private http: HttpClient) {}
onSubmit(textEnter: string) {
let json = {
"string": textEnter
};
console.log(json);
return this.http.post('http://localhost:3000/parentheses/', json).toPromise().then((data:any) => {
console.log("testing");
this.result = data.json;
});
}
}
2/ app.components.ts
<div>
<form>
<input #textEnter placeholder="text">
<button type="submit" (click)="onSubmit(textEnter.value)">click here</button>
</form>
<pre>
{{ (result | async) | json }}
</pre>
</div>
I also created a proxy.config.json file:
{
"/parentheses":{
"target": "http://localhost:3000",
"secure": false,
"logLevel1": "debug"
}
}
Thank you for your help!
Change your code as follow. In your code you are using async pipe in the html code. But the 'result' variable is not an observable typed one.
export class AppComponent {
title = 'Enter your String bellow:';
result: Observable<any>;
constructor(private http: HttpClient) {}
onSubmit(textEnter: string) {
let json = { string: textEnter};
this.result = this.http.post('http://localhost:3000/parentheses/', json);
}
}
OR
Change your HTML code like this. But as your code its not required returning the the http request.
<pre>
{{ result | json }}
</pre>
I have a simple table with angular and typescript. I am sending table data from parent class to child class(which includes the table) and in this example data name is _domainData. It is taking the data correctly but I want to show it on table and I do not know how to assign it to my main table data variable domain_Data.
As in the example: if i say this.domain_Data = this._domainData;in ngOnInit() method.
#Component({
selector: 'mg-domain-display',
templateUrl: './domain-display.component.html',
styleUrls: ['./domain-display.component.scss']
})
export class DomainWhiteListingDisplayComponent implements OnInit {
private _domainData = new BehaviorSubject<Domain[]>([]);
displayedColumns = ['id', 'domain'];
domain_Data: Domain[] = [];
#Input()
set domainData(value: Domain[]) {
this._domainData.next(value);
}
get domainData() {
return this._domainData.getValue();
}
constructor(private globalSettingService: GlobalSettingsService, private dialog: MatDialog) {
}
ngOnInit() {
this.domain_Data = this._domainData;
}
}
And the error is Type:BehaviourSubject is not assignable to type Domain[]. Property 'includes'is missing in type 'BehaviourSubject'
As I said my main table data variable is domain_Data:
<mat-table #table [dataSource]="domain_Data">
You need to subscribe and get the value from BehaviorSubject
ngOnInit() {
this._domainData.subscribe((data) => this.domain_Data = data);
}
Alternatively, As few have commented, you can subscribe in the template using async pipe:
<mat-table #table [dataSource]="domain_Data | async">
Generally, if you don't need to deal with data in the component, it's best using async pipe, as it takes care of unsubscribe automatically.
I arrived a bit late but I would like to add 2 additional information about #Aragorn answer :
Be careful when using async pipe in the template of a component with ChangeDetectionStrategy.OnPush, as it will completly force components to trigger lifecycle detection changes as often as the default Strategy.
Second important info : don't forget to unsubscribe when your component is destroyed, or you will have subscription still up if you never resolve the BehaviourSubject (here you just do 'next' operations) :
subscription: ISubscription;
this.subscription = this._domainData.subscribe((data) => this.domain_Data = data);
then in onDestroy :
ngOnDestroy() {
if (this.subscription) {
this.subscription.unsubscribe();
}
}