Angular 2 enum with ngClass - html

Hello I am trying to use a conditional class with an enum. I have used enums in html before with ngSwitchCase and have the same error that I'm getting now. When I would add a property called that enum and assign it to that enum it would work.
working example:
<ng-container *ngFor="let column of columns" [ngSwitch]="column.dataType">
<td *ngSwitchCase="DataType.Text">{{getPropertyValue(row,column.propertyName)}}</td>
<td *ngSwitchCase="DataType.Date">date</td>
<td *ngSwitchCase="DataType.Number">number</td>
<td *ngSwitchDefault>default</td>
</ng-container>
ts
private DataType = DataType;
not working:
<span *ngClass="(column.sortType === SortType.Ascending)? 'privilege-grid-sortasc': (column.sortType === SortType.Descending)?'privilege-grid-sortdesc':'privilege-grid-sortnone'"></span>
I also have tried [ngClass] ="{'class-name': var === enum,...}"
ts
private SortType = SortType;
error message:
Cannot read property 'Ascending' of undefined

I found a really good tutorial here : https://coryrylan.com/blog/angular-tips-template-binding-with-static-types
in summary the syntaxe is [ngClass] ="{'class-name': var === enum}":
#Component({
selector: 'app-message',
template: `
<div class="message" [ngClass]="{
'message--error': messageTypes.Error === type,
'message--success': messageTypes.Success === type,
'message--warning': messageTypes.Warning === type
}">
<ng-content></ng-content>
</div>
`
})
export class MessageComponent implements OnInit {
#Input() type = MessageTypes.Default;
private messageTypes = MessageTypes; //very important to declare here
constructor() { }
ngOnInit() { }
}

I think your problem must lie somewhere else. I recreated your scenario using the [ngClass] binding with an enum and it works fine for me:
[ngClass] ="{'class-name': var === enum,...}"
Is your template in the second case on a separate .html file and not in the first case? I've had problems where a private variable on my component file can't be read by the template file.

Related

Angular Input value doesn't change

