I have a dynamic array that represents columns in a table, and I have an array of rows in the table. I get the arrays as input in my angular 4 components and I don't know anything in the columns before I get them (should work on multiple types of variables).
Each column has a variable that says if it is mandatory or not and according to this I build a condition string that I want to run before I insert a new row to the array of rows or change an existing one.
I'm creating the condition in the ngInit, and after the creation it looks something like this:
"(r.id == null) || (r.name == '')" and so on (there could be && too)
I'm trying to bind the condition to the disabled attribute of a button, but I can't seem to convert it to a real.
How can I convert it to a condition so I could execute it and return true/ false accordingly?
I've already tried doing [disabled]="[conditionVar]", and also tried using.
${condition},
but those didn't work
my code looks something like this:
myComponent.ts:
export class Column {
title: string;
fieldName:string;
mandatory: boolean;
}
#Component({
selector: 'myComponent',
templateUrl: './myComponent.component.html',
styleUrls: ['./myComponent.component.css']
})
export class MyComponentimplements OnInit {
#Input() columns:Column[];
#Input() rows;
conditionString = "";
constructor() { }
ngOnInit() {
for (let c of columns.filter(d=>d.mandatory)) {
this.conditionString += "(r." + c['fieldName'] + " == null) || "; // 'r' is the name of the variable that i want to run the condition on
}
this.conditionString = this.conditionString.substr(0, this.conditionString.length - 4); // to remove the last ||
}
}
myComponent.html:
<table>
...
<tbody>
<tr *ngFor="let r of rows">
<td *ngFor="let c of columns">
<input [(ngModel)]="r.[c.fieldName]">
</td>
<td *ngFor="let c of columns">
<button [disabled]="here I want to bind my condition">save this row</button>
</td>
</tr>
</tbody>
</table>
(the user should edit the rows and if he want he can save the changes)
every thing works but the binding of the condition
Related
In a Svelte app, I have this array of countries:
let countries = [
{
name:"Alegeria",
status: "1"
},
{
name:"Bulgaria",
status :"0"
}
]
Note the status property is a string. I iterate the array this way:
{#if countries.length > 0}
<table class="table">
<thead>
<tr>
<th>Country</th>
<th class="text-right">Status</th>
</tr>
</thead>
<tbody>
{#each countries as c}
<tr>
<td>{c.name}</td>
<td class="text-right"><Switch bind:checked={Boolean(Number(c.status))} /></td>
</tr>
{/each}
</tbody>
</table>
{:else}
<p class="alert alert-danger">No countries found</p>
{/if}
As you can see, I try to convert the value of the status property to a boolean this by using Boolean(Number(c.status)).
Instead of the desired conversion I get the error: Can only bind to an identifier (e.g. foo) or a member expression as the REPL shows.
What am I doing wrong?
As it says in the error, you can only bind to an identifier or member expression - ie a variable.
This is because a bind is a two-way thing, and if you have applied Boolean(Number(()) to it, when someone changes that input, then svelte doesn't know how to undo those functions to 'save' the data back into that variable it's bound to.
If you can't change the status variables to be boolean (better solution, as suggested by other answers), you need to manually do this two-way updating. Drop the bind, just have checked={Boolean(Number(c.status))}, and handle the input's change event to convert from true/false back into "1" or "0", and save that to the status.
Use:
function handleClick(country) {
countries.find(c => c.name == country.name).status = (country.status == "1") ? "0" :"1"
}
and
<Switch checked={Boolean(Number(c.status))} on:change={() => handleClick(c)}/>
See it working in this repl
I think the problem is that the Boolean() function creates a new object, to which you can't bind, because it is never again referenced. You can bind directly to your array of values in countries, using this code:
{#each countries as c, index}
<tr>
<td>{c.name}</td>
<td class="text-right"><Switch bind:checked={countries[index].status} /></td>
</tr>
{/each}
What has changed is that you use the index parameter of the #each loop now to bind to the variable of the countries array. Please be aware that in order for this to properly work, you need to change the status values to true or false. Otherwise it will still work, but the initial value will always be true.
If you just want pass the value down to the Switch component, simply remove the bind: like so:
<td class="text-right"><Switch checked={Boolean(Number(c.status))} /></td>
If you want to update the countries model via the switch component, I suggest to forward the click event and use a simple click handler method, something like this:
function onClick(event, country) {
countries = countries.map(c => {
if (c.name === country.name) {
c.status = event.target.checked ? '1' : '0';
}
return c;
})
}
...
<td class="text-right"><Switch checked={c.status === '1'} on:click={(e) => onClick(e, c)}/></td>
full code on REPL: https://svelte.dev/repl/013286229d3847c1895c4977aee234af?version=3.9.1
I have a problem that my dynamic data is not getting binded to the UI and also Data tables. I have tried using various ways but its not working. I am using smart admin latest theme for my development of website
when I hit the api i get response.
var tabledata:any[]=[]
Get(){
this.getservice().subscribe(
res=>{
if(res && res.data && res.data.length>0){
this.tabledata=res.data;
console.log(this.tabledata);
}
}
)
}
In html
<tr *ngFor="let data of tabledata">
<td>{{data.name}}</td>
<td>{{data.age}}</td>
</tr>
Could you please try to excute your Get method in OnInit?
Why do your Get method starts with a capital letter?
Why do you declare your table as a table of any? Using typed objects i always better.
Can you try this code below?
vartabledata: Person[];
class MyComponent implements OnInit {
ngOnInit() { this.get()
}
get(){
this.getservice().subscribe(
res=> {this.tabledata = res.data;
console.log(this.tabledata);}
)}
}
In front i would suggest you to add *ngIf so you display table only when your data are loaded.
<div *ngIf="tabledata">
<tr *ngFor="let data of tabledata">
<td>{{data.name}}</td>
<td>{{data.age}}</td>
</tr>
</div>
If there are no data even with this, try to do
<div *ngIf="tabledata">
<tr *ngFor="let data of tabledata">
{{data | json}}
</tr>
</div>
I think that you receive a data object that doesn't contains name and age
attributes
I use last version of angluar. (7.2.0)
I have personal tr component like:
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'app-table-row',
templateUrl: './table-row.component.html',
styleUrls: ['./table-row.component.scss']
})
export class TableRowComponent implements OnInit {
#Input() character: any;
#Input() columns: string[];
constructor() { }
ngOnInit() {
}
}
I want to use this component inside table like:
<table class="mat-elevation-z8">
<tr>
<th *ngFor="let c of columns">{{c}}</th>
</tr>
<tr app-table-row class="component-style table-row-component" *ngFor="let ch of characters | async"
[character]="ch"
[columns]="columns">
</tr>
</table>
get errors like:
Can't bind to 'character' since it isn't a known property of 'tr'. ("table-row class="component-style table-row-component" *ngFor="let ch of characters | async"
[ERROR ->][character]="ch"
[columns]="columns">
</tr>
"): ng:///AppModule/TableComponent.html#7:7
How to add my component correctly inside table?
Define the component selector as an attribute selector with square brackets:
#Component({
selector: 'tr[app-table-row]',
...
})
so that it can be set as an attribute of the tr element:
<tr app-table-row ...>
The approach of selecting an attribute 'tr[app-table-row]' is fine when you want to generate only one row at a time, but it could become cumbersome for many rows, for instance if you need to use multiple row span
In this case, just use a regular selector 'app-table-rows' and set the component's host display to contents.
:host {
display: contents;
}
demo
As #ConnorsFan mentioned, you need to define your component as an attribute by adding the square brackets to your component's selector, but there is no need to add the 'tr' in front of it, so the following should be fine:
#Component({
selector: '[app-table-row]',
...
})
Then use it as follows:
<tr app-table-row
*ngFor="let item of items"
[myProp]="item"
(myEvent)="executeEvent($event)"
...>
</tr>
Having an *ngFor in Angular 2+ need to build the iterable (rows) through the creation of an array of n elements:
<table>
<tr *ngFor="let row of rows">
</tr>
</table>
It's there a way of iterate over a number??.
Thanks in advance.
You can generate an array of row indexes with a method of the component class:
public generateRowIndexes(count: number): Array<number> {
let indexes = [];
for (let i = 0; i < count; i++) {
this.indexes.push(i);
}
return indexes;
}
and call it in the template:
<table>
<tr *ngFor="let rowIndex of generateRowIndexes(5)">
...
</tr>
</table>
The code can be tested in this stackblitz.
I suggest you build a custom structure directive just for looping as many times as you want, it is a for-loop wrapped as directive.
The steps to doing this are:
Step-1:
In your module create a loop.directive.ts file.
Step-2: In this directive file paste this code:
import { Directive, Input, TemplateRef, ViewContainerRef } from '#angular/core';
#Directive({
selector: '[appLoop]'
})
export class LoopDirective {
constructor(
private viewContianerRef: ViewContainerRef,
private templateRef: TemplateRef<any>,
) {}
#Input('appLoop') set render(loop: number){
this.viewContianerRef.clear();
for(let i=0; i<loop; i++){
this.viewContianerRef.createEmbeddedView(this.templateRef, {});
}
}
}
Step-3: Now in your template file use *appLoop="5" if you want to loop 5 times or use a variable for example numberOfLines : *appLoop="numberOfLines".
In the code you put in the question it will look like this, where rows is of type number:
<table>
<tr *appLoop="rows">
</tr>
</table>
I would use this syntax to set the index value into an attribute of the HTML element:
<table>
<tr *ngFor="let item of items; let i = index" [attr.data-index]="i"> // data-index attribute will be assigned index
</tr>
</table>
I have table generated with dynamic ids like this one
<table>
<tbody>
<tr *ngFor="let row of createRange(seats.theatreDimension.rowNum)">
<td id={{row}}_{{seat}} *ngFor="let seat of createRange(seats.theatreDimension.seatNumInRow)">
</td>
</tr>
</tbody>
</table>
I want to access table td element from angular 2 .ts file. Here are functions:
ngOnInit() {
this.getSeats();
}
getSeats() {
this.cinemaService.getAllSeats(this.repertoire.id).subscribe(
data => {
this.seats = data.json();
this.setReservedSeats();
}
)
}
setReservedSeats() {
this.seats.reservedSeats.forEach(seat => {
let cssSeatId = seat.rowNumReserved + "_" + seat.seatNumInRowResereved;
document.getElementById(cssSeatId).className += "reserved";
}
)
}
and after that I want dynamically to set class of every td, but I am getting this console error in my browser:
ERROR TypeError: Cannot read property 'className' of null
Just to note once again that I generate td ids dynamically. They are in form rowNum_cellNum.
I am getting this object from api.
{
"theatreDimension": {
"rowNum": 17,
"seatNumInRow": 20
},
"reservedSeats": [
{
"rowNumReserved": 9,
"seatNumInRowResereved": 19
},
{
"rowNumReserved": 2,
"seatNumInRowResereved": 4
},
{
"rowNumReserved": 15,
"seatNumInRowResereved": 15
}
]
}
I am using theatreDimension to make table. Then I try to make reserved seats from reservedSeats array with red background (reserved)
How I can access td elements from angular 2 and solve this issue?
Instead of accessing the DOM directly, you should try using the ngClass directive to set the class:
<td [ngClass]="{'reserved': isReserved(row, seat)}" id={{row}}_{{seat}} *ngFor="let seat of createRange(seats.theatreDimension.seatNumInRow)">
</td>
You can then implement the isReserved(row, seat) function, and if it returns true, it will apply the reserved class.
isReserved(rowNum: number, seatNum: number) {
return this.seats.reservedSeats.some((r) => r.rowNumReserved === rowNum && r.seatNumInRowResereved === seatNum);
}
To directly answer your question, in order to get the element by ID, you need to do so after the page has been renedered. Use the function ngAfterViewInit(). You will need to remove the call to setReservedSeats() from getSeats().
ngOnInit() {
this.getSeats();
}
ngAfterViewInit(){
this.setReservedSeats();
}
However, I would suggest going a different route. You can set the style of the element based on whether or not the seat has been reserved. Assuming you have some sort of "reserved" flag on the seat object you can do something like this:
<td id={{row}}_{{seat}}
[ng-class]="{'reserved' : seat.researved}"
*ngFor="let seat of createRange(seats.theatreDimension.seatNumInRow)">
</td>