Indirect Recursive Ng-Template in Angular 6 - angular6

<ng-template #list let-Items>
<ul>
<li *ngFor="let i of Items">
<ng-container [ngTemplateOutlet]="container" [ngTemplateOutletContext]="{$implicit: i}"></ng-container>
</li>
</ul>
</ng-template>
<ng-template #container let-list>
// some code
<ng-container [ngTemplateOutlet]="list" [ngTemplateOutletContext]="
{$implicit: list.columns}"></ng-container>
</ng-template>
I have a template "container" that includes another template "item".. the container "item" includes "container" again
This is not working, I'm getting this error:
ERROR TypeError: templateRef.createEmbeddedView is not a function
Any ideas? Thanks.

This issue is quite old so you may already have found the answer but in case someone else encounter this.
The problem you had is a collision in the naming of the variable list (let-list) in your container template that overrides the list template.
If you rename the template to listTpl for example (or the variable) it should work correctly:
<ng-template #listTpl let-Items>
<ul>
<li *ngFor="let i of Items">
<ng-container [ngTemplateOutlet]="container" [ngTemplateOutletContext]="{$implicit: i}"></ng-container>
</li>
</ul>
</ng-template>
<ng-template #container let-list>
// some code
<ng-container [ngTemplateOutlet]="listTpl" [ngTemplateOutletContext]="
{$implicit: list.columns}"></ng-container>
</ng-template>

For recursion inside template, we need to understand how ng-template works along with templateOutlet in angular. If we master it the we could make the recursion easily For the reference follow following code.
<ul>
<ng-container *ngTemplateOutlet="recursiveListTmpl; context:{ $implicit: serviceNavLinks }"></ng-container>
</ul>
<ng-template #recursiveListTmpl let-list>
<li *ngFor="let item of list">
<a class="list-group-item no-border">
{{item.linkTranslationKey | translate }}
</a>
<ul *ngIf="item.childNavLinks && item.childNavLinks.length > 0">
<ng-container *ngTemplateOutlet="recursiveListTmpl; context:{ $implicit: item.childNavLinks }"></ng-container>
</ul>
</li>
This is tested in Angular 8. Did not checked for earlier versions of angular.

Related

How I can get definite value in keyvalue pair?

My object has a specific set of key / value pairs,
i want to get a specific value, not everything, how can i do this?
For example:
.ts
movie = {
title:'title',
description:'descriprtion',
rating:4
};
html:
<ul *ngFor="let item of movie | keyvalue">
<li> {{item.value}} </li>
</ul>
And I only want to receive item.description, can you tell me how this can be implemented?
<ul *ngFor="let item of movie | keyvalue">
<ng-container *ngIf="item.key === 'description'">
<li>{{item.value}}</li>
</ng-container>
</ul>
But to be honest you would be better off just doing:
<div>{{ move.description }}</div>
Since you do not seem to want to iterate the object props, but only want description.

Display row only if certain conditions are met

