*ngIf else if in template - html

How would I have multiple cases in an *ngIf statement? I'm used to Vue or Angular 1 with having an if, else if, and else, but it seems like Angular 4 only has a true (if) and false (else) condition.
According to the documentation, I can only do:
<ng-container *ngIf="foo === 1; then first else second"></ng-container>
<ng-template #first>First</ng-template>
<ng-template #second>Second</ng-template>
<ng-template #third>Third</ng-template>
But I want to have multiple conditions (something like):
<ng-container *ngIf="foo === 1; then first; foo === 2; then second else third"></ng-container>
<ng-template #first>First</ng-template>
<ng-template #second>Second</ng-template>
<ng-template #third>Third</ng-template>
But I'm ending up having to use ngSwitch, which feels like a hack:
<ng-container [ngSwitch]="true">
<div *ngSwitchCase="foo === 1">First</div>
<div *ngSwitchCase="bar === 2">Second</div>
<div *ngSwitchDefault>Third</div>
</ng-container>
Alternately, it seems like a lot of the syntaxes I've got used to from Angular 1 and Vue aren't supported in Angular 4, so what would be the recommended way to structure my code with conditions like this?

Another alternative is to nest conditions
<ng-container *ngIf="foo === 1;else second"></ng-container>
<ng-template #second>
<ng-container *ngIf="foo === 2;else third"></ng-container>
</ng-template>
<ng-template #third></ng-template>

You can just use:
<ng-template [ngIf]="index == 1">First</ng-template>
<ng-template [ngIf]="index == 2">Second</ng-template>
<ng-template [ngIf]="index == 3">Third</ng-template>
unless the ng-container part is important to your design I suppose.
Here's a Plunker

This seems to be the cleanest way to do
if (foo === 1) {
} else if (bar === 99) {
} else if (foo === 2) {
} else {
}
in the template:
<ng-container *ngIf="foo === 1; else elseif1">foo === 1</ng-container>
<ng-template #elseif1>
<ng-container *ngIf="bar === 99; else elseif2">bar === 99</ng-container>
</ng-template>
<ng-template #elseif2>
<ng-container *ngIf="foo === 2; else else1">foo === 2</ng-container>
</ng-template>
<ng-template #else1>else</ng-template>
Notice that it works like a proper else if statement should when the conditions involve different variables (only 1 case is true at a time). Some of the other answers don't work right in such a case.
aside: gosh angular, that's some really ugly else if template code...

You can use multiple way based on sitaution:
If you Variable is limited to specific Number or String, best way is using ngSwitch or ngIf:
<!-- foo = 3 -->
<div [ngSwitch]="foo">
<div *ngSwitchCase="1">First Number</div>
<div *ngSwitchCase="2">Second Number</div>
<div *ngSwitchCase="3">Third Number</div>
<div *ngSwitchDefault>Other Number</div>
</div>
<!-- foo = 3 -->
<ng-template [ngIf]="foo === 1">First Number</ng-template>
<ng-template [ngIf]="foo === 2">Second Number</ng-template>
<ng-template [ngIf]="foo === 3">Third Number</ng-template>
<!-- foo = 'David' -->
<div [ngSwitch]="foo">
<div *ngSwitchCase="'Daniel'">Daniel String</div>
<div *ngSwitchCase="'David'">David String</div>
<div *ngSwitchCase="'Alex'">Alex String</div>
<div *ngSwitchDefault>Other String</div>
</div>
<!-- foo = 'David' -->
<ng-template [ngIf]="foo === 'Alex'">Alex String</ng-template>
<ng-template [ngIf]="foo === 'David'">David String</ng-template>
<ng-template [ngIf]="foo === 'Daniel'">Daniel String</ng-template>
Above not suitable for if elseif else codes and dynamic codes, you can use below code:
<!-- foo = 5 -->
<ng-container *ngIf="foo >= 1 && foo <= 3; then t13"></ng-container>
<ng-container *ngIf="foo >= 4 && foo <= 6; then t46"></ng-container>
<ng-container *ngIf="foo >= 7; then t7"></ng-container>
<!-- If Statement -->
<ng-template #t13>
Template for foo between 1 and 3
</ng-template>
<!-- If Else Statement -->
<ng-template #t46>
Template for foo between 4 and 6
</ng-template>
<!-- Else Statement -->
<ng-template #t7>
Template for foo greater than 7
</ng-template>
Note: You can choose any format, but notice every code has own problems