I have a problem. In my Angular project I have 2 components with an two-way bound parameter. The parameter is an object id. Both components use the same service which stores the list with objects. The parent contains the list where you can select the object and the child shows the selected object details. Here is the code of the parent:
<div>
<table>
<tr>
<th scope="col">Title</th>
</tr>
<tr *ngFor="let offer of service1.findAll();"
[style.background-color]="offer.rowClicked ? '#ed9b82' : ''"
(click)="highlightClickedRow(offer)">
<td>{{offer.title}}</td>
</tr>
</table>
</div>
<div>
<app-detail3 [editedOfferId]="offerSelectedId" (editedOfferChanged)="offerSelectedId=$event"></app-detail3>
</div>
with the typescript:
#Component({
selector: 'app-overview3',
templateUrl: './overview3.component.html',
styleUrls: ['./overview3.component.css']
})
export class Overview3Component implements OnInit {
public offerSelectedId: number = -1;
constructor(public service1: OffersService) { }
ngOnInit(): void {
}
public highlightClickedRow(offer :Offer) {
let offers: Offer[] = this.service1.findAll();
for (let i = 0; i < offers.length; i++) {
if (offers[i] != offer) {
offers[i].rowClicked = false;
}
else {
offers[i].rowClicked = true;
this.offerSelectedId = offers[i].id;
}
}
}
}
And here is the detail component:
<div id="content" *ngIf="editedOfferId != -1">
<div id="data-table">
<table>
<tr>
<th scope="row" colspan="2">Selected offer details(id = {{service.findById(editedOfferId)!.id}})</th>
</tr>
<tr>
<th scope="row">Title:</th>
<td><input type="text" id="txtTitle" (input)="checkObjectChanged()"></td>
</tr>
<tr>
<th scope="row">Description:</th>
<td><input type="text" id="txtDescription" (input)="checkObjectChanged()" value="{{service.findById(editedOfferId)!.description}}"></td>
</tr>
<tr>
<th scope="row">Status:</th>
<td>
<select (input)="checkObjectChanged()" id="txtStatus">
<option *ngFor="let key of keys" [ngValue]="key" [value]="status[key]" [label]="status[key]" [selected]="service.findById(editedOfferId)!.auctionStatus === key"></option>
</select>
</td>
</tr>
<tr>
<th scope="row">Highest Bid:</th>
<td><input id="txtHighestBid" (input)="checkObjectChanged()" value="{{service.findById(editedOfferId)!.valueHighestBid}}"></td>
</tr>
</table>
</div>
</div>
<div *ngIf="editedOfferId == -1">
<label id="lblNothingSelected">Nothing has been selected yet</label>
</div>
With the typescript:
#Component({
selector: 'app-detail3',
templateUrl: './detail3.component.html',
styleUrls: ['./detail3.component.css']
})
export class Detail3Component implements OnInit {
#Input() editedOfferId: number = -1;
#Output() editedOfferChanged = new EventEmitter<number>();
public selectedOffer: Offer = new Offer("", "", new Date(), AuctionStatus.NEW, 0);
status = AuctionStatus
keys: Array<number>;
constructor(public service: OffersService) {
this.keys = Object.keys(this.status).filter(k => !isNaN(Number(k))).map(Number);
this.selectedOffer = service.findById(this.editedOfferId)!;
}
ngOnInit(): void {
}
}
Now when I click on an object in the parent component, the details will be loaded in de detail component, but when I edit for example the title input field and then change the object in the parent, I would expect that the data of the new selected object will be loaded. This happens, but only the fields that were not edited at that moment, so when I edit the title, everything will be loaded correctly, but the value of the title will remain the same. Even tough the object has a different title, the value that I was typing stays in the input field. Why is this happening and how can I fix this?
First of all , you did not implement your save changes business logic of your details component , so , when you make some changes in your object , all changes you made will not save then will not be loaded in your parent component using service1. so recommend implementing a save change service request to apply changes .
to do that exactly how you want by your code logic , Use one of those solutions:
Solution 1:
Create an update method for each offer field in your OffersService.
class OffersService {
...
updateTitle(offerId:number,newTitle:string){ ... }
updateDescription(offerId,newDesc:string){ ... }
....
}
In your edit component use onFocuseOut event ( angular focus Out event depend to your angular version ) to each control field , example for title:
<tr>
<th scope="row">Title:</th>
<td><input type="text" id="txtTitle" (focusout)="service1.updateTitle(editedOfferId,$event.target.value)"></td>
</tr>
please do the same thing for each other fields and make sure your update.. methods save offer values as well .
Solution 2:
create a service method in service1 that saves all changes of a specific Offer object. example:
class OffersService {
...
updateOfferById(id:number,newOffer:Offer) { ... }
...
}
make submit changes button in your details component .html , and in its handler implement change detection and call :
service1.updateOfferById(editedOfferId,changedOffer).subscribe( ... );
the difference between the two methods , the first apply changes without using user action to do like submit change button, the input focusOut event do that for each field instead of saving all changes at once .
Instead of using the offerSelectedId in your Overview3Component, change your variable to store the entire object reference.
So instead of public offerSelectedId: number = -1;,
use something like public offerSelected: Offer = new Offer(...)
Then set the selected item (this.offerSelected = offers[i];)
and pass the reference (offerSelected) into the your child component Detail3Component.
#Component({
selector: 'app-overview3',
templateUrl: './overview3.component.html',
styleUrls: ['./overview3.component.css']
})
export class Overview3Component implements OnInit {
public offerSelected: Offer = new Offer("", "", new Date(), AuctionStatus.NEW, 0);
allOffers: Offer[] = [];
constructor(public service1: OffersService) { }
ngOnInit(): void {
this.allOffers = this.service1.findAll();
}
public highlightClickedRow(offer :Offer) {
for (let i = 0; i < this.allOffers.length; i++) {
if (offers[i] != offer) {
offers[i].rowClicked = false;
}
else {
offers[i].rowClicked = true;
this.offerSelected = offers[i]; // set with entire object
}
}
}
}
Remember to make the required changes in the child component as well, changing from just the object ID #Input() editedOfferId: number = -1;
to the object #Input() selectedOffer: Offer;.
#Component({
selector: 'app-detail3',
templateUrl: './detail3.component.html',
styleUrls: ['./detail3.component.css']
})
export class Detail3Component implements OnInit {
// NOTE: This object would be binded back to the source
#Input() selectedOffer: Offer = new Offer("", "", new Date(), AuctionStatus.NEW, 0);
constructor() {
}
ngOnInit(): void {
}
}
Passing the entire object reference like this will allow you to change the values and have them reflected in the parent, in this case the allOffers array.
There should be no need for the EventEmitter #Output() editedOfferChanged = new EventEmitter<number>(); unless you want to know if they changed something.
Just note that this will only update the data in the current state and will not be saved on refresh unless you implement some saving logic.
Also this is by no means the best solution but it is a solution.
You've not implemented save changes logic in the detail3 component and values are not linked to any object or variable. It can be done in multiple ways. You should pass an offer object to the child component instead of an id so that you don't have to find selected offer from all the offers. Bind selected offer object to your forms using ngModel or use reactive forms.
Since data passed to child component is object and any change in object will be reflected to main array (you can prevent that by using spread operator when passing value to child component if you don't want these change to be reflected to main array and fire an event to manually to save data on change).
I would prefer reactive forms and submit/save button to save change. I've implemented an example in stackblitz.
"the value of the title will remain the same"
<tr>
<th scope="row">Title:</th>
<td><input type="text" id="txtTitle" (input)="checkObjectChanged()"></td>
</tr>
Your child component don't assign value for title.
value="{{service.findById(editedOfferId)!.title}}"
It seem the parent component doesn't reload the list offers after child component already updated it.
I suggest you change the function:
(editedOfferChanged)="offerSelectedId=$event"
To
(editedOfferChanged)="onEditedOffer($event)"
For easily debug and do few tasks at parent component after offer was updated at child component.
For Example
onEditedOffer(event){
console.log('onEditedOffer',event;
offerSelectedId = event;
//do something to update list or single offer.
//i choose easy way "reload list offers"
offers: Offer[] = this.service1.findAll();
}
it is possible to simply use Ngmodel with your variable.
[(ngModel)]="YourVar"
in the tag you want and then on the .ts side you can reuse this same variable by using the name of the variable

Change the value of checkbox to string

I already saw many questions but I still can't change the value of a checkbox to a string. I need to change the value according to true - false -> 'A' - 'B'. I am using reactive forms in angular.
I got this:
...
..*ngFor="let item of myForm.get('people')['controls'];
...
<mat-checkbox formControlName="check"
[checked]="item.get('check').value === 'A' ? true : false"
(change)="item.get('check').setValue($event.checked ? 'A' : 'B')">
</mat-checkbox>
It has to be in check true if the value that comes is 'A' and in false if it is 'B', I really don't see whydon't set that value on change. I need to send as a string the value of the checkbox
<form [formGroup]="form" (ngSubmit)="submit()">
{{form.value | json}} // just for to see the output
<ng-container formArrayName="people" *ngFor="let item of people.controls; let i = index">
<div [formGroupName]="i">
<mat-checkbox formControlName="check" [checked]="item.get('check').value === 'A' ? true : false"
(change)="setCheck($event.checked, i)">
</mat-checkbox>
</div>
</ng-container>
<button mat-button>submit</button>
</form>
import { FormArray } from '#angular/forms';
import { Component, OnInit } from "#angular/core";
import { FormBuilder, FormGroup } from '#angular/forms';
#Component({
selector: "app-test",
templateUrl: "./test.component.html",
styleUrls: ["./test.component.scss"],
})
export class TestComponent implements OnInit {
form: FormGroup
constructor(private formBuilder: FormBuilder) {}
ngOnInit() {
this.createForm();
}
createForm(){
this.form = this.formBuilder.group({
people: this.formBuilder.array([
this.createItem()
])
})
}
createItem(){
return this.formBuilder.group({
check: ''
})
}
addItem(){
this.people.push(this.createItem());
}
get people(){
return this.form.get("people") as FormArray;
}
setCheck(checked, index){
this.people.controls[index].get("check").setValue(checked ? 'A' : 'B');
}
submit(){
console.log(this.form.value);
}
}
and one more thing, try to use presentation logic in the component class.
style guide: Style 05-17.
link: https://angular.io/guide/styleguide#put-presentation-logic-in-the-component-class
First, if you create many many checkboxes inside a loop, and you set formControlName to a specific one, every checkbox will be bind to 1 control. (You will check and uncheck all of them with the same click.)
On second, you should use formControlName, in formGroup:
<div [formGroup]="myForm">
...
..*ngFor="let item of myForm.get('people')['controls'];"
...
<mat-checkbox formControlName="check"
[checked]="item.get('check').value === 'A' ? true : false"
(change)="item.get('check').setValue($event.checked ? 'A' : 'B')">
</mat-checkbox>
</div>
On Third, I suggest to use brackets in ngFor and '' to look for control name as string.
[formControlName]="'check'"
And finally, I suggest to use mat-checkbox's (change) event, and call a custom function, and do whatever you want on ts side:
<mat-checkbox [formControlName]="'check'" (change)="someFunction(item, $event)"></mat-checkbox>
And in ts:
someFunction(item, event){
...//Do your black magic here
}
By the way: If you use reactive form, and bind a controls to a checkbox, it's value always will be boolean, cuz it's logic... You should decide to map the value later to 'A' or 'B', but you shouldn't use the same time.
And backwards: The checkbox will always work with true or false, width FormControl.
Think about it: You want a Control to control true or false value, but you force 'A' or 'B' in it... It's illogic.
Please re-think your solution.

