Angular modular object property binding to ngModel - javascript

I have an object with 1 set properties and X modular ones which are added depending on at array, the array elements are the object names. This works perfectly to display the elements in my table. My issue comes when I want to bind the textboxes to my model, as I am getting an error saying the object properties does not exist. I have included an *ngIf to check if the property is there and in theory it finds it.
This is part of my HTMl code trying to use one of the modular property names, I have used one which I know will exists to simplify the debugging, next step will be to change this to be fully modular:
<ng-container *ngIf="newWaste[0].hasOwnProperty('Metal')">
<ng-container [matColumnDef]="column" *ngFor="let column of displayedColumns | slice:2 let i = index">
<th mat-header-cell *matHeaderCellDef>{{ displayedColumns[i+2] }}</th>
<td mat-cell *matCellDef="let emp">
<input type="text" matInput placeholder="" value="{{ emp[column] }}" [(ngModel)]="newWaste[0].Metal" />
</td>
</ng-container>
</ng-container>
I have also tried counting the number of properties instead of directly checking if it exists, no difference neither.
This is my error:
src/app/pages/waste/containers/waste-page/waste-page.component.html:33:132 - error TS2339: Property 'Metal' does not exist on type '{ measuredDate: Date; }'.
33 <input type="text" matInput placeholder="" value="{{ emp[column] }}" [(ngModel)]="newWaste[0].Metal" />
~~~~~
src/app/pages/waste/containers/waste-page/waste-page.component.ts:19:16
19 templateUrl: './waste-page.component.html',
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Error occurs in the template of component WastePageComponent.
I create the object with Date parameters, the rest added later.
Binding to Date works perfectly.
Any suggestions?

I create the object with Date parameters, the rest added later
Here's the problem. Initialize newWaste with Metal property already there (no problem if it's initialized to undefined), and probably adjust the TS typing of newWaste so Metal property is non-optional. Angular objects to ngModel pointing at a property which potentially doesn't exist.

Thinking about the whole initialization issue, I have tried to initialize an empty array, instead of an array with the object including the "measuredDate" property.
So instead of this:
public newWaste = [{measuredDate: Date}];
I am just using this, and add all properties in the code:
public newWaste = [];
ngOnInit(): void {
//add the measureDate property
this.newWaste.push({measuredDate: new Date()});
//in other methods I add the more specific properties such as Metal
}
It workpes perfectly the way like that.

Related

How to loop over object properties with ngFor in Angular app

I can write data by keyValuePipe or {{data | JSON}} but I think that is not good solution for me. How I can write data differently? How I use for this example Object.entries, Object.keys or Object.value?
Code with JSON in Stackblitz: https://stackblitz.com/edit/angular-ivy-jn8snh?file=src/app/app.component.ts
Your object is considered as a dictionary in javascript.
You can loop over properties of your object as follow:
<div *ngFor="let property of data">
<p>{{ property }} - {{ data[property] }}</p>
</div>
You can't to do that out of the box. NgForOf allows iterating over a collection, and by default object does not implement required methods and properties. In theory you could get the keys of the object by writing something like this:
<p *ngFor="let property of Object.keys(object)">
{{property}} {{object[property]}}
</p>
But the way the contents of ngFor are evaluated, the global variable Object is not present in the scope. So you're best off writing a custom pipe that would do the work for you by transforming your object into iterable of your choice that is suitable for your requirements (not idea what that would be since the only information is that "keyValuePipe or {{data | JSON}} is not good solution for me" without any reasoning).

Angular how to get a value back from a function

i am quite new to angular and am trying to get a value froma button displayed in the html via a function.
the button layout is:
`<button class="btn" id="btn-gold" (click)="value(9)"
name="mybutton" value="9">`
9
i am also calling the function in the html file like following: {{ value() }}.
this is my setup in the .ts file:
public value(a) {
console.log(a);
return a;
}
sadly i am not able to get any value displayed in my html file. i can see this in the console:
7
scoring.component.ts:18 undefined
scoring.component.ts:18 undefined
now to the question. why am i not able to see the value in my .html file as i can see the number in my console, and secondly why am i also getting per click two undefined values with it :/
chears,
ArcherMark
Have a property value in your TypeScript
value: number;
and have a function to set the value
setValue(value: number) {
this.value = value;
}
Now you can set the value with a button in the template
<button class="btn" id="btn-gold" (click)="setValue(9)" name="mybutton" value="9">
and display the value with a template binding
{{ value }}
If you do something like {{ value() }} you are calling a method not a property so angular is gonna evaluate that expression every time it checks for changes, which is associated with the DoCheck lifecycle hook thats why the console log prints 2 times the value,
The solution is to convert value into a property and have a getter and a setter and use two way data binding.

How to access variable defined in *ngFor?