Or maybe just use conditional chains with ternary operator. if … else if … else if … else chain.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator#Conditional_chains
<ng-container [ngTemplateOutlet]="isFirst ? first : isSecond ? second : third"></ng-container>
<ng-template #first></ng-template>
<ng-template #second></ng-template>
<ng-template #third></ng-template>
I like this aproach better.

To avoid nesting and ngSwitch, there is also this possibility, which leverages the way logical operators work in Javascript:
<ng-container *ngIf="foo === 1; then first; else (foo === 2 && second) || (foo === 3 && third)"></ng-container>
<ng-template #first>First</ng-template>
<ng-template #second>Second</ng-template>
<ng-template #third>Third</ng-template>

you don't need to use *ngIf if you use ng-container
<ng-container [ngTemplateOutlet]="myTemplate === 'first' ? first : myTemplate ===
'second' ? second : third"></ng-container>
<ng-template #first>first</ng-template>
<ng-template #second>second</ng-template>
<ng-template #third>third</ng-template>

<ion-row *ngIf="cat === 1;else second"></ion-row>
<ng-template #second>
<ion-row *ngIf="cat === 2;else third"></ion-row>
</ng-template>
<ng-template #third>
</ng-template>
Angular is already using ng-template under the hood in many of the
structural directives that we use all the time: ngIf, ngFor and
ngSwitch.
> What is ng-template in Angular
https://www.angularjswiki.com/angular/what-is-ng-template-in-angular/

I came a cross this type of situation *ngIf elseIf else and I solved using ng-template, Hope the following snippet may depicts briefly,
I have a form control named "NIC" and need to show one error message at a time when the form control invalid.
form: FormGroup = new FormGroup({
NIC: new FormControl('', [Validators.required, Validators.minLength(10), Validators.maxLength(10), Validators.pattern("^[0-9]*$")])
});
Template
<mat-form-field appearance="standard">
<mat-label>NIC Number</mat-label>
<input matInput placeholder="Enter NIC no" formControlName="NIC">
<mat-error *ngIf="form.controls['NIC'].errors?.required; else minvalue">This field is mandatory.
</mat-error>
<ng-template #minvalue>
<mat-error *ngIf="form.controls['NIC'].errors?.minlength; else maxvalue">Minimum 10 charactors
needed.
</mat-error>
</ng-template>
<ng-template #maxvalue>
<mat-error *ngIf="form.controls['NIC'].errors?.maxLength; else numericonly">Maximum 10
charactors allowed.
</mat-error>
</ng-template>
<ng-template #numericonly>
<mat-error *ngIf="form.controls['NIC'].errors?.pattern">
Numeric characters only.
</mat-error>
</ng-template>
</mat-form-field>

If you have a straightforward case in which some variable can take several values (for example, foo is of 1 | 2 | 3 type) just use the plain ngSwitch:
<ng-container [ngSwitch]="foo">
<div *ngSwitchCase="1">First</div>
<div *ngSwitchCase="2">Second</div>
<div *ngSwitchCase="3">Third</div>
</ng-container>
However, if you need a more sophisticated if-else logic it may be better and clearer to describe it inside the component's code and then turn its result into one of the set of states:
getLandformType(): 'continent' | 'island' | 'peninsula' {
if(this.pieceOfLand.bigEnough){
return 'continent';
}
else if(this.pieceOfLand.surroundedByWater){
return 'island';
}
return 'peninsula';
}
which, again, can be handled through ngSwitch:
<ng-container [ngSwitch]="getLandformType()">
<div *ngSwitchCase="'continent'">Continent</div>
<div *ngSwitchCase="'island'">Island</div>
<div *ngSwitchCase="'peninsula'">Peninsula</div>
</ng-container>
This approach also ensures that only one option can win at once, in contrast to that with three different *ngIf (without elses) blocks.