Unable to append dynamic-component to the dynamic anchor

I'm new Angular using Angular 6.0.0-beta.
I'm not able to append dynamic-component to the dynamic anchor as explained below: -
I have placed anchor(appContent) like this in html: -
<mat-card>
<mat-card-content>
<mat-tab-group class="tab-group" (selectedTabChange)="onTabChanged($event)">
<mat-tab *ngFor="let obj of tags">
<ng-template mat-tab-label>{{ obj.name }}</ng-template>
<div class="tab-content" appContent>
{{ obj.name }}
</div>
</mat-tab>
</mat-tab-group>
</mat-card-content>
</mat-card>
I'm using below code to append to ContentComponent to the appContent, I'm not adding piece of code which create TagId and Array(Content) Mapping : -
private tags: Array<Tag>;
private currentTag: Tag;
// private contents: Array<Content>;
private tagContentsMap: Map<number, Array<Content>>;
onTabChanged(event: MatTabChangeEvent) {
this.currentTag = this.tags[event.index]; // fetching current tag from the Tags Array
this.loadContentForTag(this.currentTag);
}
private loadContentForTag(tag: Tag) {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(ContentComponent);
const viewContainerRef = this.contentsDirective.viewContainerRef;
viewContainerRef.clear();
const componentRef = viewContainerRef.createComponent(componentFactory);
console.log(tag);
console.log(this.tagContentsMap.get(tag.tagId)); // I can see content array here on tab change
(<ContentComponent>componentRef.instance).contentList = this.tagContentsMap.get(tag.tagId); // On assigning content array here is not reflecting in the HTML of ContentComponent
}
ContentComponent is as below: -
import { Component, OnInit } from '#angular/core';
import { Content } from '../../models/flat/content';
#Component({
selector: 'app-content',
template: `
<p *ngFor="let content of contentList">
{{ content.title }}
</p>
`,
styleUrls: ['./content.component.scss']
})
export class ContentComponent implements OnInit {
contentList: Array<Content>;
constructor() { }
ngOnInit() {
}
}
So now the problem is, tabs getting created dynamically and appContent vary with current Tab. For the First auto selected tab ContentComponent reflects the data in (div class="tab-content" appContent) but on changing tabs it's not reflecting data of the current tab's even though anchor(appContent) is available.
Please help me resolve this.
Thank you in advance.
I think you're complicating this a bit too much. I mean, why are you using dynamic components? You are always adding/removing the same component (ContentComponent). It would be much easier to just inject that component, and bind its contentList property to the content you want to show (I don't know the selector for ContentComponent os I', assuming itscontent`):
<mat-card>
<mat-card-content>
<mat-tab-group class="tab-group" (selectedTabChange)="onTabChanged($event)">
<mat-tab *ngFor="let obj of tags">
<ng-template mat-tab-label>{{ obj.name }}</ng-template>
<div class="tab-content" appContent>
<content [contentList]="tagContent"></content>
{{ obj.name }}
</div>
</mat-tab>
</mat-tab-group>
</mat-card-content>
</mat-card>
Now, in the component:
private tags: Array<Tag>;
private currentTag: Tag;
private contentTag: Array<Content>;
// private contents: Array<Content>;
private tagContentsMap: Map<number, Array<Content>>;
onTabChanged(event: MatTabChangeEvent) {
this.currentTag = this.tags[event.index]; // fetching current tag from the Tags Array
this.tagContent = this.tagContentMap.get(this.currentTag.tagId);
}
As you see, much easier. You can also add an ngIf to the <content ...></content> tag so you can hide that component if tagContent is empty or null (don't know if this is possible)

How to set an Angular2/4 Class Variable via html

I am trying to set an Angular2 Class Variable via html.
This is what I've Tried
The problem is that although the value gets set via
<div class="row" *ngVar="client.clientName as clientName">
It doesn't keep the value on the loop. It's only seems to have a value after the declaration.
Directive
import { Directive, Input, ViewContainerRef, TemplateRef } from '#angular/core';
#Directive({
selector: '[ngVar]',
})
export class VarDirective {
#Input()
set ngVar(context: any) {
this.context.$implicit = this.context.ngVar = context;
this.updateView();
}
context: any = {};
constructor(private vcRef: ViewContainerRef, private templateRef: TemplateRef<any>) {}
updateView() {
this.vcRef.clear();
this.vcRef.createEmbeddedView(this.templateRef, this.context);
}
}
Component
#Component({
selector: 'details',
template: `
<div class="row" *ngVar="'' as clientName">
<ng-template *ngIf="clients.length" ngFor [ngForOf]="clients" let-client>
aaaaaaaaa|{{clientName}}|aaaaaaaa
<div class="row" *ngIf="clientName != client.clientName">
<div>
<div class="row" *ngVar="client.clientName as clientName">
bbbbbbbb|{{clientName}}|bbbbbbbbb
</div>
</div>
</div>
</ng-template>
</div>
`
})
export class ComponentName {
className: string = "";
}
Ok the way I achieved this was
Removing the directive.
Creating a class function to set the variable
setVariable(clientName) {
this.clientName = clientName;
}
Then in the loop I added the function in an interpolation tag
<ng-template *ngIf="clients.length" ngFor [ngForOf]="clients" let-client>
aaaaaaaaa|{{clientName}}|aaaaaaaa
<div class="row" *ngIf="clientName != client.clientName">Hey
<div>
{{setVariable(statesWithClient.client.clientName)}}
bbbbbbbb|{{clientName}}|bbbbbbbbb
</div>
</div>
</ng-template>
The issue I am having now is getting an error:
ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'true'. Current value: 'false'
The Hack
Reading this github I ran across "when changing a component 'non model' value" - simple enough I'll just add the ngmodel.
<input type="hidden" ngModel #clientName />
It Works. no more error
But Now
But now error code no longer detects clientName != statesWithClient.client.clientName.