I'm a intern, and I need some help.
I've a ngx-datatable, and I want to display row when condition are met. How can I do it? For example, if country covered is America, I display, if not, I didn't.
<ngx-datatable class="table-zenmoov" [columnMode]="'force'" [rows]="rows" [rowHeight]="'auto'"
[headerHeight]="'auto'" [footerHeight]="'auto'" [externalPaging]="true" [count]="paginationInfo.totalElements"
[loadingIndicator]="loadingIndicator"
[offset]="paginationInfo.currentPage" [limit]="paginationInfo.pageSize" (page)="setTablePage($event)"
(sort)="onSort($event)">
<ngx-datatable-column name="Name" [prop]="'first_name'" [sortable]="true">
<ng-template let-row="row" ngx-datatable-cell-template>
<div class="user-wrap">
<div class="zenmoov-vendor" [style.background]="row.company_id?'#f0ad4e':'#5bc0de'"></div>
<div class="img-circle img-wrapper vendor-avatar">
<img *ngIf="row.avatar_url" [src]="row?.avatar_url " alt="logo">
</div>
<ng-container *ngIf="auth.userRID=='admin'">
<a [routerLink]="['../../vendor/',row._id]">{{ row?.first_name }} {{ row?.last_name }}</a>
</ng-container>
<ng-container *ngIf="auth.userRID=='hr'">
<a [routerLink]="['../../../vendor/',row._id]">{{ row?.first_name }} {{ row?.last_name }}</a>
</ng-container>
<ng-container *ngIf="auth.userRID=='talent'">
<a [routerLink]="['/talent/vendor/',row._id]">{{ row?.first_name }} {{ row?.last_name }}</a>
</ng-container>
</div>
</ng-template>
</ngx-datatable-column>
<ngx-datatable-column name="Company" [sortable]="false">
<ng-template let-row="row" ngx-datatable-cell-template>
{{ row?.company_name }}
</ng-template>
</ngx-datatable-column>
</ngx-datable class>
You can use *ngIf on html element and it will be displayed only if the conditions are met.
I have never used ngx-datatable however, from a quick look into the documentation and your code I think I got it. I suppose that this bit [rows]="rows" is where you feed your data to the datatable, what you need to do is filter out the objects you don't want to be in the datatable, something like this in your TypeScript file:
TypeScript File
// Array of strings that contains all countries in America
const americaCountries = [...];
// Iterate over each row and verify if the country of that row is in the array americaCountries
// If it is, that row is added to a new array named americaRows
this.americaRows = this.rows.filter(row => americaCountries.includes(row.country));
HTML File
[rows]="americaRows"
Bear in mind that this is pseudo code because I don't know your TypeScript code and the rows object structure.

mat-list in angular 5

I have a question about implement mat-list in angular 5.
I had this code, when I had the divider insede the mat-list-item but the divider line didn't show always
<mat-list >
<mat-list-item class="pathitem" *ngFor="let item of Lst" style="height: 76px;">
<div>
<h3>item.title</h3>
<p>item.desc</p>
<p>item.ad</p>
</div>
<mat-divider class="background-divider"></mat-divider>
</mat-list-item>
</mat-list>
then I change that for this:
<mat-list *ngFor="let item of LstTramosFiltro" >
<mat-list-item class="pathitem" style="height: 76px;">
<div>
<h3>item.title</h3>
<p>item.desc</p>
<p>item.ad</p>
</div>
</mat-list-item>
<mat-divider class="background-divider"></mat-divider>
</mat-list>
My question is which of the two is the correct solution if you use ngfor in mat-list or use in mat-list-item; and where the use of the mat-divider is correct
thanks for your help.
First, ngfor should be placed in the mat-list-item instead of mat-list, otherwise you will get multiple list and each have multiple mat-list-item, and if you have multiple sections you can use mat-divider to divide into different sections, the mat-divider should be placed between two mat-list-item not inside the mat-list-item. Check https://material.angular.io/components/list/overview#lists-with-multiple-sections for details.
the first one is current. The item you need to iterate over is the one that should have ngFor. If you want to include the divider, you may wrap both items into ng-template like this:
<mat-list>
<ng-template *ngFor="let item of LstTramosFiltro">
<mat-list-item class="pathitem" style="height: 76px;">
<div>
<h3>item.title</h3>
<p>item.desc</p>
<p>item.ad</p>
</div>
</mat-list-item>
<mat-divider class="background-divider"></mat-divider>
</ng-template>
</mat-list>
You should use the ngFor for the mat-list-item, not mat-list, assuming you only want to display a single list of items.
Doc references: https://material.angular.io/components/list/examples, https://material.angular.io/components/divider/overview
If you want the divider to show after every item except the last, it can be inserted as part of the mat-list-item (which I think you did), like so:
<mat-list>
<mat-list-item class="pathitem" *ngFor="let item of Lst; last as last" style="height: 76px;">
<h3>item.title</h3>
<p>item.desc</p>
<p>item.ad</p>
<mat-divider class="background-divider" *ngIf="!last"></mat-divider>
</mat-list-item>
</mat-list>

