State changes but component doesn't update - ANGULAR - javascript

I'm using ionic with angular, when i'm creating alert in ok button's callback function i'm changing my state. State changes, but this change doesn't effects in iu, i think component is not updating. How can I fix this?
async presentAlert() {
const alert = await this.alertController.create({
header: '',
message: '',
buttons: [
'cancel',
{
text: 'ok',
handler: () => {
this.currentScreen = "";
this.dates[this.currentDateIndex].isOrdered = false;//disable order
}
}
]
});
await alert.present();
}

You can try to use ChangeDetectorRef to explicitly state that change has been made and view needs to be updated.
Reference: https://angular.io/api/core/ChangeDetectorRef
Example:
Declare ChangeDetectorRef in constructor
constructor(public cd: ChangeDetectorRef) {}
Then use it in your callback:
buttons: [
'cancel',
{
text: 'ok',
handler: () => {
this.currentScreen = "";
this.dates[this.currentDateIndex].isOrdered = false;//disable order
this.cd.detectChanges();
}
}
]

Related

Reusable object-literals

I have some component with a table which has actions buttons. When clicking on the button, the component emits an action, for example: (edit, delete,route)
getEvent(action: ActionButtons, object: any) {
// type: (edit,delete,route), route: info where to redirect
const {type, route} = action;
this.action.emit({ type, route, body: object });
}
In the parent component I catch this object by the following function and do some logic depending on the action:
getAction({type, route, body: {...faculty }}) {
const action = {
edit: () => {
this.openFacultyModal(faculty);
},
delete: () => {
this.openConfirmDialog(faculty);
},
route: () => {
this.redirecTo(faculty.faculty_id);
}
};
action[type]();
}
The poblem is, if I want to use the table in another component I have to cut and paste getAction() and just change the function inside object.
It turns out that there will be code duplication.
Is it possible to somehow solve the problem of code duplication using closures or creating a separate class?
You can make your action map object reusable:
const actions = {
edit(target, faculty) {
target.openFacultyModal(faculty);
},
delete(target, faculty) {
target.openConfirmDialog(faculty);
},
route(target, faculty) {
target.redirecTo(faculty.faculty_id);
}
};
Then use it where necessary, for instance in getAction:
getAction({type, route, body: {...faculty }}) {
actions[type](this, faculty);
}

"Cannot read property 'props' of undefined" while mounting functional Vue component

Case:
I am trying dynamically mount functional vue component inside custom directive
(I need to show special button, when user hovers on some elements on the page). I do it in this way, dynamically, because I have thousands of elements on the page, so I try to avoid excess components and try to mount them on the fly, while hovering.
Directive code:
import AIcon from './ui/AIcon';
let AIconComponent = Vue.extend(AIcon);
editable: {
bind: (element, binding, vNode) => {
element.addEventListener('mouseover', () => {
element.style.position = 'relative';
let editButtonWrp = htmlToElement(`<div class="btn-wrp"><span class="btn"></span></div>`);
element.prepend(editButtonWrp);
new AIconComponent({
el: editButtonWrp.querySelector('.btn'),
parent: vNode.context,
props: {
size: 'xs', icon: 'edit',
},
});
});
}
}
Button functional component code:
export default {
functional: true,
props: {
icon: {
type: String,
default: '',
},
size: {
type: String,
default: 'md',
}
},
render(createElement, { props }) {
let iconHref = `/dist/img/sprite.svg#${props.icon}`;
return createElement('svg', {
class: { 'a-icon': true, [`-${props.icon}`]: true },
}, [
createElement('use', {
attrs: {
'xlink:href': iconHref,
},
}),
]);
},
}
But on the page I get this error:
TypeError: Cannot read property 'props' of undefined
at Proxy.render (AIcon.js?27d5:19)
As I understand, when I call new AIconComponent({ ... }) in directive, it passes undefind context to render(createElement, { props }) ... but why? How I can resolve this case?

How to make objects properties reactive when they rely on an external object?

