I have the below code, I want to add a class to a span selector, but I have this error
Cannot read property 'classList' of null
<th *ngFor="let column of columns;"
<ng-container *ngIf="column?.sortable">
<span [id]="'sort-'+column?.field" (click)="sortArray(column?.field)"></span>
</ng-container>
</th>
if (this.columns) {
this.columns.forEach(column => {
if (column.sortable) {
const parent: HTMLElement = document.getElementById("sort-" + column.field);
this.renderer.setElementClass(parent, "sorting_asc", true);
}
})
}
I think you have overly complicated your code, You are using angular and I believe its not a good practice to access DOM elements using document.getElementById().
For your code to work, you will need to ensure that the view has loaded before you can access the DOM elements. You need to move your code to the AfterViewInit life cycle hook.
Below is how I would refactor the code
<th *ngFor="let column of columns;"
<ng-container *ngIf="column?.sortable">
<span [class.sorting_asc]="column?.sortable" (click)="sortArray(column?.field)"></span>
</ng-container>
</th>
We simply bind to [class.sorting_asc]="column?.sortable" and let angular apply the classes for us
Related
I've been struggling with the foreach blade directive when combined with Laravel-Livewire and dynamically changing data rendered in an html table.
Specific scenario:
A table is presented to the user
Each row of the table includes a clickable icon that toggles a variable associated with that row
Toggling this icon visibly removes the row from the table
It sounds simple enough but I cannot get it to work correctly.
<?php
namespace App\Http\Livewire;
use Livewire\Component;
class UnreconciledServices extends Component
{
public $services;
public function render()
{
return view('livewire.unreconciled-services');
}
//
// receives an invoice number and changes the "reconciled" value for the $aaaServices record to true
//
public function test($a)
{
}
}
This is pretty simple - there is a function called "test" that accepts a variable (the function does nothing at all) and there is a collection of records called $services that has been passed in from the parent.
The blade file is as follows:
<div>
<table style="width:100%">
<thead>
<tr>
<th width="5%">
</th>
<th>
Service Date
</th>
<th>
Call #
</th>
<th>
Payment
</th>
</tr>
</thead>
<tbody>
#foreach ($services as $service)
{{-- <span wire:key="{{$service->invoice_number}}"> --}}
{{-- #if ($service->reconciled == false) --}}
<tr id="{{$service->invoice_number}}" class="text-center" style="{{ $service->reconciled == 1 ? 'display:none' : 'show'}}">
<td>
{{-- <input class="m-2" type="checkbox"> --}}
{{-- <span class="m-2"><i class="far fa-trash-alt text-red-500"></i></span> --}}
<span wire:key="{{$service->invoice_number}}" wire:click="test('{{$service->invoice_number}}')" class="m-2 cursor-pointer"><i class="lm-2 rm-2 fas fa-plus text-green-500"></i></span>
</td>
<td>
{{$service->invoice_date}}
</td>
<td>
{{$service->call_number}}
</td>
<td>
{{$service->service_price}}
</td>
</tr>
{{-- #endif --}}
{{-- </span> --}}
#endforeach
</tbody>
</table>
</div>
This is a simple table with 4 columns. The 1st is a clickable icon, the other 3 are data. Rows should only be visible if the "reconciled" boolean is 0.
When initially rendered, the appropriate records are displayed (there are 2 that qualify and 177 that do not). However, as soon as I click the icon - even when the function it links to has zero actual content - all records are suddenly visible except for the one that I've just hidden.
Looking at the html change when I click the icon, the tag changes from
<tr id="21031251674" class="text-center" style="display:none">
to
<tr id="21031253205" class="text-center" style="show">.
I have tried using livewire keys, though I'm not sure how this would affect the functionality since I'm not rendering components, but rather data driven html.
Surely someone else has encountered this and overcome. Please point me in the right direction.
Sorry for the delay. This whole thread should be deleted as bone-headed. But it was written in earnest...
When all was said and done, I was unclear on the differences b/w an html / ajax - based approach such as livewire and something more javascript-based such as Vue. I was expecting functionality that didn't exist in the framework as I was attempting to use it.
To solve the problem, I moved towards individual row items being components in and of themselves, using emit to communicate between components and adjust the backend data source.
Once I embraced the html / ajax approach and stopped trying to force / expect javascript functionality & dom manipulation then all was well. Just a mindset difference from what I am familiar with, and it took me some time to turn the corner.
I have a mat table with several columns. One of the columns is a status field. I want to put colored badges in that column for the status that record has. I made them non clickable buttons. A record can have one of the 3 statuses "Completed", "Deleted", and "Canceled". I cannot seem to get it to work dynamically when it loads to set the colors. I have seen many examples where people use ng-class and I am trying hard to implement it, or more so to refer to the value of row.OrderStatus.
HTML
<!-- Status Column -->
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef>Status</th>
<td mat-cell *matCellDef="let row">
<button ng-class="row.OrderStatus">{{row.OrderStatus}}</button>
</td>
</ng-container>
CSS
.Completed{
background: green;
}
.Cancelled{
background: yellow;
}
.Deleted{
background: red;
}
That is the incorrect syntax for ngClass. Check out the docs for the correct syntax.
This should work for you.
<button [ngClass]="{'Completed': row.OrderStatus === 'Completed', 'Deleted': row.OrderStatus === 'Deleted', 'Cancelled': row.OrderStatus === 'Cancelled'}">
{{row.OrderStatus}}
</button>
STACKBLITZ DEMO
I have a material table as following:
<table mat-table [dataSource]="dataSource" >
... Rows ...
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-paginator #MatPaginator [pageSizeOptions]="[4]" showFirstLastButtons></mat-paginator>
I need the table to be hidden when the variable info is false.
Attempt 1:
<table *ngIf="info" mat-table [dataSource]="dataSource" >
It works fine but the pagination does not work when info is true and all rows are displayed on the first page.
Attempt 2:
<table [hidden]="!info" mat-table [dataSource]="dataSource" >
Makes the pagination work, but when info is false the table is still displayed with no results.
Attempt 3:
Tried to sourround the whole table with a div and apply *ngIf to the div. Result: Same problem as in first attempt.
Tried to apply [hidden] to the div but still not hidding.
EDIT: Attempt 4:
Applied directly to the envolving div css style="display:none" and getting the same result as in attempt 3, the table is shown even if it has no values.
Any idea on how to hide the table when info is false and show it
when info is true with a working pagination?
Thanks!
Use this way
[hidden]="true"
<div [hidden]="true" >
<table mat-table [dataSource]="dataSource" >
<ng-container matColumnDef="test">
<th mat-header-cell *matHeaderCellDef> Test. </th>
<td mat-cell *matCellDef="let exp"> {{exp}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-paginator #MatPaginator [pageSizeOptions]="[2, 4, 6]" showFirstLastButtons>
</mat-paginator>
<div>
stackblitz demo
make sure to change your condition in [hidden]
you can use ngClass to add dynamic class and use that class to hide the table like
[ngClass]="{'hidden': !info}"
and in your style.css
.hidden{
display:none;
}
demo
You can't put the paginator inside a ngIf, otherwise, when you set this.dataSource.paginator = this.paginator, this.paginator is undefined. There is a solution if you REALLY want to ngIf it (tell me if it is realy necessary), but I recommend changing it to hidden instead.
Angular 8 solution:
[hidden] can be used instead of *ngIf and activation of the tab can be done using CSS properties.
.tableClass .mat-tab-group.mat-primary .mat-ink-bar {
width: 160px !important;
}
<div [hidden]="true" class="tableClass">
<mat-tab-group>
<mat-tab label="Tab1">
</mat-tab>
<mat-tab label="Tab2">
</mat-tab>
</mat-tab-group>
</div>
I had the same problem because I was using the *ngIf and async pipe in my template and pagination was not working. After some research I was able to make it work using the #ViewChild annotation and a set function so I decided to share my complete solution here :)
The hidden solution propose by other answers didn't work for me.
In my component.ts file:
constructor(private cdRef: ChangeDetectorRef) { }
private paginator: MatPaginator;
/*
We are using #ViewChild because of the ngIf with async pipe on the template
When the data is emmited for the first time from the observable, we need to bind the
pagination component with the datasource. This can be achieved with the code bellow.
*/
#ViewChild(MatPaginator) set matPaginator(mp: MatPaginator) {
if(mp) {
this.paginator = mp;
this.dataSource = new MatTableDataSource<any>(your_data);
this.dataSource.paginator = this.paginator;
this.cdRef.detectChanges();
}
}
and in my template file:
// my observable with the async pipe
<div *ngIf="raceList$ | async as races">
<div class="mat-table-width">
<table mat-table [dataSource]="dataSource">
...
</table>
<mat-paginator [pageSizeOptions]="[10, 20, 30]"
showFirstLastButtons
aria-label="select page of races">
</mat-paginator>
</div>
</div>
With this solution I was able to display the data in the table and make pagination work.
I couldn't find something that will help me to solve this issue in Angular2. I'd like to set a css class when I select a row. (without using jQuery)
<table class="table table-bordered table-condensed table-hover">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Website</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of companies" (click)="selectedCompany(item, $event)">
<td>{{item.name}}</td>
<td>{{item.email}}</td>
<td>{{item.website}}</td>
</tr>
</tbody>
</table>
I'm using Angular2 final release
There are plenty of solutions to do this, one of them is you can store the current company when clicked.
In the *ngFor you check if the current item is the currentCompany and you add the class highlighted or whatever class you wish if its the same company.
export class TableComponent {
public currentCompany;
public selectCompany(event: any, item: any) {
this.currentCompany = item.name;
}
}
And then on your template:
<tr *ngFor="let item of companies" (click)="selectCompany($event, item)"
[class.highlighted]="item.name === currentCompany">
--
Another solution if you wish to have multiple highlighted companies you can add a property highlighted to your item. Then on selectCompany() you just set the property to true. On your check you do [class.highlighted]="item.highlighted".
I know this was answered a while ago, but to expand on the accepted answer, you could also use [ngClass]="{'class_name': item.id === currentCompany }". The table hover may need to be removed as it may hide the background color change
<tr *ngFor="let item of companies" (click)="selectCompany($event, item)" [ngClass]="{'class_name': item.id === currentCompany }" >
Then css
.class_name{ background-color: yellow; }
I have a table which I have applied sorting to but to complete this task I would like the classes to change to show carets going up or down based on the sorting.
As you can see I have used the standard SortableMixin within Ember to get the sorting functionality but I'm having trouble changing the class of the individual element which has been clicked.
App.CampaignsController = Ember.ArrayController.extend({
sortProperties: ["id"],
sortAscending: true,
actions: {
sortBy: function(property){
if (this.get("sortProperties")[0] === property){
this.toggleProperty("sortAscending");
} else {
this.set("sortProperties", [property]);
this.set("sortAscending", true)
}
}
}
});
The table I'm I've applied the actions to is below:
<table class="table table-striped table-hover">
<thead>
<tr>
<th {{action "sortBy" "name"}}><i {{bind-attr class=":fa sortAscending:fa-caret-up:fa-caret-down"}}></i>Campaign Name</th>
<th {{action "sortBy" "campaign_code"}}><i {{bind-attr class=":fa sortAscending:fa-caret-up:fa-caret-down"}}></i>Campaign Code</th>
</tr>
</thead>
<tbody>
<td>{{name}}</td>
<td>{{campaign_code}}</td>
</tbody>
</table>
I'm using the sortAscending boolean to dictate what CSS class will appear. My problem is if I click the first heading, the classes on the second heading also change.
How do I get the CSS to change only on the heading that I have clicked?
Your <th> now has state (whether it's sorted, and whether it's ascending or descending), so you should wrap it up in a component. Something like this
<table>
<thead>
<tr>
{{#sortable-th property='name' action='sortBy'}}
Campaign Name
{{/#sortable-th}}
</tr>
</thead>
</table>
The component's template
// templates/components/sortable-th.js
<th>
<i {{bind-attr class=":fa sortAscending:fa-caret-up:fa-caret-down"}}></i>
{{yield}}
</th>
and code
// components/sortable-th.js
export default Ember.Component.extend({
sortAscending: true,
click: function() {
this.toggleProperty('sortAscending');
this.sendAction('action', this.get('property'), this.get('sortAscending'));
}
}
That's just a rough outline, try the implementation yourself. But that's how I would start thinking about it.