How to declare a variable in a template in Angular

I have the following template :
<div>
<span>{{aVariable}}</span>
</div>
and would like to end up with :
<div "let a = aVariable">
<span>{{a}}</span>
</div>
Is there a way to do it ?
Update
We can just create directive like *ngIf and call it *ngVar
ng-var.directive.ts
#Directive({
selector: '[ngVar]',
})
export class VarDirective {
#Input()
set ngVar(context: unknown) {
this.context.$implicit = this.context.ngVar = context;
if (!this.hasView) {
this.vcRef.createEmbeddedView(this.templateRef, this.context);
this.hasView = true;
}
}
private context: {
$implicit: unknown;
ngVar: unknown;
} = {
$implicit: null,
ngVar: null,
};
private hasView: boolean = false;
constructor(
private templateRef: TemplateRef<any>,
private vcRef: ViewContainerRef
) {}
}
with this *ngVar directive we can use the following
<div *ngVar="false as variable">
<span>{{variable | json}}</span>
</div>
or
<div *ngVar="false; let variable">
<span>{{variable | json}}</span>
</div>
or
<div *ngVar="45 as variable">
<span>{{variable | json}}</span>
</div>
or
<div *ngVar="{ x: 4 } as variable">
<span>{{variable | json}}</span>
</div>
Plunker Example Angular4 ngVar
See also
Where does Angular 4 define "as local-var" behavior for *ngIf?
Original answer
Angular v4
div + ngIf + let
{{variable.a}}
{{variable.b}}
div + ngIf + as
view
<div *ngIf="{ a: 1, b: 2, c: 3 + x } as variable">
<span>{{variable.a}}</span>
<span>{{variable.b}}</span>
<span>{{variable.c}}</span>
</div>
component.ts
export class AppComponent {
x = 5;
}
If you don't want to create wrapper like div you can use ng-container
view
<ng-container *ngIf="{ a: 1, b: 2, c: 3 + x } as variable">
<span>{{variable.a}}</span>
<span>{{variable.b}}</span>
<span>{{variable.c}}</span>
</ng-container>
As #Keith mentioned in comments
this will work in most cases but it is not a general solution since it
relies on variable being truthy
See update for another approach.
You can declare variables in html code by using a template element in Angular 2 or ng-template in Angular 4+.
Templates have a context object whose properties can be assigned to variables using let binding syntax. Note that you must specify an outlet for the template, but it can be a reference to itself.
<ng-template #selfie [ngTemplateOutlet]="selfie"
let-a="aVariable" [ngTemplateOutletContext]="{ aVariable: 123 }">
<div>
<span>{{a}}</span>
</div>
</ng-template>
<!-- Output
<div>
<span>123</span>
</div>
-->
You can reduce the amount of code by using the $implicit property of the context object instead of a custom property.
<ng-template #t [ngTemplateOutlet]="t"
let-a [ngTemplateOutletContext]="{ $implicit: 123 }">
<div>
<span>{{a}}</span>
</div>
</ng-template>
The context object can be a literal object or any other binding expression. Other valid examples:
<!-- Use arbitrary binding expressions -->
<ng-template let-sum [ngTemplateOutletContext]="{ $implicit: 1 + 1 }">
<!-- Use pipes -->
<ng-template let-formatPi [ngTemplateOutletContext]="{ $implicit: 3.141592 | number:'3.1-5' }">
<!-- Use the result of a public method of your component -->
<ng-template let-root [ngTemplateOutletContext]="{ $implicit: sqrt(2116) }">
<!--
You can create an alias for a public property of your component:
anotherVariable: number = 123;
-->
<ng-template let-aliased [ngTemplateOutletContext]="{ $implicit: anotherVariable }">
<!--
The entire context object can be bound from a public property:
ctx: { first: number, second: string } = { first: 123, second: "etc" }
-->
<ng-template let-a="first" let-b="second" [ngTemplateOutletContext]="ctx">
Ugly, but:
<div *ngFor="let a of [aVariable]">
<span>{{a}}</span>
</div>
When used with async pipe:
<div *ngFor="let a of [aVariable | async]">
<span>{{a.prop1}}</span>
<span>{{a.prop2}}</span>
</div>
update 3
Issue https://github.com/angular/angular/issues/2451 is fixed in Angular 4.0.0
See also
https://github.com/angular/angular/pull/13297
https://github.com/angular/angular/commit/b4db73d
https://github.com/angular/angular/issues/13061
update 2
This isn't supported.
There are template variables but it's not supported to assign arbitrary values. They can only be used to refer to the elements they are applied to, exported names of directives or components and scope variables for structural directives like ngFor,
See also https://github.com/angular/angular/issues/2451
Update 1
#Directive({
selector: '[var]',
exportAs: 'var'
})
class VarDirective {
#Input() var:any;
}
and initialize it like
<div #aVariable="var" var="abc"></div>
or
<div #aVariable="var" [var]="'abc'"></div>
and use the variable like
<div>{{aVariable.var}}</div>
(not tested)
#aVariable creates a reference to the VarDirective (exportAs: 'var')
var="abc" instantiates the VarDirective and passes the string value "abc" to it's value input.
aVariable.var reads the value assigned to the var directives var input.
I would suggest this: https://medium.com/#AustinMatherne/angular-let-directive-a168d4248138
This directive allow you to write something like:
<div *ngLet="'myVal' as myVar">
<span> {{ myVar }} </span>
</div>
Here is a directive I wrote that expands on the use of the exportAs decorator parameter, and allows you to use a dictionary as a local variable.
import { Directive, Input } from "#angular/core";
#Directive({
selector:"[localVariables]",
exportAs:"localVariables"
})
export class LocalVariables {
#Input("localVariables") set localVariables( struct: any ) {
if ( typeof struct === "object" ) {
for( var variableName in struct ) {
this[variableName] = struct[variableName];
}
}
}
constructor( ) {
}
}
You can use it as follows in a template:
<div #local="localVariables" [localVariables]="{a: 1, b: 2, c: 3+2}">
<span>a = {{local.a}}</span>
<span>b = {{local.b}}</span>
<span>c = {{local.c}}</span>
</div>
Of course #local can be any valid local variable name.
In case if you want to get the response of a function and set it into a variable, you can use it like the following in the template, using ng-container to avoid modifying the template.
<ng-container *ngIf="methodName(parameters) as respObject">
{{respObject.name}}
</ng-container>
And the method in the component can be something like
methodName(parameters: any): any {
return {name: 'Test name'};
}
If you need autocomplete support from within in your templates from the Angular Language Service:
Synchronous:
myVar = { hello: '' };
<ng-container *ngIf="myVar; let var;">
{{var.hello}}
</ng-container>
Using async pipe:
myVar$ = of({ hello: '' });
<ng-container *ngIf="myVar$ | async; let var;">
{{var.hello}}
</ng-container>
A simple solution that worked for my requirement is:
<ng-container *ngIf="lineItem.productType as variable">
{{variable}}
</ng-container>
OR
<ng-container *ngIf="'ANY VALUE' as variable">
{{variable}}
</ng-container>
I am using Angular version: 12. It seems it may work with other version as well.
I liked the approach of creating a directive to do this (good call #yurzui).
I ended up finding a Medium article Angular "let" Directive which explains this problem nicely and proposes a custom let directive which ended up working great for my use case with minimal code changes.
Here's the gist (at the time of posting) with my modifications:
import { Directive, Input, TemplateRef, ViewContainerRef } from '#angular/core'
interface LetContext <T> {
appLet: T | null
}
#Directive({
selector: '[appLet]',
})
export class LetDirective <T> {
private _context: LetContext <T> = { appLet: null }
constructor(_viewContainer: ViewContainerRef, _templateRef: TemplateRef <LetContext <T> >) {
_viewContainer.createEmbeddedView(_templateRef, this._context)
}
#Input()
set appLet(value: T) {
this._context.appLet = value
}
}
My main changes were:
changing the prefix from 'ng' to 'app' (you should use whatever your app's custom prefix is)
changing appLet: T to appLet: T | null
Not sure why the Angular team hasn't just made an official ngLet directive but whatevs.
Original source code credit goes to #AustinMatherne
For those who decided to use a structural directive as a replacement of *ngIf, keep in mind that the directive context isn't type checked by default. To create a type safe directive ngTemplateContextGuard property should be added, see Typing the directive's context. For example:
import { Directive, Input, TemplateRef, ViewContainerRef } from '#angular/core';
#Directive({
// don't use 'ng' prefix since it's reserved for Angular
selector: '[appVar]',
})
export class VarDirective<T = unknown> {
// https://angular.io/guide/structural-directives#typing-the-directives-context
static ngTemplateContextGuard<T>(dir: VarDirective<T>, ctx: any): ctx is Context<T> {
return true;
}
private context?: Context<T>;
constructor(
private vcRef: ViewContainerRef,
private templateRef: TemplateRef<Context<T>>
) {}
#Input()
set appVar(value: T) {
if (this.context) {
this.context.appVar = value;
} else {
this.context = { appVar: value };
this.vcRef.createEmbeddedView(this.templateRef, this.context);
}
}
}
interface Context<T> {
appVar: T;
}
The directive can be used just like *ngIf, except that it can store false values:
<ng-container *appVar="false as value">{{value}}</ng-container>
<!-- error: User doesn't have `nam` property-->
<ng-container *appVar="user as user">{{user.nam}}</ng-container>
<ng-container *appVar="user$ | async as user">{{user.name}}</ng-container>
The only drawback compared to *ngIf is that Angular Language Service cannot figure out the variable type so there is no code completion in templates. I hope it will be fixed soon.
With Angular 12 :
<div *ngIf="error$ | async as error">
<span class="text-warn">{{error.message}}</span>
</div>
I am using angular 6x and I've ended up by using below snippet.
I've a scenerio where I've to find user from a task object. it contains array of users but I've to pick assigned user.
<ng-container *ngTemplateOutlet="memberTemplate; context:{o: getAssignee(task) }">
</ng-container>
<ng-template #memberTemplate let-user="o">
<ng-container *ngIf="user">
<div class="d-flex flex-row-reverse">
<span class="image-block">
<ngx-avatar placement="left" ngbTooltip="{{user.firstName}} {{user.lastName}}" class="task-assigned" value="28%" [src]="user.googleId" size="32"></ngx-avatar>
</span>
</div>
</ng-container>
</ng-template>
I was trying to do something similar and it looks like this has been fixed in newer versions of angular.
<div *ngIf="things.car; let car">
Nice {{ car }}!
</div>
<!-- Nice Honda! -->
Short answer which help to someone
Template Reference variable often reference to DOM element within a
template.
Also reference to angular or web component and directive.
That means you can easily access the varible anywhere in a template
Declare reference variable using hash symbol(#)
Can able to pass a variable as a parameter on an event
show(lastName: HTMLInputElement){
this.fullName = this.nameInputRef.nativeElement.value + ' ' + lastName.value;
this.ctx.fullName = this.fullName;
}
*However, you can use ViewChild decorator to reference it inside your component.
import {ViewChild, ElementRef} from '#angular/core';
Reference firstNameInput variable inside Component
#ViewChild('firstNameInput') nameInputRef: ElementRef;
After that, you can use this.nameInputRef anywhere inside your Component.
Working with ng-template
In the case of ng-template, it is a little bit different because each template has its own set of input variables.
https://stackblitz.com/edit/angular-2-template-reference-variable
I'm the author of https://www.npmjs.com/package/ng-let
Structural directive for sharing data as local variable into html component template.
Source code:
import { Directive, Input, TemplateRef, ViewContainerRef } from '#angular/core';
interface NgLetContext<T> {
ngLet: T;
$implicit: T;
}
#Directive({
// tslint:disable-next-line: directive-selector
selector: '[ngLet]'
})
export class NgLetDirective<T> {
private context: NgLetContext<T | null> = { ngLet: null, $implicit: null };
private hasView: boolean = false;
// eslint-disable-next-line no-unused-vars
constructor(private viewContainer: ViewContainerRef, private templateRef: TemplateRef<NgLetContext<T>>) { }
#Input()
set ngLet(value: T) {
this.context.$implicit = this.context.ngLet = value;
if (!this.hasView) {
this.viewContainer.createEmbeddedView(this.templateRef, this.context);
this.hasView = true;
}
}
/** #internal */
public static ngLetUseIfTypeGuard: void;
/**
* Assert the correct type of the expression bound to the `NgLet` input within the template.
*
* The presence of this static field is a signal to the Ivy template type check compiler that
* when the `NgLet` structural directive renders its template, the type of the expression bound
* to `NgLet` should be narrowed in some way. For `NgLet`, the binding expression itself is used to
* narrow its type, which allows the strictNullChecks feature of TypeScript to work with `NgLet`.
*/
static ngTemplateGuard_ngLet: 'binding';
/**
* Asserts the correct type of the context for the template that `NgLet` will render.
*
* The presence of this method is a signal to the Ivy template type-check compiler that the
* `NgLet` structural directive renders its template with a specific context type.
*/
static ngTemplateContextGuard<T>(dir: NgLetDirective<T>, ctx: any): ctx is NgLetContext<Exclude<T, false | 0 | '' | null | undefined>> {
return true;
}
}
Usage:
import { Component } from '#angular/core';
import { defer, Observable, timer } from 'rxjs';
#Component({
selector: 'app-root',
template: `
<ng-container *ngLet="timer$ | async as time"> <!-- single subscription -->
<div>
1: {{ time }}
</div>
<div>
2: {{ time }}
</div>
</ng-container>
`,
})
export class AppComponent {
timer$: Observable<number> = defer(() => timer(3000, 1000));
}
Try like this
<ng-container
[ngTemplateOutlet]="foo"
[ngTemplateOutletContext]="{ test: 'Test' }"
></ng-container>
<ng-template #foo let-test="test">
<div>{{ test }}</div>
</ng-template>
original answer by #yurzui won't work startring from Angular 9 due to - strange problem migrating angular 8 app to 9.
However, you can still benefit from ngVar directive by having it and using it like
<ng-template [ngVar]="variable">
your code
</ng-template>
although it could result in IDE warning: "variable is not defined"
It is much simpler, no need for anything additional. In my example I declare variable "open" and then use it.
<mat-accordion class="accord-align" #open>
<mat-expansion-panel hideToggle="true" (opened)="open.value=true" (closed)="open.value=false">
<mat-expansion-panel-header>
<span class="accord-title">Review Policy Summary</span>
<span class="spacer"></span>
<a *ngIf="!open.value" class="f-accent">SHOW</a>
<a *ngIf="open.value" class="f-accent">HIDE</a>
</mat-expansion-panel-header>
<mat-divider></mat-divider>
<!-- Quote Details Component -->
<quote-details [quote]="quote"></quote-details>
</mat-expansion-panel>
</mat-accordion>