You can also use this old trick for converting complex if/then/else blocks into a slightly cleaner switch statement:
<div [ngSwitch]="true">
<button (click)="foo=(++foo%3)+1">Switch!</button>
<div *ngSwitchCase="foo === 1">one</div>
<div *ngSwitchCase="foo === 2">two</div>
<div *ngSwitchCase="foo === 3">three</div>
</div>

Related

How can you make the *ngIf more compact?

Is it possible to somehow put the condition for changing the icon in one line?
Example #1
<ng-template [ngIf]="isMenuCollapsed" [ngIfThen]="open" [ngIfElse]="close">
</ng-template>
<ng-template #open>
<fa-icon [icon]='["fas", "bars"]'></fa-icon>
</ng-template>
<ng-template #close>
<fa-icon [icon]='["fas", "times"]'></fa-icon>
</ng-template>
Example #2
<fa-icon [icon]='["fas", "bars"]' *ngIf="isMenuCollapsed"></fa-icon>
<fa-icon [icon]='["fas", "times"]' *ngIf="!isMenuCollapsed"></fa-icon>
Yes, with method for example:
<fa-icon [icon]='getIcon()'></fa-icon>
In TS something like:
getIcon = () => isMenuCollapsed ? ["fas", "bars"] : ["fas", "times"];

How to groupBy rows in Mat-Table

