I have several components that use basically the same table so I'm in the process of abstracting out that table. I have solved most of my dynamic table population needs but have yet to find a solution to the following.
In one of my table instances the rows need to be clickable. In the original table I simply added a click event in the row and had it call a function in my typescript file.
Now that the table is a child of any consuming component I am not sure how to dynamically add this click event. Here is an essentially what I am trying to achieve:
HTML:
<tr class="someClass" <!-- want click event here -->>
<td *ngFor="let column of row;"><div [innerHtml]="column"></div></td>
</tr>
This is the tables typescript file, where all the data is coming in on the visibleData object:
export class GenericTableComponent implements OnInit {
#Input() visibleData;
constructor() { }
ngOnInit() {
}
}
I implement the generic table in my parent HTML here
Parent HTML:
<oma-generic-table [visibleData]="visibleData"></oma-generic-table>
And here is a function in the parent which prepares the data. I have attempted to store the click event in a string and pass it but everything I've tried so far has failed (data binding with {{}}, square brackets, etc..).
transformData(visibleData) {
const ret: any = {};
ret.headings = visibleData.headings;
ret.action = '(click)="rowClicked([row.id])"';
ret.checkbox = this.checkBox; //add if the table needs checkboxes
ret.content = [];
for (let i = 0; i < visibleData.content.length; i++) {
ret.content.push(_.values(_.omit(visibleData.content[i], 'id')));
}
return ret;
}
However, even when hard coded into the child, the click event doesn't recognize the function in the parent and I get the following error:
EXCEPTION: Error in ./GenericTableComponent class GenericTableComponent - inline template:35:4 caused by: self.parentView.context.rowClicked is not a function
ORIGINAL EXCEPTION: self.parentView.context.rowClicked is not a function
I'm not sure if this is something simple or not. I'm new to Angular 2 so I apologize if this question is simplistic. Thanks in advance for any help.
Your generic table can emit custom events to which parent component can subscribe:
#Component({
selector: 'oma-generic-table',
template: `
<table>
<tr *ngFor="let row of visibleData" class="someClass" (click)="selectRow(row)">
<td *ngFor="let column of row;"><div [innerHtml]="column"></div></td>
</tr>
</table>
`
})
export class OmaGenericTable {
#Input() visibleData: VisibleDataRow[];
#Output() select = new EventEmitter<VisibleDataRow>();
selectRow(row: VisibleDataRow) {
this.select.emit(row);
}
}
Then in your parent component:
// in template
<oma-generic-table
[visibleData]="visibleData"
(select)="tableRowSelected($event)"
></oma-generic-table>
// in component
tableRowSelected(r: VisibleDataRow) {
console.log(`Selected row ${r}`);
}
Angular2 as far as I know will not bind to a "(click)" event that way. It may look ugly, but I would add the NgIf directive to the table on the row that the click event should act upon, and inversely apply the logic to another row that should not be affected by a click event. Ex:
<tr *ngIf="!isClickable" class="someClass">
<tr *ngIf="isClickable" class="someClass" (click)="rowClicked()>
<td *ngFor="let column of row;"><div [innerHtml]="column"></div></td>
</tr>
Then you can pass the isClickable variable as an input into your table from anywhere that you instantiate the table component, and act upon it.
Using this in HTML
<tr *ngIf="!isClickable" class="someClass">
<tr *ngIf="isClickable" class="someClass" #click>
<td *ngFor="let column of row;"><div [innerHtml]="column"></div></td>
</tr>
this in typescript
export class GenericTableComponent implements OnInit {
#Input() visibleData;
#ViewChild('click') protected _click:ElementRef;
constructor(enderer: Renderer) {
renderer.listen(this._click.nativeElement, 'click', (event) => {
// Do something with 'event'
})
}
}
Hope that help you
Related
I have the following code and i wish to update the parent class when click on the image. The image will call "SelectVariation" method when clicked. Is there any way to do this?
component.html :
<div class="clr-row">
<ng-container *ngFor="let variOption of product.variOptions">
<div class="card clickable clr-col-2 variationCard"
*ngFor="let variOptionTwo of variOption.variOptionTwos"> //Update this class
<div class="card-img" (click)="selectVariation(variOptionTwo.id, $event)">
<clr-tooltip>
<img src="{{variOptionTwo.url}}" class="variationImgScale" clrTooltipTrigger>
<clr-tooltip-content clrPosition="top-right" clrSize="m" *clrIfOpen>
<span>{{variOption.optName}} - {{variOptionTwo.optName}}</span>
</clr-tooltip-content>
</clr-tooltip>
</div>
</div>
component.ts :
selectVariation(id: number, event: any) {
//Update parent class
}
In the child component use
#Output() variableHere = new EventEmitter<>();
this.variableHere.emit(this.variableToSend);
Then in the parent associate the variable to a method in html child template definition:
<app-child (variableHere)="manageVariable($event)"></app-achild>
In the parent component define the method and do a variable equels the result of the method for example:
manageVariable(event) {
this.variableToUpdate = event;
}
If you have to check if the variable has changed his state call what you need to check in an ngDoCheck().
Take the advantage of the EventEmitter in angular with output
parent.component.html
<my-child-comp (onSelectVariation)="myVariation($event)" ></my-child-comp>
parent.component.ts
myVariation(myVars) {
console.log(myVars)
}
child.component.html
<button (click)="onSelectVariation.emit(myData)">trigger variation</button>
child.component.ts
#Output() onSelectVariation = new EventEmitter();
Name which you have defined in the output should be used as a event in it host element in parent
In my angular application I am attempting a workaround because the ag-Grid api getRowClass() is not working at intended. All I need to do is add a css class to an expanded row and remove it when the row is collapsed.
The original method using ag-Grid api that does not work looks as follows:
setRowNode(params) {
this.gridOptions.getRowStyle = (params) => {
if(params.node.expanded) {
return { background: 'red' };
}
}
}
Ideally I would be able to selected the DOM and append a class to it. I tried this with some JQuery and it worked, but for obvious reasons I do not want to have JQuery in this app. I wrote something along these lines:
$('.ag-row[row="'+params.node.id+'"]',self.api.grid.gridPanel.eBodyContainer).addClass('ag-row-focus');
How would I fulfill this req using vanilla JS?
You can do it by creating a custom directive, like this one :
//row-focus.directive.ts
import { Directive, HostBinding, HostListener } from '#angular/core';
#Directive({
selector: '[appRowFocus]' // you can target classes, id etc.: '.grid-Holdings' for example
})
export class RowFocusDirective {
#HostBinding('class.ag-row-focus') isFocused = false;
#HostListener('click') toggleOpen() {
this.isFocused = !this.isFocused;
}
}
Import this directive on your module, and then attach the directive to your elements :
// your-component.component.ts :
<div class="row" appRowFocus>
These elements will toggle the ag-row-focus on click. You can add different #HostListener for other events.
I have an Angular 2 app using Typescript but i am new to this, what i have is a table with a 'Delete' button,
I can pass the object data to my confirmation modal but when i 'Confirm' it, its still in my table.
delete-modal.component
import { Component, OnInit, Inject, Input } from '#angular/core';
import { TestService } from '../../ABC/TestService/TestService.service';
import { MdDialog, MdDialogRef, MD_DIALOG_DATA } from '#angular/material';
import { testModal } from 'models/test';
#Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.css']
})
export class testDeleteModalComponent implements OnInit {
#Input('test') test: testModal;
constructor(private TestService: TestService, private accountService: AccountService,
#Inject(MD_DIALOG_DATA) private dialogData: any) { }
ngOnInit() {
console.log('test', this.dialogData.beneficiary);
this.test = this.dialogData.test;
}
deleteTest() {
if (this.dialogData.test.identifier) {
// this.dialogData.beneficiary.splice(this.dialogData.beneficiary.indexOf(this.beneficiaryAnt), 1);
// this.dialogData.beneficiary.splice(this.beneficiary);
// delete this.beneficiary;
this.dialogData.test.splice(this.dialogData.test.indexOf(this.dialogData.test), 1);
} else {
this.dialogData.test.operation = 'X';
}
}
}
HTML
<button md-icon-button (click)="deleteTest()" name="deleteTestDetails">
<md-icon>delete forever</md-icon>
</button>
All other HTML is in a main component and the 'Delete' button is used as shown below
<app-test-main-page-delete-button [test]="test"></app-test-main-page-delete-button>
The 'deleteTest' method is called when the user click the confirm button.
I have also included above some ways i have tried in the IF but they always come back
... is not a function
It is good that you asked this question, my projects of three peoples also struggling with this. we have found is two ways. what i will show is two ways of doing typescriptdelete.
solution a.
because it is object, it will need identifier. First is
var objectdelete = {
identifier: 'Mydelte',
value: '168%'
}
Next what we need is now service. some people call them directives but from my experience they are the same thing. We have alert so user knows if they did not set identifier that they must go back. I do not see service on your side, i see array being deleted. if you combine the array and the service, this will then be working across whole website.
export class DeleteService
delete(objectToDelete: string) {
if (!objectToDelete.identifier) {
alert('No identifer');
}else {
// Delete from your array here.
}
}
Solution 2.
If the above does not meed your needs, our tema also experimented with interfaces in typescript. You can see them here https://www.typescriptlang.org/docs/handbook/interfaces.html
so it becomes
export class myDeleteService {
deleter: IDeleter
}
export interface IDeleter {
delete: this.delete.delete(deletE);
deleteArray: this.array =[];
}
then simply in your html it will be
<button (click)='delete(dieleter)'>Delete me!</button>
These are all common typescript behaviours for angular2/4/5 so we are hoping to become more used to them when we have hads more time to use them!
The easiest way to delete data object on button click and refresh instantly when it's done :
Your parent html has to call children like this :
<app-component [inputData]="dataTable" (inputDataChange)="resetData()"/>
Add dataTable as class variable and implement the output function :
resetData() { this.dataTable=[] }
Then in children html leave your code (you can use this changes)
<button class="fa fa-delete" (click)="deleteTest()" name="deleteTestDetails">Delete</button>
Finaly in your children ts file set your data object for each change, and implement your input function
myDataTable: any = [];
#Input set inputData(data: DataTable) {
if(data) {
this.myDataTable = data;
}}
#Output() inputDataChange: EventEmitter<any> = new EventEmitter();
deleteTest() {
this.inputDataChange.emit(true);
}
What does this code do ?
It will emit and event to the parent when the delete button is clicked, then your parent will delete the dataTable, and finally, your children input will refresh it, as setter will catch the changes and refresh the variable.
If you want to apply those rules to table changes, then simply emit your dataTable and reassign it instead of reset it.
I am in a project with and our team have struggled on this for a whiles.
First thing I will say is this, Angular has not made this an easy task, so we will attempt to ignore the framework and write pure Java instead to make our lives easyer on ourselves.
SO looking at your button, I can see that you have started on the right track.
If the button is calling your component like the following
Html/Java
<button ng-click="delete()">Click me<button>
Component.ts
function delete = deleteMethod(testIdentifier) {
var abc = this.beneficiary.beneficiaryIdentifier.test.splice(this.beneficiary.beneficiaryIdentifier.test.indexOf(testIdentifier));
component2.deleteFunction();
}
Component2.ts
Then we can pass our identifiers into our parent or child components and remove the beneficiary like so:
deleteMethod(deetle) {
this.beneficiary.removeAtIndex(testIdentifier.splice(1), 1);
}
Nice and easy looking back, but it took our team of threes a long whiles to figure that ones out.
I am trying to modify the input box model of Angular 4 such that if the user types in, say 23, in the box, the box should display 23%.
I have tried appending the % to the model value after accessing it in events like (input) or (change). This however changes the model entirely. What I want is that the Angular model variable should still contain 23 but should display 23%.
Is there any way I can build a directive around this. Any suggestions or link ?
It's a little hacky way, but you can do this:
<input #input1 value="{{title}}%" (keydown)=setModel(input1.value)>
And in component:
title = '23';
setModel(val) {
this.title = val.split('%').join('');
console.log(this.title)
};
value="{{title}}%" will take title value and will add % at the end. And you can set new value using setModel method, but before setting, you need to remove all % characters, like this: this.title = val.split('%').join('');.
Since you're trying to change the way how a component displays the value, you should use a directive instead of changing the actual value in the model. In other words, you need an input mask here. You can use one of the existing packages (e.g. https://github.com/text-mask/text-mask) or write your own directive.
You can create a component with 2 values: one is the bound value, and the other is the displayed value. The component would look a little like this:
#Component({
selector: 'hello-world',
template: `
<input type="text" [(ngModel)]="display" (keyup)="updateInput($event)" placeholder="Enter a name here">
<hr>
<h1>Input: {{input}}</h1>
`
})
export class HelloWorld {
#Input() input = 0;
#Output() inputChange = new EventEmitter();
display;
ngOnInit() {
this.display = `${this.input}%`
}
updateInput(evt) {
this.input = this.display.replace(/[^0-9.]/g, "");
this.display = `${this.input}%`;
this.inputChange.emit(this.input);
}
}
And you can bind to the component like so:
<hello-world [(input)]="myVar"></hello-world>
I have child/parent components, and in parent view in table assign the value and child tag in td
Code example:
<tr *ngFor="let user of pagedItems" (click)="getUserDetails(user,$event)"
data-toggle="control-sidebar" id="table-row">
<td>
<control-sidebar [user]="userDetails"></control-sidebar>
{{ user.name }}
</td>
<tr>
and when I clicked on element the child with tag control-sidebar constructor is invoked several times instead of just one time.
constructor code in child component:
constructor(private http: UserHttpService) {
if (jQuery.AdminLTE && jQuery.AdminLTE.controlSidebar) {
jQuery.AdminLTE.controlSidebar.activate();
}
}
...
if I add this condition to AfterViewInit
ngAfterViewInit() {
if (jQuery.AdminLTE && jQuery.AdminLTE.controlSidebar) {
jQuery.AdminLTE.controlSidebar.activate();
}
}
this condition used 1 time, but i need to only use constructor