I am trying to pass a dictionary into a function like so:
<ng-template class="example-container" ngSwitchCase="twoTextFields">
<mat-form-field appearance="outline">
<mat-label>{{field.firstFieldName}}</mat-label>
<input matInput (change)="onUpdate(field.db_name, {[field.first_db_name]: $event})">
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{field.secondFieldName}}</mat-label>
<input matInput (change)="onUpdate(field.db_name, {[field.second_db_name]: $event})">
</mat-form-field>
</ng-template>
It gives me many syntax errors similar to this:
Parser Error: Missing expected ) at column 26 in [onUpdate(field.db_name, {[field.first_db_name]: $event})] in #14:43 (" <mat-label>{{field.firstFieldName}}</mat-label>
<input matInput (change)="[ERROR ->]onUpdate(field.db_name, {[field.first_db_name]: $event})">
</mat-form-field>
<mat-form-f"): #14:43
Is it possible to pass a dictionary as done above or not? If it is could I have some guidance on how it would be possible, otherwise it would also help to know if it is not possible! I am new to Angular so anything helps!
I'm trying to translate over some code I had from React and it would be much simpler if I didn't have to rewrite the change function. I also tried using ngModelChange and I receive the same error.
Thank you!
Unfortunately, this triggers a syntax error. The main reason for this is that "[]" is used as a special character(s) in angular directives / templates.
You can do this instead:
<mat-form-field appearance="outline">
<mat-label>{{field.firstFieldName}}</mat-label>
<input matInput (change)="onUpdate(field.db_name, convertToDict(field.first_db_name, $event))">
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>{{field.secondFieldName}}</mat-label>
<input matInput (change)="onUpdate(field.db_name, convertToDict(field.second_db_name, $event))">
</mat-form-field>
Then on your code behind, you can create the helper function like so (in your app.component.ts):
export class AppComponent {
... truncated code . ..
convertToDict(key: any, value: any): any {
return {[key]: value}
}
onUpdate(fieldName: string, eventDict: any) {
console.log(fieldName);
console.log(eventDict);
}
... truncated code ...
}
This is a workaround if you still would want to have a dictionary of your data. We did it by using a simple helper / converter function.
Related
I have an autocomplete field that behaves correctly on the frontend, but it's sending the entire object to the backend due to [value]="option". When I change it to [value]="option.id" the form submits correctly, however this breaks the [displayWith] function once an option is selected. The input is blank due to the fact displayWith uses the object to update the input.
What Can I modify to send the correct id to the backend while preserving the display function? I tried sending the expenseCategory through displayWith as a parameter, but it's not in the for block, therefore I don't have access to the option object. One thing to note, these are dynamically created inputs as part of a formArray.
Heres the autocomplete field:
<mat-form-field appearance="outline" class="full-width-input">
<mat-label>Expense Category</mat-label>
<input type="text" aria-label="Assignee" matInput formControlName = "expense_category_id" [matAutocomplete]="auto">
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayExpenseCategoryFn">
<mat-option *ngFor="let option of filteredExpenseCategories[i] | async" [value]="option">
{{option.category}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
Here's the displayWith function:
displayExpenseCategoryFn(expenseCategory?: ExpenseCategory): string | undefined {
return expenseCategory ? expenseCategory.category : undefined;
}
First, I'm sure this is a typo, but what is i, in:
let option of filteredExpenseCategories[i]
Secondly, you don't need a displayWith function if you also are defining a template, which you are with the binding {{option.category}}.
So you could just go back to [value]="option.id", which is the easiest way IMHO.
If you are submitting your form via code, then you can always massage the data before it goes off to the gods.
You could also make use of the (optionSelected) event of the MatAutoComplete (https://material.angular.io/components/autocomplete/api):
public selectOption(e: MatAutocompleteSelectedEvent): void {
const myRealObject = e.option.value as MyRealObject;
// do anything else you need here, such as tweak the data or clearout the typeahead control
}
<mat-autocomplete #myAutoComplete (optionSelected)="selectOption($event)">
<mat-option *ngFor="let option of options" [value]="option.id">
{{option.name}}
</mat-option>
</mat-autocomplete>
Hope this helps.
Ok. I figured it out from a previous project I did a couple years ago (thought I solved this before.)
So what I did was bind displayExpenseCategoryFn to the controller, so I could search through the expenseCategories array to extract the category name and display that in the input field.
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayExpenseCategoryFn.bind(this)">
<mat-option *ngFor="let option of filteredExpenseCategories[i] | async" [value]="option.id">
{{option.category}}
</mat-option>
</mat-autocomplete>
I then made the following changes to the displayWith function.
displayExpenseCategoryFn(expenseCategoryId?: number): string | undefined {
const index = this.expenseCategories.findIndex(expenseCategoryIndex => expenseCategoryIndex.id === expenseCategoryId);
return expenseCategoryId ? this.expenseCategories[index].category : undefined;
}
I know that using function calls in Angular templates is a bad practice. In short it is because Change Detection will make the function run many times, which will lead to bad performance. (This article goes more into depth on the topic)
I have been following this rule closely except from one exception, and that is when I need the value from my Reactive Form in my template. Usually I use it like this:
My way of doing it
Template: <div *ngIf="myFormGroup.get('name').invalid">Filling out name is required</div>
Here I am not following the rule because I am calling the function myFormGroup.get('name') inside my template. To solve this I decided to go to the Angular documentation to see how they do this, and I found a similar example here: https://angular.io/guide/form-validation#built-in-validator-functions.
What the Angular documentation does is that they put the form control in a getter, and then they use the getter in the template, like this:
Angular documentation way of doing it
Component class: get name() { return this.myFormGroup.get('name'); }
Template: <div *ngIf="name.invalid">Filling out name is required</div>
My questions are
Is it ok to use "myFormGroup.get('name')" in a template, even though it breaks the rule of not having function calls in the template?
Is the Angular Documentation way of doing this any different from my way of doing it when it comes to performance? (My understanding is that using a getter this way does noe solve the Change Detection Performance issue)
You can do like this:
In Template:
<input type="text" id="name" formControlName="name" />
<div *ngIf="isControlInvalid('name')">
Something
</div>
In Component:
isControlInvalid(controlName: string): boolean {
const control = this.myForm.controls[controlName];
const result = control.invalid && control.touched;
return result;
}
I have this issue
Cannot read property 'first_name' of undefined
This is my HTML File checkout.html
<ion-content>
<ion-item>
<ion-label>First Name</ion-label>
<ion-input type="text" [(ngModel)]="newOrder.billing.first_name"></ion-input>
</ion-item>
....
</ion-content>
and this is my ts file checkout.ts
this.storage.get("userLoginInfo").then((userLoginInfo) => {
this.userInfo = userLoginInfo.user;
let email = userLoginInfo.user.email;
let id = userLoginInfo.user.id;
this.WooCommerce.getAsync("customers/email/"+email).then((data) => {
this.newOrder = JSON.parse(data.body).customer;
})
})
and this is an image of the error
It seems that you are loading the newOrder asynchronous.
As a result your page get rendered, but the async-task is not finished yet.
You wrote that you declared this.newOrder.billing = {} in the constructor.
Because of that, he now tries to read newOrder.billing.first_name in your html.
newOrder.billing is an empty object, so there is no first_name there.
Only when the async-task has finished, this data will be existent. But currently Angular throws an error before that could happen.
There are two very common ways to handle a situation like that.
You could tell the template that it should NOT render the part with the missing data
<ion-item *ngIf="newOrder.billing">
<ion-label>First Name</ion-label>
<ion-input type="text" [(ngModel)]="newOrder.billing.first_name"></ion-input>
</ion-item>
Then the *ngIf will see that the variable is undefined and that part of the template won´t be shown in the DOM => no error :-)
As soon as the async-task finishes, the variable is not undefined anymore and the template-part will be shown.
Another way (more common when you just want to show data) is using
{{ newOrder.billing.first_name | async }}
The async-pipe will also make sure that Angular could handle that one.
In combination with [ngModel] i think the async-pipe will not work. But it may be worth a try.
warm regards
Just add a quotemark, i think it will help.
<ion-input type="text" [(ngModel)]="newOrder?.billing.first_name"></ion-input>
You can use simply a quote mark with the object in your interpolation as said by
#Kraken.
But two solution provided by the #JanRecker:
if you use async pipe in your interpolation it won't work, because this works with Observables rxjs.
So, adding a quote mark really a helpful option.
I'm running into an issue where I can access locally declared variables in the component controller instantiating the mat-autocomplete. The problem I'm facing is the local variables are stuck in this scope and I can't update them.
Any ideas or thoughts on updating the mat-autocomplete scope variables.
Ultimately what I'm doing is concatenating the display string and a variable bound to the input model. This is giving me an autocomplete input that adds helper text for the user, ideally the text is up to date with clearing the input. The text is currently continuously concatenating, creating unusable text pretty quickly
html
<input
[(ngModel)]="filter>
mat-autocomplete
#auto="matAutocomplete"
[displayWith]="displayFn">
<mat-option
*ngFor="let option of filteredOptions | async"
[value]="option">
{{ option }}
</mat-option>
</mat-autocomplete>
component.ts
displayFn(search): string | undefined {
if(!search) return; //check if the search isn't already populated
if(!search.match(/(=|\*)/)){
if(this.filter){
this.filter += ' ' + search + '==*term*';
}else{
this.filter = search +'==*term*';
}
return this.filter; //this isn't persisting across the lifecycle
}
}
You have two options, the first one is just calling [displayWith]="displayFn.bind(this)" which looks weird in the Angular world, but I can confirm that it works (although I got an Error on my WebStorm saying ng: Unknow Method bind)
And the second one is to use an arrow function in order to preserve the context.
Something like this:
displayFn(offer?: Offer): string | undefined {
return offer && offer.name == this.currentOffer.name ? offer.name : undefined;
}
displayFnWrapper() {
return (offer) => this.displayFn(offer);
}
And in the template:
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFnWrapper()" (optionSelected)='assign($event.option.value)'>
<mat-option *ngFor="let offer of filteredOffers$ | async" [value]="offer">{{ offer.name }}</mat-option>
</mat-autocomplete>
If I use an example, MyClass, where
#Input() modeCity = false;
in ngOnInit() I can access the modeCity and change it. It is reflected over other methods in the class.
in HTML,
<mat-autocomplete #auto="matAutocomplete" autoActiveFirstOption
[displayWith]="itemDisplayFn" (optionSelected)="selected($event)">
then for method itemDisplayFn(item: ..) in the ts file, the modeCity is undefined.
I found that somehow the method itemDisplayFn() has static context. Therefore I created the property,
static staticModeCity = false;
staticModeCity can be set in the ngOnInit() like so,
MyClass.staticModeCity = true
and used in the Method itemDisplayFn() like so,
if(MyClass.staticModeCity) ....
I do not know why this is. Of course static can be conflicting, if the same component is used multiple times in the same parent component.
I have written a simple code to demonstrate ngTemplate
<div> <ng-container [ngTemplateOutlet] ="template1"> </ng-container></div>
and here are the template
<ng-template #template1> This is 1st template </ng-template>
<ng-template #template2>This is 2nd template </ng-template>
It is working as expected, but my problem is I am not able to pass dynamic value to [ngTemplateOutlet]
I want to achieve something like this
<div> <ng-container [ngTemplateOutlet] ="selectTemplate"></ng-container> </div>
Which will either select template1 or template2.
Whenever I am passing some dynamic value which I have defined in my typescript file it gives is error
ERROR TypeError: templateRef.createEmbeddedView is not a function
My typescript file contains this code
if(some condition) {
this.selectTemplate = "template1";
} else {
this.selectTemplate = "template2";
}
In your first example, you're binding ngTemplateOutlet to the value of the expression template1, which is the first ngTemplate element. So far so good.
In your second example, you're binding ngTemplateOutlet to the value of the expression selectTemplate, which is not a template element - you've set it to be a string!
To fix this, you first need to add the template references to your component as fields using the ViewChild annotation:
#ViewChild('template1') template1;
#ViewChild('template2') template2;
Then set selectTemplate to refer to the element itself rather than its name:
if (some condition) {
this.selectTemplate = this.template1;
}
else {
this.selectTemplate = this.template2;
}
Note that the ViewChild variables will not be set until the component's view has been initialized - you can use the ngViewAfterInithook to wait for that to happen.
update Angular 5
ngOutletContext was renamed to ngTemplateOutletContext
See also https://github.com/angular/angular/blob/master/CHANGELOG.md#500-beta5-2017-08-29
original
You can pass a context
<ng-container [ngTemplateOutlet]="template1; context: {foo: 'foo', bar: 'bar', $implicit: 'implit' }"
In the template you can use it like
<ng-template #template1 let-foo1="foo" let-foo2="bar" let-item>
This is 1st template
<div>foo1: {{foo1}}</div>
<div>foo2: {{foo2}}</div>
<div>foo1: {{item}}</div>
</ng-template>
I hope the names I used are not too confusing. I tried to make the distinction between
the name as the value is passed to the context
the name as it is used inside the template
$implicit which doesn't require ="somename" in the template variable definintion (let-item)