I have mat-Table with filters and i want to group the table by one of the columns.
I want to make it a collapsible row that just shows how much you have of "that" kind and you can press it to see every single row inside that group.
I will share my HTML(that's a snippet not the whole HTML :
<mat-checkbox class="CheckBoxClass" value="clientType" [(ngModel)]="isChecked " disabled (change)="updateFilter('LQOCH_SHM_LOEZI_QTSR', clientType)" >{{clientType}}</mat-checkbox>
<br><br>
<mat-checkbox class="CheckBoxClass" value="contSize" [(ngModel)]="isChecked" disabled (change)="updateFilter('AORKH_MCOLH', contSize)" >{{contSize}}</mat-checkbox>
<br><br>
<mat-checkbox class="CheckBoxClass" value="storageType" [(ngModel)]="isChecked" disabled (change)="updateFilter('TAOR_QTSR_EBRI', storageType)" >{{storageType}}</mat-checkbox>
</div>
<!-- Container Table -->
<div>
<mat-table [dataSource]="dataSource" [hidden]="!show" >
<!-- Location -->
<ng-container matColumnDef="AITOR">
<mat-header-cell *matHeaderCellDef> Location
</mat-header-cell>
<mat-cell *matCellDef="let container"> {{container.AITOR}} </mat-cell>
</ng-container>
<!-- Type -->
<ng-container matColumnDef="SOG_MCOLH">
<mat-header-cell *matHeaderCellDef > Container Type</mat-header-cell>
<mat-cell *matCellDef="let container"> {{container.SOG_MCOLH}}</mat-cell>
This is my component NgOnInit :
ngOnInit() {
this.marinService.getAllContainers().subscribe((result) => {
//Data
this.dataSource = new MatTableDataSource(result);
//Paginator
this.dataSource.paginator = this.paginator;
this.dataSource.filterPredicate = ((data: Container, filter: string): boolean => {
const filterValues = JSON.parse(filter);
let conditions = true;
for (let filterKey in filterValues) {
if (filterKey === 'SOG_MCOLH' || filterKey === 'LQOCH_SHM_LOEZI_QTSR' || filterKey === 'AORKH_MCOLH' || filterKey === 'TAOR_QTSR_EBRI') {
conditions = conditions && data[filterKey].trim().toLowerCase().indexOf(filterValues[filterKey]) !== -1;
}
else if (filterValues[filterKey].length) {
conditions = conditions && filterValues[filterKey].includes(data[filterKey].trim().toLowerCase());
}
}
return conditions;
});
}
)}
Basically what I want to achieve is a row that collapse and shows how many rows it has and you can press on it to open the rows.
This doesn't come out of the box with angular Material. There are some solutions to do it yourself.
Here is another SO post that gives some directions: Angular Material mat-table Row Grouping
The second StackBlitz link provided there gives a good example of a table using grouped row.
You can also have a look at other libraries that include row grouping by default, if that's something your project allows.

How to give condition in image tag using angular 6?

My webpage contains,
<ng-container *ngFor="let horizontal of categories">
<ng-container *ngFor="let horizontalval of horizontal.items;let i=index">
<button [ngClass]="{'selected-color' : i==selectedIndex }"
[ngStyle]="{'padding-top':horizontal.items[i].title==='Business'?'2.56%':(horizontal.items[i].title==='Plat' ? '2.56%':null)}"type="submit" class="btn1" [id]="horizontal.items[i].title"
(click)="changeImage(id);">
<img [src]= horizontal.items[i].icon alt="image not found" class="icon"/>
{{ horizontal.items[i].title }}
</button>
</ng-container>
My ts file:
changeImage(option){
if(option == "Trans"){
this.imgDefault = 'imgChange';
console.log("this.imgDefault :",this.imgDefault);
}
else if(option == "Col"){
this.imgDefault = 'imgChange1';
console.log("this.imgDefault :",this.imgDefault1);
}
else if(option == "Business"){
this.imgDefault = 'imgChange2';
console.log("this.imgDefault :",this.imgDefault2);
}
}
So in my css I have kept the images which needs to be changed onclick of the button.
.imgChange{
background-image: url('../assets/Trans_2.png');
color:blue;
}
.imgChange1{
background-image: url('../assets/Trans_3.png');
color:blue;
}
.imgChange2{
background-image: url('../assets/Trans_4.png');
color:blue;
}
How can I give condition in img tag, so that onclick of a button to change the image?
You should add image in a div as below:-
ts
imgDefault = 'imgChange'; <-- default;
changeImage(option){
if(option == "Trans"){
this.imgDefault = 'imgChange';
console.log("this.imgDefault :",this.imgDefault);
}
else if(option == "Col"){
this.imgDefault = 'imgChange1';
console.log("this.imgDefault :",this.imgDefault1);
}
else if(option == "Business Process Re-engineering"){
this.imgDefault = 'imgChange2';
console.log("this.imgDefault :",this.imgDefault2);
}
}
.html
<ng-container *ngFor="let horizontal of categories">
<ng-container *ngFor="let horizontalval of horizontal.items;let i=index">
<button [ngClass]="{'selected-color' : i==selectedIndex }"
[ngStyle]="{'padding-top':horizontal.items[i].title==='Business'?'2.56%':(horizontal.items[i].title==='Plat' ? '2.56%':null)}"type="submit" class="btn1" [id]="horizontal.items[i].title"click)="changeImage(id);">
{{ horizontal.items[i].title }}
</button>
</ng-container>
</ng-container>
<div [ngClass]="imgDefault"></div>
Link:- https://stackblitz.com/edit/angular6-sqemmd?file=app/app.component.html
You can directly add class names based on the conditions - you are trying to add images from css replace the img tag as div which will render background image based on the class applied
Make sure if you want your images in the loop, if not move your div outside *ngFor
<div [class.imgChange]="imgDefault === 'imgChange'"
[class.imgChange1]="imgDefault === 'imgChange1'"
[class.imgChange2]="imgDefault === 'imgChange2'" ></div>
This will add all your classes based on the condition and the div will render the img - Hope this works Happy coding !!

Angular 6: How to use an array in template syntax with ngFor?

