Elvis Operator (?) on [ngModel] with Dynamic Parameter Name not Working - html

I have a data object obj with multiple parameters:
export interface obj {
param1: number;
param2: number;
param3: number;
}
I would like to dynamically add mat-grid-tiles with mat-form-fields displaying their value.
I do this by creating an array of the parameter names:
params = ['param1', 'param2', 'param3']
Then, in my HTML code, I use my mat-grid-tiles with *ngFor on the parameter names and then assign the reference to my obj in [ngModel]:
<mat-grid-tile [colspan]=1 *ngFor="let param of params">
<mat-form-field>
<input matInput [ngModel]="coilData[params]" readonly>
</mat-form-field>
</mat-grid-tile>
It works! However, because my obj is initially null until an API call is made, it seems to throw hundreds of null errors like so:
ERROR TypeError: Cannot read property 'param1' of null
I realized that I can just use the Elvis Operator: ?, however I can not seem to figure out how to use this on [ngModel] when I am dynamically assigning parameter names to it like this: [ngModel]="obj[param]" instead of the usual [ngModel]="obj?.param1.
Is it possible to use the Elvis Operator when dynamically applying parameter names to ngModel?

Ended up taking Heretic Monkey's suggestion and used obj ? obj[param] : null instead which ended up working for me.

Try
< ng-container *ngIf="coilData" >
<mat-grid-tile [colspan]=1 *ngFor="let param of params">
<mat-form-field>
<input matInput [ngModel]="coilData[params]" readonly>
</mat-form-field>
</mat-grid-tile>
< ng-container />

Related

Conditional Angular Material Error Message not working?

I am trying to display a mat error depending on the type of validator met. One validator being required (will display if field is touched and left empty) and another maxLength (which will display if the field has been touched and over 20 characters have been entered) - each scenario should display a different mat error message.
Firstly, I have my form group itself (in the component.ts), with the form control and its respective validators:
this.addBranchDetailsForm = this.fb.group({
branchName: ['', Validators.required, Validators.maxLength(20)]
});
Secondly, inside the component.html file, I have the Mat Form Field which makes reference to the aforementioned form group and control:
<form [formGroup]="addBranchDetailsForm">
<mat-form-field hintLabel="Enter the new branch name">
<mat-label>Branch Name</mat-label>
<input matInput #input maxlength="30" formControlName="branchName">
<mat-hint align="end">{{input.value?.length || 0}}/30</mat-hint>
<mat-error *ngIf="branchName">{{formOneErrorMessage()}}</mat-error>
</mat-form-field>
</form>
And then lastly, i have the "formOneErrorMessage" method in my component.ts file, which is responsible for the conditional error handling:
formOneErrorMessage() {
if (this.addBranchDetailsForm.get('branchName')?.hasError('required')) {
return 'Enter new branch name';
}
else if (this.addBranchDetailsForm.get('branchName')?.hasError('maxLength')) {
return 'The new branch name may not be more then 20 characters';
}
}
The issues I have are as follows:
Within the component.html file, with regards to the reference made to the "branchName" within the ngIf of the mat error, it says "Property 'branchName' does not exist on type 'CreateBranchComponent'.ngtsc(2339)".
Similarly, with reference made to the "formOneErrorMessage()" method within the mat error itself, it says "Property 'formOneErrorMessage' does not exist on type 'CreateBranchComponent'.ngtsc(2339)".
Lastly. Looking at the "formOneErrorMessage()" method within the component.ts file, the returns are throwing errors as well, stating: "Type 'string' is not assignable to type 'void'.ts(2322):"...
My suspicion is that I am not referencing the branchName form control correctly in the "formOneErrorMessage()" method.. Although, otherwise I am quite unsure.
I ended up using the following (in component.ts):
public errorHandling = (control: string, error: string) => {
return this.addBranchDetailsForm.controls[control].hasError(error);
}
And then within the component.html:
<mat-form-field hintLabel="Enter the new branch name">
<mat-label>Branch Name</mat-label>
<input matInput #input maxlength="30" formControlName="branchName">
<mat-hint align="end">{{input.value?.length || 0}}/30</mat-hint>
<mat-error *ngIf="errorHandling('branchName','required')">
Branch Name may not be empty
</mat-error>
<mat-error *ngIf="errorHandling('branchName','maxlength')">
Branch Name may not be more than 20 characters
</mat-error>
</mat-form-field>

Angular10 - [(ngModel)] and (ngModelChange) don't read the value after it's saved to db