In my first Angular 4 application, I defined a list component :
<edm-document *ngFor="let document of documents" class="column is-one-quarter"></edm-document>
Document is an interface :
export interface Document {
id?: Number,
name: string,
filePath: string
}
All is working as expected, i.e I get my documents list. But now I would like to access to document variable inside my DocumentComponent (the edm-document tag component)
Inside my DocumentComponent template, if I try this it's not working :
<p>{{ document.name }}</p>
I get this error : DocumentComponent.html:1 ERROR TypeError: Cannot read property 'name' of undefined.
I need to enforce document definition like this, and specify document as an input :
<edm-document *ngFor="let document of documents" [document]="document" class="column is-one-quarter"></edm-document>
Now it works but seems a bit redundant to me as I defined a let in loop. Does that mean the variable defined with let is only available in tag where ngFor directive is set ?
Am I missing something?
Thanks,
Nicolas
it works but seems a bit redundant to me as I defined a let in loop
It is not as redundant as it might seem, which becomes obvious when rewriting things a bit:
When not explicitly defining what the component should use (with [document]="document" in your example) then how would your component know that the parent variable is named document? Consider:
<edm-document *ngFor="let d of documents" [document]="d"></edm-document>
One could argue that Angular could introduce some parent variable to access the outer loop variable, but then the component would know how it's going to be used, and could only be used in a loop. Reusable components should not be aware of that.
How would it know that it can use that loop variable directly, and does not need some child property instead? Like:
<edm-document *ngFor="let d of documents" [document]="d.text"></edm-document>
So: your code is just fine.
Initially during DOM rendering the documents object will undefined
Use a typesafe ? operator
<p>{{ document?.name }}</p>
Use a *ngIf with a array length condition as below,
<span *ngIf="documents.length > 0">
<edm-document *ngFor="let document of documents" [document]="document"
class="column is-one-quarter"></edm-document>
</span>
well you can also do something like this
<edm-document *ngFor="let document of documents" class="column is-one-quarter">
<span class="something">{{document.name}}</span>
</edm-document>
and in the edm-document.component.html do something like
<ng-content select=".something"></ng-content>
The value (document) of the loop is valid inside of that block where the *ngFor placed. In your case between: <edm-document>..</edm-document>
In your example:
<edm-document *ngFor="let document of documents"class="column is-one-quarter">
<p>{{ document.name }}</p> <!-- document.name is valid -->
</edm-document>
<p>{{ document.name }}</p> <!-- document.name is invalid -->

How to bind the key of a map in Angular

I receive an object which is on server-side a map, i.e. a key-value array. I would like to bind it in an Angular form and be able to modify the value (easy) and the key. Challenge is that when the key is modified, the associated value has to stay linked.
I tried :
<div ng-repeat="(type, value) in estate.extraServices track by $index">
<input ng-model="type"> <input ng-model="estate.extraServices[type]">
</div>
But when the first input change, a new line is added to the map and the new one is not linked to the new one.
My goal is to avoid using a scope function to resolve this problem, so I ask without many hope, but who knows !
If a binding on a key isn't possible, I would probably change the object structure to an array of Pair object (2 property : key and value)
<div ng-repeat="pair in array">
<input ng-model="pair.key"> <input ng-model="pair.value">
</div>
You can't bind an input to the actual property name. Object property names can't be changed dynamically
Your first example however works fine to update values
<div ng-repeat="(type, value) in estate.extraServices track by $index">
{{type}} <input ng-model="estate.extraServices[type]">
</div>
As for adding keys you would need to use a different input not bound to that object to add a new key to the object. Then with an event handler use a function that updates the object
Same for removing keys , use an event handler function that would do delete object[key]
Changing to an array of objects with same key name structure would likely be simplest depending on how this object is used
DEMO

AngularJS using push() issue when I want to override the previous push() entry

I have the following markup:
<p class="{{ UserMessageStyle }}" > Some Message </p>
And then in Angular Controller When I want to pass a specific class name to the
variable UserMessageStyle, I do the following:
$scope.UserMessageStyle = [];
if(condition_is_met)
{
$scope.UserMessageStyle.push("alert-danger");
}
But my problem is that, when once the above statement is run, then I cannot change it. It has a reason because I have declared UserMessageStyle as [] which mean on each instance of push() a new key&value pair will be added.
My Question is that, how should I use push() where I don't want my UserMessageStyle to be an object or an array. I want it to be a simple variable which is overriden on each instance call of push(). Something like this:
// the declaration of the variable as an object is removed.
if(condition_is_met)
{
$scope.UserMessageStyle.push("alert-danger");
}
But the above statement causes this error:
cannot read the property `push()` of undefined.
What should I do?
It sounds to me that you are looking for a normal variable instead of an array.
why dont you do something like this:
if(condition_is_met)
{
$scope.UserMessageStyle = "alert-danger";
}
First of all you do not need array here, class attribute understand space separated list
$scope.mystyles = "alert-danger alert-danger-bright"
and layout
<p class="{{ UserMessageStyle }}" > Some Message </p>
Also consider using ng-class directive for that
<p ng-class="UserMessageStyle" > Some Message </p>

Categories