I have two arrays: books and booksDisplay. In my template I use *ngFor to iterate through my booksDisplay array:
<div *ngFor="let bookDisplay of booksDisplay">
<div class="book">
<div class="title" *ngIf="???">{{ ???.title }}
</div>
</div>
But inside my *ngFor-Element I need to look up in my books-Array, where id is equal to the current bookId in my booksDisplay-Array. So how can I use something like a where clause inside my *ngFor-Element?
Something like this should work. However, I would better prepare data in a model instead of making such calculations in the template..
<div *ngFor="let bookDisplay of booksDisplay>
<div class="book">
<ng-container *ngIf="checkIfExist(bookDisplay.id)">
<div class="title">{{ books[bookDisplay.id] }}
</ng-container>
</div>
</div>
Template:
checkIfExist(id: number) {
return this.books.find( book => book['id'] === id )
}
Assuming your component.ts looks something like this:
export class AppComponent {
booksDisplay = [
{
id: 1,
title: 'a'
},
{
id: 2,
title: 'b'
},
{
id: 3,
title: 'c'
},
]
}
You can a counter to ngFor and use that value to filter on with ngIf conditional like so:
<div *ngFor="let book of booksDisplay; let i= index">
<div class="book">
<div class="title" *ngIf="i === book.id">{{ book.title }}
</div>
<p>{{i}}</p>
</div>
Like this; https://angular.io/guide/displaying-data
<ng-container *ngFor="let book of booksDisplay; let i = index">
<ng-container *ngIf="book.id === '1'">
<p> {{book.description}}</p>
</ng-container>

Using $implict to pass multiple parameters

I have a template to be recursive, something similar to below:
<ng-container *ngTemplateOutlet="testTemplate; context: {$implicit:jsonObj1}">
</ng-container>
<ng-template #testTemplate let-Json1>
</ng-template>
It works fine, I send jsonObj1 using $implicit, but I would like to send two parameters to , if I try:
context: {$implicit:jsonObj1, $implicit:jsonObj2}
and try to access using
<ng-template #filterTemplate let-Json1 let-json2>
</ng-template>
It doesn't work, let me know, how to pass two parameters.
You don't need to use $implicit
You can use
1:
context: {$implicit:jsonObj1, b:jsonObj2}
with
<ng-template #filterTemplate let-json1 let-json2="b">
<div>{{json1}}</div></div>{{json2}}</div>
</ng-template>
or 2:
context: {$implicit: {a: jsonObj1, b:jsonObj2}}
with
<ng-template #filterTemplate let-json1>
<div>{{json1.a}}</div></div>{{json1.b}}</div>
</ng-template>
or 3:
context: {a:jsonObj1, b:jsonObj2}
with
<ng-template #filterTemplate let-json1="a" let-json2="b">
<div>{{json1}}</div></div>{{json2}}</div>
</ng-template>
Here are couple more (similar) options, this one includes using 'ngTemplateOutletContext' and also a condition (in 4th argument - for fun).
... to try - should work by copy and paste ...
<!-- DEMO using:
"=templateID; context:{prop:value, ...}"
( 4 arguments demo)
Note: $implicit identifies the default argument in the template.
The template does not need to assign the argument name,
- see the 3rd argument
-->
<div *ngFor="let item of ['Aaa', 'Bbb', 'Ccc']; index as ix">
<ng-container *ngTemplateOutlet="myTemplate1;
context:{cDemoName:'Option 1:',
cIx:ix+1,
$implicit:item,
cIsEven: ((ix % 2) === 0) ? 'true' : 'false' }">
</ng-container>
</div>
<hr>
<!-- DEMO using:
[ngTemplateOutlet]="templateID"
[ngTemplateOutletContext]="{"=templateID; context:{prop:value, ...}"
-->
<div *ngFor="let item of ['Dddd', 'Eee', 'Fff']; index as ix">
<ng-container [ngTemplateOutlet]="myTemplate1"
[ngTemplateOutletContext]="{
cDemoName:'Option 2',
cIx:ix+1,
$implicit:item,
cIsEven: ((ix % 2) === 0) ? 'true' : 'false' }
">
</ng-container>
</div>
<!-- DEMO template:
( 4 arguments expected)
-->
<ng-template #myTemplate1
let-cDemoName="cDemoName"
let-cIx="cIx"
let-cItem
let-cIsEven="cIsEven">
Context arguments demo name: {{cDemoName}} <br>
. . . . . Context index: {{cIx}} <br>
. . . . . Context item: --- {{ cItem }} --- <br>
. . . . . Context is-even: {{ cIsEven }} <br>
<br>
</ng-template>