Expand/collapse list items in recursive tree menu in angular

How can i achieve that my nested lists expand on click?
At the moment it just opens the first level.
sidebar.component.html
<ul>
<ng-template #recursiveList let-list>
<li *ngFor="let item of list" (click)="listClick($event, item)">
{{item.name}}
<ul *ngIf="item.folders?.length > 0" [ngClass]="{ 'subfolder': selectedItem == item }">
<ng-container *ngTemplateOutlet="recursiveList; context:{ $implicit: item.folders }"></ng-container>
</ul>
</li>
</ng-template>
<ng-container *ngTemplateOutlet="recursiveList; context:{ $implicit: list }"></ng-container>
</ul>
Click from sidebar.component.ts
listClick(event, newValue) {
console.log(newValue);
this.selectedItem = newValue;
}
The first level works like it should. I click on the folder name and it expands. But when I click on the list element on the next level the list collapses instead of expanding further.
I think the event is bubbling up to the parent i.e. when you click on a child, you are also clicking on the parent element too. Adding event.stopPropagation() should stop the event bubbling to the parent. I.e.
listClick(event, newValue) {
console.log(newValue);
this.selectedItem = newValue;
event.stopPropagation();
}
Update: I am not sure how your data is being fetched, so I am not sure if this is 100% right. But here is a working example of how it should work (you do need the event.stopPropagation();) I have added && item.showSubfolders the *ngIf which gets toggled on click:
<ul>
<ng-template #recursiveList let-list>
<li *ngFor="let item of list" (click)="listClick($event, item)">
{{item.name}}
<ul *ngIf="item.folders?.length > 0 && item.showSubfolders" [ngClass]="{ 'subfolder': selectedItem == item }">
<ng-container *ngTemplateOutlet="recursiveList; context:{ $implicit: item.folders }"></ng-container>
</ul>
</li>
</ng-template>
<ng-container *ngTemplateOutlet="recursiveList; context:{ $implicit: list }"></ng-container>
</ul>
And the Listclick:
listClick(event, newValue) {
console.log(newValue);
this.selectedItem = newValue;
newValue.showSubfolders = !newValue.showSubfolders
event.stopPropagation()
}
working example: https://stackblitz.com/edit/angular-emz37r

Angular *ngFor & Materialize-CSS Collapsible Not working

I'm using Materialize-CSS collapsibles inside my project and I'm trying to put a collapsible inside of another.
If I remove the first *ngFor inside ng-container, then it works just fine, but once I add the *ngFor loop it will not work. Any help is appreciated! I attached the simplified code & a photo below
<ul class="collapsible" data-collapsible="expandable">
<ng-container *ngFor="let month of months">
<li >
<div class="collapsible-header">
<span><i class="material-icons">date_range</i> <strong>{{month | amDateFormat: 'MMMM YYYY'}}</strong></span>
</div>
<div class="collapsible-body">
<ul class="collapsible" data-collapsible="expandable">
<li *ngFor="let feeding of feedings | orderBy: 'date'">
<ng-container *ngIf="(month | amDateFormat: 'MMM YYYY') == (feeding.date | amDateFormat: 'MMM YYYY')">
<div class="collapsible-header valign-wrapper">
<span><i class="material-icons">today</i> <strong>{{feeding.date | amDateFormat:'MMMM Do YYYY'}}</strong></span>
</div>
<div class="collapsible-body">
...
</div>
</ng-container>
</li>
</ul>
</div>
</li>
</ng-container>
</ul>
Here is a photo showing what I want
The bottom collapsible "Month" is working correctly. The top one has the *ngFor loop and will not open the inside collapsibles
Thank you!
I was able to install angular2-materialize & hammerjs to my app and then change the ul tags
<ul class="collapsible" data-collapsible="accordion" materialize="collapsible">
I added materialize="collapsible" to both ul's and that fixed it!