Based on what it is selected on this field, I want to fill with data the Tasks field below.
<mat-form-field>
<mat-label>Position</mat-label>
<mat-select [(ngModel)]="position" (ngModelChange)="change(position)" multiple formControlName="position">
<mat-option *ngFor="let position of positionArray" [value]="position">
{{position}}
</mat-option>
</mat-select>
</mat-form-field>
This is the Tasks field:
<mat-form-field>
<mat-label>Tasks</mat-label>
<mat-select formControlName="tasks">
<mat-option *ngFor="let res of tasksItems" [value]="res">
{{res}}
</mat-option>
</mat-select>
</mat-form-field>
Typescript code is as below:
private tasksItems= [];
change(position) {
if (position == "IT") {
this.tasksItems= ["one", "two"];
}
else if (position == "Design") {
this.tasksItems= ["three", "four"];
}
else {
this.tasksItems= [];
}
Edit: This is the positionArray in ts. The values are stored in database and the change(position) method works fine. The problem is the field Tasks doesn't get the value that is stored and I am assuming it has something to do with [(ngModel)].
positionArray: string[] = ['IT', 'Design'];
Data is stored to database. But the problem is [(ngModel)] doesn't read the data (when I want to edit the field). Can someone explain to me why and how do I fix it?
You shouldn't need to use ngModel if you are already using formControlName.
I can't see the whole code but I assume you have something like [formGroup]="form" on the form element which would bind to the FormGroup in your ts file.
As long as your form.position form control is updated, it should reflect in your template.
If you are using reactive form controls try removing [(ngModel)]="position" and make sure the form control is set correctly in your ts file.
Edit
Also, try changing your template variable names. position seems to be used for different variables.
try:
<mat-option *ngFor="let pos of positionArray" [value]="pos">
{{pos}}
</mat-option>

How to get drop down text in mat-select using angular 6

Here is is my code HTML:
<mat-list-item class="primary-imenu-item" role="listitem">
<mat-select class="form-control" multiple formControlName="statusCode" (selectionChange)="getSelectedOptionText($event)">
<mat-option *ngFor="let list of statusList" [value]="list.id" checkboxPosition="before">
{{list.code}}
</mat-option>
</mat-select>
</mat-list-item>
TS Code:
getSelectedOptionText(event: MatSelectChange) {
let selectedData = {
code: (event.source.selected as MatOption).viewValue,
value: event.source.value
};
console.log(selectedData);
}
In that 'selectedData' I am getting the code value is undefined. I need to get code value from dropdown.
The event object has "value" array attribute which will always have one item. So, instead of (event.source.selected as MatOption).viewValue, you can do event.value[0]. Look at the screenshot for more details. Hope it helps.

How can I get the an reference to an mat-option object in material auto complete component

I am trying to replace boostrap's combobox with material's autocomplete without refactoring much of the previous code;
This is what the html of the new material autocomplete looks like :
<input matInput placeholder="New room" aria-label="Newsroom Source" [matAutocomplete]="autoGroup" [formControl]="newsRoomCtrl" [value] = 'selectedNewsFeed.sourceName'>
<mat-autocomplete #autoGroup="matAutocomplete" (optionSelected)='onNewsRoomSelect($event.value)' >
<mat-option *ngFor="let newsroomsource of filteredNewsRoomSources | async" [value]="newsroomsource" [attr.data-row]="newsroomsource">
<span>{{ newsroomsource.sourceName }}</span> |
<small>Type: {{ newsroomsource.sourceType }}</small>
</mat-option>
</mat-autocomplete>
On '(optionSelected)' event I call the function onNewsRoomSelect(selectedNewsRoom: NewsRoomSource) by passing the selected newsroomsource as an object using event.value
This works fine ,the problem though is that the value of the input becomes an object in this case and NewsRoomSource object, and this is what I see in the input after the selection is made;
Selecting:
After selection :
Now I understand why this happens as I set [value] of the mat-input tag to an object i.e. newsroomsource, but I don't know how else I can reference the selected object if I don't do this.
As you can see in the code I tried using data-attribute but wasn't sure how exactly I can use it in this case
In this case newsRoomCtrl has the reference of the selected object try by printing this variable console.log(newsRoomCtrl);
Finally I think it is going to be like that, take a look to the object structure to be sure.
<input matInput placeholder="New room" aria-label="Newsroom Source" [matAutocomplete]="autoGroup"
[formControl]="newsRoomCtrl" [value] = 'newsRoomCtrl.value.sourceName'>

Passing variables into function on (valueChange) (TypeScript, Angular)

I'm new to Angular and html, so I'm looking for help. I have this code:
<mat-form-field>
<mat-select (valueChange)="changeStatus(list.name, card.name)">
<mat-option *ngFor="let i of lists"> {{i.name}} </mat-option>
</mat-select>
</mat-form-field>
What I need to do, is to pass {{i.name}} (instead of list.name) into changeStatus() function.
Basically what happens, is when I choose option in drop-box, I want to pass option I chose into function. Any ideas on how to do that? Thanks in advice
You can use onSelectionChange provided by MatSelect component and pass the $event variable.
Taken from the documentation: selectionChange: EventEmitter<MatSelectChange>
MatSelectChange has two properties:
source: MatSelect
value: any
You have to change:
<mat-select (valueChange)="changeStatus(list.name, card.name)">
To:
<mat-select (selectionChange)="onSelectionChange($event)">
And on your component.ts you can handle the event like this:
onSelectionChange($event: MatSelectChange) {
const list = $event.value; // const or let depending on your handle logic
// Your logic here
}
PS: 80% of the times simple tasks that you look for in Angular Material are straight forward. Make sure you check the API + Examples so you can learn the "Angular Way" of doing stuff.
It is the actual purpose of Angular Material "Our goal is to build a set of high-quality UI components built with Angular and TypeScript, following the Material Design spec. These components will serve as an example of how to write Angular code following best practices."
Use selectionChange event with Template reference variables for it
<mat-form-field>
<mat-select #ref (selectionChange)="changeStatus(ref.value)">
<mat-option *ngFor="let i of lists" [value]="i"> {{i.name}} > </mat-option>
</mat-select>
</mat-form-field>
Component
changeStatus(value)
{
console.log(value);
}
LIVE DEMO