Angular Highcharts hideNoData not working dynamically - javascript

I have trouble getting the hideNoData() and showNoData() to work with the official highcharts-angular component (https://github.com/highcharts/highcharts-angular).
Here is a basic example in stackblitz: https://stackblitz.com/edit/angular-highcharts-zvkcys?file=app%2Fapp.component.ts
This is a simplified version. In my real project I'm fetching data async, and while loading I show my custom (not the one highcharts provides) loading indicator. So initially I want to turn off the no Data Message but once the result comes back, depending on the result I might need to show the no Data Message.
I already tried using the [(update)]="updateFlag" on the chart component to trigger a change after calling hideNoData() but with no effect. Same goes for update(), reflow() or redraw().
I even manually called detectChanges to force a digest cycle but also with no effect.

With Highcharts, I find it's important for my components to know the charts reference, you can use the supplied Output callback, in highcharts-chart to do this. In your component you can do:
public callback = this.chartCallback.bind(this);
Where chartCallback is:
public chartCallback(chart: Highcharts.Chart)
{
this.chart = chart;
}
HTML will look like:
<highcharts-chart [Highcharts]="Highcharts" [options]="options
[callbackFunction]="callback">
</highcharts-chart>
This allows us to grab the actual reference of the Highchart object, and use it like:
public hideNoData(): void
{
this.chart.hideNoData();
}
But ofcourse, once you have the reference to the chart, you can use it freely within the component.
See here for a full working example.
Additionally, if you never want the no data message to show. Then this might be your best route:
this.options = {
title : { text : 'simple chart' },
series: [{
data: [],
}],
noData: null
};

ok I found the solution. Apparently Highcharts cannot handle it in the same digest cycle. if i call
setTimeout(() => {
chart.hideNoData();
});
It works and is correctly hidden. However now it briefly flashes in the beginning, the solution to that is disabling the automatic message completely. In the chart options add following:
chart: {
events: {
load(): void {
this.hasData = (): boolean => {
return true;
};
},
},
},

Related

[Vue.js 2][Google Places API] How to fix this error : "Uncaught TypeError: Cannot read property 'getPlacePredictions' of null"?

I would like to receive a boost as I have been stuck on this issue for quite some time. Thank you very much for your precious time.
Goal :
I would like to solve a problem. The problem is this error message in the console:
Results:
On a page where I use it, a redirect is done and redirects me to another page where I use it but I am no longer able to use the tool. It no longer offers me anything and shows me this error above in the console.
However, if I refresh the page (hard reload) or click on another page then click back to the profile settings page, then it works fine.
User flow
When a user signs up, he/she gets to fill a form (on a vue.js component) as part of an "onboarding" flow. On that specific form, the user is asked to add a place (via the v-autocomplete). To do so, I'm using the code (see below) under the script section of that component.
When the user is done, he is redirected to his/her profile settings page (again, another vue.js component) where he/she can again edit the place via the v-autocomplete. To do so, I'm using exactly the the code below(see below)under the script section of that component.
Note. Hence, the app vue.js create twice a google map instance.
My code :
The v-autocomplete :
<v-autocomplete
v-model="userPrefs.prefPlace"
:items="searchResults"
:search-input.sync="location"
hide-no-data
hide-selected
:placeholder="userPrefs.prefPlace"
item-text="Description"
item-value="API"
:error-messages="placeErrors"
#blur="$v.userPrefs.prefPlace.$touch()"
required: true
outlined
></v-autocomplete>
The metaInfo() :
metaInfo() {
return {
script: [
{
src: `https://maps.googleapis.com/maps/api/js?key=${process.env.VUE_APP_FB_APIKEY}&libraries=places`,
async: true,
defer: true,
callback: () => this.MapsInit() // will declare it in methods
}
]
};
}
The watch :
watch: {
location(newValue) {
this.timeOutID = debounce(() => {
if (newValue) {
this.service.getPlacePredictions(
{
input: this.location,
types: ["(regions)"],
componentRestrictions: { country: "be" }
},
this.displaySuggestions
);
}
}, this.timeOutID, 500);
}
}
The method MapsInit() and displaySuggestions() :
methods: {
MapsInit() {
this.service = new window.google.maps.places.AutocompleteService();
},
displaySuggestions(predictions, status) {
if (status !== window.google.maps.places.PlacesServiceStatus.OK) {
this.searchResults = [];
return;
}
this.searchResults = predictions.map(prediction => prediction.description);
}
};
So, would you know where the problem is coming from? If you are missing anything, please let me know, thank you!
The problem seems to be the Google Places script is not loaded (via the meta callback to MapsInit()) by the time the location watcher is called, so this.service is still its initial value (null).
One option is to load the script earlier in the app's lifecycle (e.g., in the App component, or within the head tag of index.html).
Another option is to conditionally render the v-autocomplete based on service so that the location watcher is only called when service is actually set:
<v-autocomplete v-if="service" :search-input.sync="location">
This assumes <v-autocomplete> is the only source of change to location.

ag-grid showing a custom overlay

Is there a way to show other custom overlays based on program logic? E.g. I'd like to have an "Error" overlay in addition to "No rows" and "Loading"
I have reviewed and implemented the customized "loading" and "no rows" overlays as described here: https://www.ag-grid.com/javascript-grid-overlay-component/ Super easy, really clean implementation.
Ideally, the api of showing an overlay could take a parameter that specifies which template to use...
You could have an overlay component which renders the different overlays based on the parameters you send.
You could accomplish this using ngIf in your template and rendering different html based on the parameter.
I don't think there is currently any other way to do it through Ag-Grid.
You can send it parameters for example by doing the following
[loadingOverlayComponent]="loadingOverlayComponent"
[loadingOverlayComponentParams]="loadingOverlayComponentParams"
this.loadingOverlayComponent = "customLoadingOverlay";
this.loadingOverlayComponentParams = { template: "overLay1" };
and in your component
export class CustomLoadingOverlay implements ILoadingOverlayAngularComp {
private params: any;
agInit(params): void {
this.params = params;
if (params.template == "overLay1") {
// do something
}
}
}
I know its not the best way to get this done but that works till we get something implemented into Ag-Grid

Submitting a post that belongs to current user - how to define the relation on client-side in Vue?

Im building an app using Node.js, specifically Express server-side and Vue client-side, with SQLite + Sequelize for managing the database.
Part of the functionality is that a user can make a post. This is currently possible but I needed to implement a relation so a post can be associated with the author.
I did this server-side in sequelize and all seems to be well on that end as the table columns all look correct with foreign key and references etc.
So now I need to somehow presumably set the current UserId for the post before it gets submitted. Here is the script element for the Vue component which is to be used as the interface to make posts.
<script>
import PostService from '#/services/PostService'
export default {
data () {
return {
post: {
title: null,
body: null
}
}
},
methods: {
async submit () {
try {
await PostService.post(this.post)
} catch (err) {
console.log(err.response.data.error)
}
}
}
}
</script>
I'm using Vuex to manage the state, where the 'user' object response from the database upon login is stored as simply user in my store.
So I was guessing all I had to do was the following to access the user:
post: {
title: null,
body: null
UserId: this.$store.state.user.id
}
The problem is any time I insert this line, the component stops working in some way. Doing as above stops the component displaying at all. So I tried setting it to null as default, then instead doing this inside my submit method:
this.post.UserId = this.$store.state.user.id
If I do this, the component displays but the submit method no longer runs; I also tried both these methods without the .id just incase, no difference.
I then tried removed from the data model completely, after reading that sequelize may be generating a setUser method for my post model. So tried the following in submit method before sending the post:
this.post.setUser(this.$store.state.user)
... again the method stops running. Again, even if adding .id.
Without trying to set any ID at all, everything works until the server returns an error that 'Post.UserID cannot be null', and everything worked perfectly before I implemented the relation.
I haven't really found anything useful beyond what I already tried from doing a few searches. Can anyone help out? I'm totally new to Node.
I have accessed other parts of the state such as isUserLoggedIn, and it all works fine and no crashes occur in those cases.
I just managed to get it working correctly, I'm not sure why it suddenly started working as I am sure I had tried this before, but I solved it by doing the following within my component's script element:
<script>
import PostService from '#/services/PostService'
export default {
data () {
return {
post: {
title: null,
body: null,
UserId: null
}
}
},
methods: {
async submit () {
this.post.UserId = this.$store.state.user.id
try {
await PostService.post(this.post)
} catch (err) {
console.log(err.response.data.error)
}
}
}
}
</script>
Posts now create and display as normal. If anyone knows anything I'm technicially not doing right in any of my approach though please let me know, as I am still learning Node, Express and Vue.

Why is Angular 2's template not updating from calls outside an angular zone?

I thought I would create a very simple login form with component-bound username and password properties that would run through the following steps:
Submit credentials with a fetch() call
THEN obtain the Response result object's JSON content
THEN check that content for the result of the server-side check
If the credentials were wrong, it would change a component property that would tell Angular to apply a class to the username and password inputs to make them red temporarily (using setTimeout to change that back)
The problem I ran into is that Angular would not correctly apply the class, and I wasn't sure why. I decided to create a simplified test project to narrow down the problem and ended up with the inclusion of system-polyfills/when.js being cause.
This code goes through 1, 2, then 3, and sets that both in the component property and outputs it to the debug console. Angular correctly renders the component property unless system-polyfill is included, in which case it will stop at 1 and never change it to 2 or 3, even though the console shows that the property is in fact changed:
export class App {
private stateIndicator:string = "0";
public showState(state:string) {
console.log('State: ' + state);
this.stateIndicator = state;
}
public showFetchProblem() {
this.showState('1')
fetch('http://www.google.com/robots.txt',{mode:'no-cors'}).then((response:any) => {
this.showState('2')
response.text().then((value) => {
this.showState('3')
});
});
}
}
I created the following Plunker to demonstrate this behaviour: http://plnkr.co/edit/GR9U9fTctmkSGsPTySAI?p=preview
And yeah, the obvious solutions are:
Don't manually include system-polyfills, or
Manually include a differeny Promise polyfill before SystemJS if you need it
But I'm still curious why this is happening, and hopefully somebody can shed some light on this (and possibly help to resolve the base issue).
Edit: The original title of this was Why is Angular 2's template rendering misbehaving when using system-polyfills (when.js). Thanks to Thierry and Günter for contributing and pointing out that Angular 2's use of zones is at play here. For anybody who comes across this in the future, here are two excellent articles that explain zones in further detail and will enhance your understanding of this scenario should you run into it:
http://blog.thoughtram.io/angular/2016/01/22/understanding-zones.html
http://blog.thoughtram.io/angular/2016/02/01/zones-in-angular-2.html
Promise polyfill provides a custom implementation of Promise that seems to be executed outside the scope of Angular2 (i.e. not in a zone).
If you execute your code explicitly within a zone managed by Angular2 (with the NgZone class), it works when the system-polyfill.js file is included.
Here is a sample:
constructor(private zone:NgZone) {}
public showFetchProblem() {
this.showState('1')
this.zone.run(() => {
fetch('http://www.google.com/robots.txt',{mode:'no-cors'}).then((response:any) => {
this.showState('2')
response.text().then((value) => {
this.showState('3')
});
});
});
}
See the corresponding plunkr: http://plnkr.co/edit/EJgZKWVx6FURrelMEzN0?p=preview.
update
Not sure about the explanation anymore but the workaround works. But I think there is some other underlying issue.
See also https://github.com/angular/angular/issues/7792#issuecomment-211217775
original
I assume the problem is caused by fetch not being patched by Angulars zone
To work around make the code execute inside Angulars zone manually
export class App {
constructor(private zone:NgZone) {}
private stateIndicator:string = "0";
public showState(state:string) {
console.log('State: ' + state);
this.stateIndicator = state;
}
public showFetchProblem() {
this.showState('1')
fetch('http://www.google.com/robots.txt',{mode:'no-cors'}).then((response:any) => {
this.showState('2')
response.text().then((value) => {
this.zone.run(() => this.showState('3'));
});
});
}
}
Plunker example

Why isn't my view re-rendered again as expected?

I created a custom control which includes a currentValue property. I defined it in metadata as below:
properties:
{
currentValue:
{
type: 'int',
defaultValue: 0
},...
in my Main.controller.js I'm calling the custom control that I created and changed it's currentValue property as below.
var oCustomControl = this.getView().byId("customID1");
oCustomControl.setCurrentValue(75);
in this step in my control.js, I didn't create a setCurrentValue function. Because I know UI5 is creating it itself. But the currentValue property of my control couldn't been updated. So I'm thinking my control couldn't been rerendered. So I overwrote the currentValue setter and change it as below:
setCurrentValue : function(iCurrentValue)
{
this.setProperty("currentValue", iCurrentValue);
},
But still I couldn't see the value which I changed in my view.
Here is my renderer:
renderer :
{
render : function(oRm, oControl) {
var layout = oControl.createGauges();//I created layout
oRm.write("<div");
oRm.writeControlData(layout);
oRm.writeClasses();
oRm.write(">");
oRm.renderControl(layout);
oRm.addClass('verticalAlignment');
oRm.write("</div>");
}
},
I am thinking now maybe it is because I'm rendering layout as a control?
and my other properties are related with d3.js. And I coded d3.js codes in my onAfterRendering function.
Actually your code looks fine.
Accept that you have specified:
bindable: 'bindable'
What should that do?
And what does rerender()?
The method to render a control is called renderer().
But you don't need to call it when you say:
this.setProperty("currentValue", iCurrentValue);
If you dont say true it will rerender the control.
It would be nice to know whats inside your renderer() function, or what happens when you call getCurrentValue().
It is solved by changing
oRm.writeControlData(layout); this line as follow:
oRm.writeControlData(oControl);

Categories