Angular material table, how to format output - html

I am working on Angular module, and I am fetching data from web api.
I am displaying web api response in a mat table.
I have a list of strings that I want to display with line break.
To do that I am formatting the response and adding <span> and <br> tags.
But the output is displaying the <span> and <br> tags also.
Here is my code.
const data:any=[];
this.receiveService.get(this._parentLocID).subscribe(
res => {
res.forEach( g1=>{
let nstatus=[];
let newstatus=g1.status;
newstatus.forEach(
res1=>{
nstatus.push('<span>'+res1+'</span>'+'<br>');
}
);
g1.status=nstatus;
data.push(g1);
});
const resultData: any = data;
this.dataSource.data = resultData;
My objective is simple, I want the values 'completed','In progress' and 'Queued' in separate lines.

In order to bind HTML content you should use [innerHTML] property, you can find more details in this article
https://www.google.com/amp/s/blog.fullstacktraining.com/binding-html-with-angular/amp/
I fell in your scenario instead of building HTML in typescript you can push only string values and use ngFor in HTML and bind it to {{content}} something like that will be a better option.

I have solved it this way. It was much simpler than I thought.
I had to put *ngIf within mat-cell. And for each item in the array,I put an "br" tag at the end of each item.
<mat-cell *matCellDef="let element">
<p class="font-weight-600 text-center" *ngIf="columns.key=='Status'">
<span *ngFor="let item of element[columns.value]">
{{ item }}
<br>
</span>
</p>
<p class="font-weight-600 text-center" *ngIf="columns.key!='Status'">
{{element[columns.value]}}
</p>
</mat-cell>
also, I removed this part from .ts file.
res.forEach( g1=>{
let nstatus=[];
let newstatus=g1.status;
newstatus.forEach(
res1=>{
nstatus.push('<span>'+res1+'</span>'+'<br>');
}
);
g1.status=nstatus;
data.push(g1);
});
so the .ts is
this.receiveService.get(this._parentLocID).subscribe(
res => {
const resultData: any = res;
this.dataSource.data = resultData;
}
that solved the issue.

Related

Get data from nested json in Angular

Im trying to iterate over an array but page is displaying [ object Object] instead. How can I get data.id of my example?
Service:
return this._http.get<CustomersList[]>(this.apiUrl,{ headers: reqHeader });
My json looks like this:
[ {"current_page":1,"data":[{"id":25},{"id":26}] }]
And here is my component:
customerslist$: CustomersList[];
return this.customerdataService.getCustomersList().subscribe(data => this.customerslist$ = data);
Would be great if anybody can help me?
The data property is an array contained in an array. You either need two *ngFor directives
<ng-container *ngFor="let customer of customerslist$">
<ng-container *ngFor="let item of customer.data">
{{ item.id }}
</ng-container>
/ng-container>
Or if you're sure the customerslist$ array will always contain only one element, you could access the first element directly.
<ng-container *ngFor="let item of customerslist$[0].data">
{{ item.id }}
</ng-container>
Also if you're aren't using the this.customerList$ in the controller, you could skip the subscription in the controller and use the async pipe in the template. It takes care of any potential memory leak issues due to open subscriptions.
Contoller
customerslist$: Observable<CustomersList[]>; // <-- typeof `Observable`
ngOnInit() {
this.customerslist$ = this.customerdataService.getCustomersList(); // <-- don't subscribe yet
}
<ng-container *ngIf="(customerslist$ | async) as customerslist">
<ng-container *ngFor="let customer of customerslist">
<ng-container *ngFor="let item of customer.data">
{{ item.id }}
</ng-container>
</ng-container>
</ng-container>
By convention, variable names suffixed with a dollar sign (customerlist$) are used to denote observables.
If you are using Observable $ sign at the end of variable denotes observable,
in that case you do not need to subscribe only return value, in template you can subscribe using async pipe which automatically unsubscribe as well to prevent memory leak and a recommended approach.
Here we assume we are only taking first element of array that why directly accessing it giving first index, otherwise we need to iterate that array in template as well.
customerslist$: Observable<CustomersList[]>;
this.customerslist$ = this.customerdataService.getCustomersList()
.pipe(map(res) => res[0].data));
In template use async pipe to subscribe
<ng-container *ngFor="let data of customerslist$ | async">
<span> {{ data.id }} </span>
</ng-container>
WITHOUT OBSERVABLE
customerslist: CustomersList[];
this.customerdataService.getCustomersList()
.subscribe((res) => this.customerslist = res[0].data));
In template
<ng-container *ngFor="let data of customerslist">
<span> {{ data.id }} </span>
</ng-container>

How to assign a starting index value while using ngFor in angular

I have a requirement to always display minimum of 5 rows(5 or more rows) in a table. For example, if there are 2 rows available in Database, I need to display other 3 more rows in UI with empty rows.
Here is what I tried so far:
<div *ngFor="let task of tasks; let i = index">
<div class="rowDiv">{{task.id}}</div>
</div>
Here I want to run the loop from i = tasks.size to i < = 5. So that I have total of 5 rows in UI. How to achieve this?
<div *ngFor=" let i = index">
<div class="rowDiv"></div>
</div>
You can loop over an array of 5 items, and use *ngIf to display an additional row if no data item exists at a given index:
<div *ngFor="let task of tasks">
<div class="rowDiv">{{task.id}}</div>
</div>
<ng-container *ngFor="let i of [0,1,2,3,4]">
<div *ngIf="!tasks[i]">
<div class="rowDiv">This row is empty</div>
</div>
</ng-container>
See this stackblitz for a demo.
you can also add so many rows you need after
<table>
<row *ngFor="let task in task">
</row>
<!--if task.length<5-->
<ng-container *ngIf="tasks.length<5">
<!-use slice:0:5-tasks.length-->
<row *ngFor="let i of [0,1,2,3,4] |slice:0:5-tasks.length">
</row>
</ng-container>
</table>
You don't need to keep this logic in html.
In you class you can do something like this: (suppose you fetch tasks from server)
this.getTasks().subscribe((tasks) => {
const emptyTasks = Array(5).fill({id: 'empty'});
this.tasks = tasks.map((t, index) => t || emptyTasks[index]);
})
This could be better handled in the controller. In case of default change detection strategy, the template is reloaded multiple times without our control or knowledge. So it's better to make sure the tasks variable has atleast 5 elements in the controller rather than to control the flow in the template. You could something like the following in the controller and leave the template unchanged.
for (let i = 0; i < 5; i++) {
if(!this.tasks[i].id) {
this.tasks[i].id = '';
}
}

display multiple nested data with angular6

I'm receiving JSON data from an API which has some child objects as well. The API has a menu level and down the menu, it's having meals. What I want to do is to display meals relating to each menu under the menu
JSON from API
[{"id":6,"name":"Menu 1","serveDate":"2019-05-10","meals":[{"id":13,"name":"Rice with Stew","description":"rice","image":"","mealType":"BREAKFAST","unitPrice":5,"status":"ENABLED"}]},{"id":5,"name":"Menu 2","serveDate":"2019-06-10","meals":[{"id":13,"name":"Corn Flakes,"description":"Flakes","image":"","mealType":"BREAKFAST","unitPrice":5,"status":"ENABLED"}]},{"id":4,"name":"Menu 3","serveDate":"2019-07-10","meals":[]}]
HTML
<div *ngFor="let item of menuList">
<h2>Menu</h2>
{{item.name}} - {{item.servate}}
<h2 *ngFor="let item of menuList.meals">Meals</h2>
{{item.name}} - {{item.mealType}}
</div>
JS
getMenus() {
this.menuServices.menuList(this.pagedData)
.subscribe(
response => {
if (response && response.code === HttpStatus.OK) {
this.menuList = response.data;
}
},
);
}
Any help on how to make this work correctly the way it should work?
<div *ngFor="let menu of menuList">
<h2>Menu</h2>
{{menu.name}} - {{menu.servate}}
<h2>Meals</h2>
<ng-container *ngFor="let meal of menu.meals">
{{meal.name}} - {{meal.mealType}}
</ng-container>
</div>
Using this way you don't have to add unnecessary divs or any other html tag for looping in angular.
this is the perfect way to do nested loops without changing your html
No need to access the main list as you have your meals array in the item object.
Change HTML Code to:
<div *ngFor="let item of menuList">
<h2>Menu</h2>
{{item.name}} - {{item.servate}}
<h2>Meals</h2>
<div *ngFor="let item of item.meals">
{{item.name}} - {{item.mealType}}
</div>
</div>
When you're doing something like let item of menuList that means the item variable should be used to refer to an individual item within your loop. To avoid confusion, I'd also recommend naming these item vars for nested loops differently.
Another important thing to keep in mind that all the markup that you want to be output for each array item should be wrapped with an element with *ngFor. It's not the case with your <h2> tag being printed for each meal, but not the meal description.
Edit the template as follows:
<div *ngFor="let menuItem of menuList">
<h1>Menu</h1>
<h2>{{menuItem.name}} - {{menuItem.serveDate}}</h2>
<p>maybe description here</p>
<h3>Meals</h2>
<p *ngFor="let mealItem of menuItem.meals">{{mealItem.name}} - {{mealItem.mealType}}</p>
</div>

Why don't the String values get displayed as HTML tags using innerHTML?

I get string data from my database to my variable, I want to display them as HTML tags by [innerHTML], but it doesn't work.
The variable is displayed on string instead HTML Tags.
I tried to use with DomSanitizer but it don't work:
article:Article[];
(article.articlesTitleHtml:SafeHtml;)
in the function:
this.article.forEach(elementArticle => {
elementArticle.articlesTitleHtml = this.sanitizer.bypassSecurityTrustHtml(elementArticle.articleTitle)
});
in HTML page:
<div *ngFor="let item of articles">
<div id="{{item.articleId}}">
<h2 class="chart" [innerHTML]="item.articlesTitleHtml"></h2>
</div>
my code:
in Type Script:
articles:Article[];
ngOnInit() {
this.apiArticle.getArticleList().subscribe(data=>{
this.articles=data
})
in HTML page:
<div *ngFor="let item of articles">
<div id="{{item.articleId}}">
<h2 class="chart" [innerHTML]="item.articleTitle"></h2>
</div>
</div>
It should work, you can check here...
if you can share the type of data that you're dealing with, it will give more insight into the appropriate DomSanitizer method which should be called
in my example above, i used both bypassSecurityTrustHtml & bypassSecurityTrustUrl for the 2 different types of strings which needed sanitization

What is the proper way to show different elements based on function return

So basically what I want is to show
'Something1' when the job is not processing
'Something2' when the job is running and status is '0'
'Something3' when the job is running but status is something else
I tried the following code snippet, but it looks like let-status in the outer template will never get assigned. Not sure whether the implementation is correct or not, could anyone give me two cents on how to make this logic work?
Thanks.
<span *ngIf="!isProcessing(); else elseBlock">
Something1
</span>
<ng-template #elseBlock let-status="queryPlaybackStatus()" *ngIf="queryStatus() === '0'; else innerElseBlock">
<span>
Something2
</span>
<ng-template #innerElseBlock>
<span>
Something3
</span>
</ng-template>
</ng-template>
I would suggest defining a string in your component, where you have much better control over your logic. In the component, set the string to the appropriate text.
Then bind to that string in the template.
I don't have all of your needed logic here, but something like this:
isImage = false;
get statusText(): string {
if (!isProcessing()) {
this.isImage = false;
return 'Something1';
} else {
this.isImage = true;
return 'path to image';
}
}
This uses a getter, which provides a way for a component property to have logic.
Then just bind to statusText in the template.
<span *ngIf='!isImage'>
{{statusText}}
</span>
<span *ngIf='isImage>
<img ...>
</span>
<span *ngIf="!isProcessing(); else elseBlock">
Something1
</span>
<ng-container #elseBlock *ngIf="queryStatus() as status">
<span *ngIf="status === '0'; else innerElseBlock">
Something2
</span>
<ng-template #innerElseBlock>
<span>
Something3_with_{{status}}%
</span>
</ng-template>
</ng-container>
So basically this need a magic combination of ng-container and ng-template.
It sounds to me like you want ngSwitch. This allows you to switch based on logic, which you should encapsulate in your component, not your template. First, let's create a property that encapsulates our logic in our component:
public get currentStep(): number {
if (!this.isProcessing) {
return 1;
} else if (this.queryStatus === 0) {
return 2;
} else {
return 3;
}
}
Next, let's bind our ngSwitch statement to this newly-created property:
<div [ngSwitch]="currentStep">
<div *ngSwitchCase="1">
<p>Something1</p>
<div>Put whatever you want in here! Images, etc.</div>
</div>
<div *ngSwitchCase="2">
<p>Something2</p>
<p>Loading....</p>
</div>
<div *ngSwitchCase="3">
<p>Something3</p>
<p>All done!</p>
</div>
</div>
That should get you where you need to go. Since this is simple, I created a stackblitz example that will demonstrate a working version of this. In the example, you can click a button and watch the app cycle through all the steps (I'm using setTimeout to simulate a long-running server query).