tldr:
User is a global object.
If I change show value, the component will update instantly, that's OK.
What I want to gain is like "When User.isLoggedIn() becomes false, the Log out element must hide. When it becomes true, the element must show and Login/Signup must hide." In my app, this goal would transform into another, "When I'm redirecting from login,signup, orsignout pages, these properties(and state of the button) must be updated."
Toolbar.Vue.
Script:
<script>
export default {
data() {
return {
items: [
{title: 'Questions', to: '/questions', show: true},
{title: 'Ask question', to: '/askQuestion', show: true},
{title: 'Categories', to: '/categories', show: true},
// only F5.
{title: 'Login/Signup', to: '/login', show: !User.isLoggedIn()},
{title: 'Log out', to: '/logout', show: User.isLoggedIn()},
]
}
},
}
Piece of markup:
<router-link
v-for="item in items"
:key="item.title"
:to="item.to"
v-if="item.show"
>
You know, I'm trying to do the 'logout thing' with vue. I have that Toolbar component with router-link to Logout component.
Note: I don't import User class in my component directly, but I do it in my app.js. like this:
import User from './helpers/AppUser';
window.User = User;
So, I think that everybody has access to the right User. Furthermore, this class is just a grouped couple of methods. Main method is retrieve().
In my Logout component there's the code:
beforeMount() {
User.logout();
router.push('questions')
// window.location = '/questions'
}
So, when I go logout, all is fine (I'm returning to questions page), but my Log out button is still here.
User.isLoggedIn() works properly (When I F5 my page, all is fine).
I also mentioned that if I change show value, the component will update instantly, that's OK.
That try also doesn't work:
{title: 'Login/Signup', to: '/login', show: ()=> !this.isLoggedIn},
{title: 'Log out', to: '/logout', show: ()=> this.isLoggedIn},
],
}
},
computed:{
isLoggedIn: function() {
return User.isLoggedIn();
},
My temp solution is using window.location = '/questions' instead of vue-router.
Maybe I need some watchers, or add User in my global Vue... I don't know.
Update: User class.
/**
* Class-helper for managing user login/signup part. Also represents the user.
*/
class AppUser {
constructor() {
this.storageKey = 'appUser';
}
/**
* retrieves user data from localStorage. if none stored, returns null
* #return {object}|{null}
*/
retrieve() {
let data = {};
try {
data = JSON.parse(localStorage.getItem(this.storageKey));
} catch (e) {
console.log(e);
} finally {
// console.log(data)
}
return data;
}
/**
* clears localStorageEntry
*/
clear() {
localStorage.removeItem(this.storageKey);
}
/**
* #return boolean
*/
hasId() {
let data = this.retrieve();
return data === null ? false : data.hasOwnProperty('id');
// if id then return true
}
/**
* #return boolean
*/
hasToken() {
let data = this.retrieve();
return data === null ? false : data.hasOwnProperty('jwt');
// if token then return true
}
isLoggedIn() {
// console.log('in user.isLoggedIn')
// try {
return this.hasToken() && this.hasId();
// }
// catch (e) {
// console.log(e);
// }
}
}
export default AppUser = new AppUser();
You can replace the original method of the User at "created" phase.
return {
data () {
return {
items: [
{ title: 'Questions', to: '/questions', show: true },
{ title: 'Ask question', to: '/askQuestion', show: true },
{ title: 'Categories', to: '/categories', show: true },
{ title: 'Login/Signup', to: '/login', show: !User.isLoggedIn() },
{ title: 'Log out', to: '/logout', show: User.isLoggedIn() },
]
}
},
created () {
let app = this;
let items = app.items;
let loginUser = User.login.bind(User);
let logoutUser = User.logout.bind(User);
User.login = () => {
// modify the data
items[3].show = false;
items[4].show = true;
// do login
loginUser();
};
User.logout = () => {
// modify the data
items[3].show = true;
items[4].show = false;
// do logout
logoutUser();
};
}
};

Ag-Grid cellRender with Button Click

I am using an angular 5 with ag-grid data table
i cant able to trigger a click event from cell using cellRenderer here how am using my ag-grid --> colDefs
this.columnDefs = [
{headerName: '#', rowDrag: true, width: 75},
{headerName: 'One', field: 'fieldName',
cellRenderer : function(params){
return '<div><button (click)="drop()">Click</button></div>'
}
}
];
drop() {
alert("BUTTON CLICKEFD")
}
if am using onClick="alert("123")" --> it works,
but i cant able to use onClick="drop()" it throws drop of undefined,
i tried this too inside of cellRenderer --> params = params.$scope.drop = this.drop;
if am using gridOptions with angularCompileRows : true it throws an error Cannot read property '$apply' of undefined.
Do i need to install ag-grid enterprise ??
You can use cellRenderer with a button component.
If you want to get the click event on the button from the user on the table, just declare the callback function you want to cellRendererParams.
// app.component.ts
columnDefs = [
{
headerName: 'Button Col 1',
cellRenderer: 'buttonRenderer',
cellRendererParams: {
onClick: this.onBtnClick.bind(this),
label: 'Click'
}
},
...
]
The above code is just a small part, check out full example on Stackblitz
Angular.
Here we create the button cell renderer as an Angular component that implements the ICellRendererAngularComp interface. Access to the params object can be found on the agInit hook.
// app/button-cell-renderer.component.ts
#Component({
selector: 'btn-cell-renderer',
template: `
<button (click)="btnClickedHandler($event)">Click me!</button>
`,
})
export class BtnCellRenderer implements ICellRendererAngularComp, OnDestroy {
private params: any;
agInit(params: any): void {
this.params = params;
}
btnClickedHandler() {
this.params.clicked(this.params.value);
}
ngOnDestroy() {
// no need to remove the button click handler as angular does this under the hood
}
}
The renderer is registered to ag-Grid via gridOptions.frameworkComponents. Note that we’re passing the button click handler dynamically to our renderer via cellRendererParams - allowing for a more flexible and reusable renderer.
// app/app.component.ts
this.columnDefs = [
{
field: 'athlete',
cellRenderer: 'btnCellRenderer',
cellRendererParams: {
clicked: function(field: any) {
alert(`${field} was clicked`);
}
},
minWidth: 150,
}
// [...]
];
this.frameworkComponents = {
btnCellRenderer: BtnCellRenderer
};
It is also necessary to pass our renderer to our #NgModule decorator to allow for dependency injection.
// app/app.modules.ts
#NgModule({
imports: [
BrowserModule,
FormsModule,
HttpClientModule,
AgGridModule.withComponents([BtnCellRenderer]),
],
declarations: [AppComponent, BtnCellRenderer],
bootstrap: [AppComponent],
})
See demo.
Learn more about Angular Cell Renderer.
Vanilla JavaScript.
A DOM element is created in the init method, which is then returned in the getGui method. The optional destroy hook has also included to do some cleanup (removing the click listener from our component).
// btn-cell-renderer.js
function BtnCellRenderer() {}
BtnCellRenderer.prototype.init = function(params) {
this.params = params;
this.eGui = document.createElement('button');
this.eGui.innerHTML = 'Click me!';
this.btnClickedHandler = this.btnClickedHandler.bind(this);
this.eGui.addEventListener('click', this.btnClickedHandler);
}
BtnCellRenderer.prototype.getGui = function() {
return this.eGui;
}
BtnCellRenderer.prototype.destroy = function() {
this.eGui.removeEventListener('click', this.btnClickedHandler);
}
BtnCellRenderer.prototype.btnClickedHandler = function(event) {
this.params.clicked(this.params.value);
}
The renderer is registered to ag-Grid in gridOptions.components and is used on the athlete column. Note that we’re passing the button click handler dynamically to our renderer via cellRendererParams - this makes for a more flexible and reusable renderer.
// main.js
var gridOptions = {
columnDefs: [
{
field: 'athlete',
cellRenderer: 'btnCellRenderer',
cellRendererParams: {
clicked: function(field) {
alert(`${field} was clicked`);
}
},
minWidth: 150
},
// [...]
components: {
btnCellRenderer: BtnCellRenderer
}
};
See demo.
Learn more about JavaScript Cell Renderers.
React.
Here our button cell renderer is constructed as a React component. The only thing to take note of here is that cell params will be available on the component via props.
// BtnCellRenderer.jsx
class BtnCellRenderer extends Component {
constructor(props) {
super(props);
this.btnClickedHandler = this.btnClickedHandler.bind(this);
}
btnClickedHandler() {
this.props.clicked(this.props.value);
}
render() {
return (
<button onClick={this.btnClickedHandler}>Click Me!</button>
)
}
}
The renderer is registered to ag-Grid via gridOptions.frameworkComponents. The button click handler is passed to our renderer at run time via cellRendererParams - allowing for a more flexible and reusable renderer.
// index.jsx
columnDefs: [
{
field: 'athlete',
cellRenderer: 'btnCellRenderer',
cellRendererParams: {
clicked: function(field) {
alert(`${field} was clicked`);
},
},
// [...]
}
];
frameworkComponents: {
btnCellRenderer: BtnCellRenderer,
}
See demo.
Learn more about React Cell Renderers.
Vue.js.
Configuring the renderer in Vue.js is simple:
// btn-cell-renderer.js
export default Vue.extend({
template: `
<span>
<button #click="btnClickedHandler()">Click me!</button>
</span>
`,
methods: {
btnClickedHandler() {
this.params.clicked(this.params.value);
}
},
});
As with the other frameworks, the renderer is registered to ag-Grid via gridOptions.frameworkComponents and the button click handler is passed to our renderer at run time via cellRendererParams - allowing for a more flexible and reusable renderer.
// main.js
this.columnDefs = [
{
field: 'athlete',
cellRenderer: 'btnCellRenderer',
cellRendererParams: {
clicked: function(field) {
alert(`${field} was clicked`);
}
},
// [...]
],
this.frameworkComponents = {
btnCellRenderer: BtnCellRenderer
}
See demo.
Learn more about Vue.js Cell Renderers.
Read the full blog post on our website or check out our documentation for a great variety of scenarios you can implement with ag-Grid.
Ahmed Gadir | Developer # ag-Grid
To expand on the answer from #T4professor, I will post some code to also have a dynamic label on that Click button.
// Author: T4professor
import { Component, OnInit, AfterContentInit } from '#angular/core';
import { ICellRendererAngularComp } from 'ag-grid-angular';
#Component({
selector: 'app-button-renderer',
template: `
<button class="{{btnClass}}" type="button" (click)="onClick($event)">{{label}}</button>
`
})
export class ButtonRendererComponent implements ICellRendererAngularComp {
//https://stackblitz.com/edit/angular-ag-grid-button-renderer?file=src%2Fapp%2Fapp.component.ts
params: any;
label: string;
getLabelFunction: any;
btnClass: string;
agInit(params: any): void {
this.params = params;
this.label = this.params.label || null;
this.btnClass = this.params.btnClass || 'btn btn-primary';
this.getLabelFunction = this.params.getLabelFunction;
if(this.getLabelFunction && this.getLabelFunction instanceof Function)
{
console.log(this.params);
this.label = this.getLabelFunction(params.data);
}
}
refresh(params?: any): boolean {
return true;
}
onClick($event) {
if (this.params.onClick instanceof Function) {
// put anything into params u want pass into parents component
const params = {
event: $event,
rowData: this.params.node.data
// ...something
}
this.params.onClick(params);
}
}
}
Then, in the component with the grid you do the following:
columnDefs = [
{
headerName: 'Publish',
cellRenderer: 'buttonRenderer',
cellRendererParams: {
onClick: this.onRowPublishBtnClick.bind(this),
label: 'Publish',
getLabelFunction: this.getLabel.bind(this),
btnClass: 'btn btn-primary btn-sm'
}
}
]
onRowPublishBtnClick(e) {
this.rowDataClicked = e.rowData;
}
getLabel(rowData)
{
console.log(rowData);
if(rowData && rowData.hasIndicator)
return 'Republish';
else return 'Publish';
}
You have this issue because you invoke drop() incorrectly you should change it to this.drop()
In general you should use cellRenderer property with simple logic. More convenient way for complex logic renderer you should use cellRendererFramework: YourCustomRendererAngularComponent.
columnDefs = [
{
headerName: 'Col Name',
cellRendererFramwork: MyAngularRendererComponent, // RendererComponent suffix it is naming convention
cellRendererParams: {
onClick: (params) => this.click(params);
}
},
...
]
MyAngularRendererComponent should implements AgRendererComponent.
Also in angular module where you use MyAngualrRendererComponent don`t forget put this code:
#NgModule({
imports: [
AgGridModule.withCompoennts([
MyAngualrRendererComponent
])
]
})
I was looking for a solution to this but for multiple buttons in the same column. I couldn't find an answer anywhere so I wrote up this Plain Javascript solution. I hope it helps other people looking for the solution I was looking for. Also open to suggestions on how to make the javascript less hacky.
// multi-btn-cell-renderer.js
function multiBtnCellRenderer() {}
multiBtnCellRenderer.prototype.init = function(params) {
var self = this;
self.params = params;
self.num_buttons = parseInt(this.params.num_buttons);
self.btnClickedHandlers = {};
let outerDiv = document.createElement('div')
for(let i = 0; i < self.num_buttons; i++) {
let button = document.createElement('button');
button.innerHTML = self.params.button_html[i];
outerDiv.appendChild(button);
self.btnClickedHandlers[i] = function(event) {
self.params.clicked[i](self.params.get_data_id());
}.bind(i, self);
button.addEventListener('click', self.btnClickedHandlers[i]);
}
self.eGui = outerDiv;
};
multiBtnCellRenderer.prototype.getGui = function() {
return this.eGui;
};
multiBtnCellRenderer.prototype.destroy = function() {
for(let i = 0; i < this.num_buttons; i++) {
this.eGui.removeEventListener('click', this.btnClickedHandlers[i]);
}
};
// main.js
var columnDefs = [
{
headerName: "Action",
maxWidth: 60,
filter: false,
floatingFilter: false,
suppressMenu: true,
sortable: false,
cellRenderer: multiBtnCellRenderer,
cellRendererParams: {
num_buttons: 2,
button_html: ["<i class='fa fa-pencil'></i>","<i class='fa fa-trash'></i>"],
get_data_id: function() {
return this.data.id;
},
clicked: {
0: function(data_id) {
$.get(`/employee/${data_id}/edit`)
},
1: function(data_id) {
$.delete(`/employee/${data_id}`)
}
}
}
}
]

Display datas on modal dialog in angular4 application

I have a angular 4 application and I want to display datas in dialog. So, I use #Output to pass data from child to parent component.
So, in the parent component I have :
export class DashboardComponent {
myTask;
public returnTask(task: any):void {
console.log("returnTask");
this.myTask = task;
console.log(this.myTask);
}
openDialogEditTask() {
console.log(this.myTask);
let dialogRef = this.dialogEditTask.open(DialogEditTask, {
//task
data: {
start: this.myTask.start,
end: this.myTask.end,
status: this.myTask.status,
user: this.myTask.user,
content: this.myTask.content,
id: this.myTask.id,
rate: this.myTask.rate
}
});
dialogRef.afterClosed().subscribe(result => {
this.selectedOption = result;
});
}
}
In the parent html, I have :
<visTimeline (myTask)="returnTask($event)"></visTimeline>
In the child component, I have :
export class VisTimelineComponent {
#Output() myTask: EventEmitter<any> = new EventEmitter<any>();
}
And I emit the task with self.myTask.emit(task);
So, I get the datas in the parent component (I can see them in the console) but I can't use them in openDialogEditTask because it's undefined.
So, do you know how can I get the datas before calling the function to have the datas in the dialog ?
EDIT :
This is my code to emit datas in child component :
ngOnInit() {
Timeline.prototype.onTaskDoubleClick = function(task) {
console.log("Double click on task " + task.id);
console.log(task);
$('#editTask').click();
self.myTask.emit(task);
};
}
Timeline.prototype.onTaskDoubleClick is a function from a library.
I think you are not able to pass data into you modal component. try with componentInstance method.
openDialogEditTask() {
console.log(this.myTask);
let dialogRef = this.dialogEditTask.open(DialogEditTask, {
height: '90%',
width: '80%'
});
dialogRef.componentInstance.myTaskValue = this.myTask; //<- passing data into DialogEditTask component
dialogRef.afterClosed().subscribe(result => {
this.selectedOption = result;
});
}
in your DialogEditTask declare a variable myTaskValue: any;
you will get all your value you pass into DialogEditTask component in this myTaskValue variable

Categories