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
Related
I am having a project requirement on Angular. I am using search option where I am able to search data from external api and display user below it when clicking on Add button. I tried implementing using angular primeng autocomplete.
But problem is that as per the screenshot here https://imgur.com/a/wXYqOpM
When I clicked on Upload List, all the user data displayed should be uploaded with a button click (either as file or array). Since I am new to angular, can you help me to find a suitable solution for the same?
Thanks in advance
Patric
There's a bit more input needed in order to properly, entirely answer your question. I'll just make a couple assumptions to do so.
I suppose that you're having an array of patients/users in your .ts file and display that data in your .html using data interpolation. Something like this:
upload-list.component.ts:
...
public userList = ['Patient 1', 'Patient 2', 'Patient 3']
...
upload-list.component.html:
...
<your-list-display>{{ userList }}</your-list-display>
<button (click)='sendData()'>Upload list</button>
Eventually, all you need to do is inject the HttpClient (imported from #angular/common/http) into your component's constructor and use it (preferably in a service, read up in the angular.io docs for that specifically, how to share data between components etc).
Roughly outlined, something like this shall work:
upload-list.component.ts:
import { HttpClient } from '#angular/common/http';
...
export class UserList {
constructor(private http: HttpClient) {}
public sendData(): void {
this.http.post<any>(apiURL, this.userList).subscribe(result => {
... // do something with the result from your HTTP request.
}, error => {
... // handle error as wished
});
}
}
Hope that'll help - Regards
EDIT - moved API call into function which'll be called upon button click
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;
};
},
},
},
I have a variable that stores the available cars at any moment. Is there a way to automatically re-evaluate this function on every change?
Just using this.carFactory.available in this case is not a solution, because this example I'm showing is simplified - the real calculation in my project is alot more complex.
calculateAvailableCars(){
this.carFactory.available.forEach(function(item){
this.availableCars.push(car.id);
}.bind(this));
}
How could I do this in Angular 2? In Angular JS there was the possibility to $watch a function.
I could of course manually call this function everytime something changes, but it would be nice not to have to call this function in every part of the application that can change the data.
Using template function reference with auto change detection
You can use this function output on template:
carOutput(): cars[] {
this.calculateAvailableCars()
return this.availableCars;
}
and use output on template:
<p>My car ratio is {{ carOutput() }} </p>
However this will trigger very aggressive change detection strategy on this variable. This solution is the simpliest one, but from engineering perspective rather worst: consumes tons of unnecessary function calls. One note, that hosting element must not be set to detect changes onPush.
Separate data model to parent component and pass as property to child
You can store car list display in separate component, and pass new car array as input property to this component:
<car-display [cars]="availableCars"></car-display>
Then you can set changeDetetcion policy in this component to onPush, and each time input property bind to availableCars will change, <car-display> will re-render.
If update relays on some host binding
If some external host action is triggering new cars calculation, then hostBinding may help:
#hostListener(`hover`) recalculateCars() {
this.calculateAvailableCars()
}
And finally, (because you describe your use case quite cryptically, without many details, thus I'm scratching all possible scenarios) if some external component action shall trigger re-calculation, you can hook to ngLifecycle ngOnChanges() if for example external input property change shall re-trigger cars calculation.
In other words and summing all that up, it depends who and from where triggers changes, that shall re-trigger available cars recalculation.
And very important, see an answer from #chiril.sarajiu, because what we are trying to work around here can be handled automatically by single observable. This requires additional setup (service, provide observable to components, e.c.t.) but it's worth.
--- EDIT ---
If each variable change shall retrigger data
As OP clarified, that changes are related with model bound to component. So another option with mentioned by #marvstar is using set, where each model variable change will retrigger fetching function:
modelSchangeSubject: Subject<Model> = new Subject<Model>();
ngOnInitt() {
this.modelSchangeSubject
.subscribe((v: Model) => {
this.calculateAvailableCars()
})
}
/* Rest of controller code */
set modelBounded(v: Model) {
this.modelSchangeSubject.next(v);
}
You need RxJS. What you do is you create a data service, which will store an Observable (in my case a BehaviorSubject, which is mostly the same, but in my case I start with a value).
export class DataService {
private dataStorage$ = new BehaviorSubject(null); //here is the data you start with
get getDataStorage() {
return this.dataStorage$.asObservable(); // so you won't be able to change it outside the service
}
set setDataStorage(data: any) {
this.dataStorage$.next(data);
}
}
Then you subscribe to this data changes everywhere you need to:
constructor(private dataService: DataService){}
ngOnInit() {
this.dataService.getDataStorage.subscribe((data) => this.calculateAvailableCars(data));
}
calculateAvailableCars(){
this.carFactory.available.forEach(function(item){
this.availableCars.push(car.id);
}.bind(this));
}
Read more about best practices of using RxJS in Angular, as there can be quite a bit of pitfalls and problems.
Try using setter and getter.
private _YourVariable:any;
public set YourVariable(value:any){
this._YourVariable = value;
//do your logik stuff here like. calculateAvailableCars
}
public get YourVariable():any{
return this._YourVariable ;
}
I have got 2 components, let's say, Component A is a list view and Component B is a details view. Each row from the list view is clickable and will redirect to Component B upon clicking.
Component B allows editing and saving the details. I have added a Back button to Component B to allow me to go back to the list view.
But the problem I am having is that I can't see the updated list view and have to manually refresh the browser, and then I can see the updated list there.
I have tried directly using window.location and it works but really I don't prefer this approach.
public back() {
window.location.assign('/listview');
}
I wonder if there's any better way to solve this problem?
Update:
public onSelected(model: MyModel) {
const detailsViewUrl = `/detailsview/${model.id}`;
this._router.navigateByUrl(detailsViewUrl );
}
You can just emit an #Output EventEmitter with a method on Parent that looks in the event for a change with a variable stored in the component like this:
#Output someOutput: EventEmitter = new Event Emitter<any>;
HTML:
<b-component (someOutput)=getOutput($event)></b-component>
AComponent:
getOut(event){
let output = event;
if(this.something != output){
this.ngOnDestroy(); // or something that you can use to make it
}
That should work as intended.
It sounds like this is an issue with Angular's change detection when changing the contents of an array. See here:
Angular 2: How to detect changes in an array? (#input property)
The solutions in this questions should work but an easy way I have used in the past to force changes in an array to be recognised by Angular is to reassign the array after making the changes:
myArray = [...myArray];
use following routing fuction on back button click
public back() {
this._router.navigateByUrl('/listview')
}
or
public back() {
this._router.navigate('/listview')
}
Try this,
Just called the list view again internally and hit db at same time so updated values will be displayed in the list view.
calling the route by using below:
this.router.navigate(['/listview']);
Seems like a change detection issue, there are some ways to manually trigger change detection like so:
Inject ChangeDetectorRef.
Call it when you go back like so:
public back() {
ChangeDetectorRef.detectChanges()
}
Refer to this: Triggering change detection manually in Angular
In Sitecore you can easily render a context item's value by using
#RenderingContext.Current.Rendering.Item["itemname"]
However: when I place a Controller Rendering to a placeholder there is no content assigned yet. therefore nothing will be rendered, and therefore I can't check for null.
My specific problem:
I want to add the value of a database item to the parameters of a call.
var options = {{lat: #RenderingContext.Current.Rendering.Item["lat"],
lng: #RenderingContext.Current.Rendering.Item["lng"]},
zoom: #RenderingContext.Current.Rendering.Item["zoom"]};
As there is simply NOTHING (yet) the partial view fails to load because there is no associated content (yet) ==> javascript error. If there is content it works fine.
So how can I
Add a default associated content item to a controller rendering (mhhhh)
Check if ?NOTHING? is null? (better)
Any idea?
Anyone who's got my problem?
Cheers!
You have a few options available to you, I would suggest a combination of things depending on your needs but you also want to be defensively coding in all aspects since content authors have the potential to do things that they should not!
You can set some default values for your templates so that they always have an initial value. With your template selected and the Builder tab highlighted, select the Options tab from the ribbon and then add Standard Values
A new item called __Standard Values will be added under the template, you can set default values there. Make sure you have set the Datasource Location and Datasource Template fields on your rendering which will cause the prompt to create/select datasource item.
You still need to defensively code though. How you do this is up to you. For example, if certain fields are not set correctly then maybe you do not show the component at all or return a different view to show it is incorrect:
public class WidgetController : GlassController
{
public ActionResult Index()
{
var configItem = GetDataSourceItem<ILocationConfiguration>();
if (configItem.Longitude == null && configItem.Latitude == null)
return PartialView("~/Views/Components/Shared/IncorrectSettings.cshtml");
return PartialView("~/Views/Components/Widget/Index.cshtml", configItem);
}
}
(The above sample is using Glass Mapper, I know you are not using it but I would highly recommend it or to use strongly types models in any case. The example still stands though.)
You can also make some checks in the View itself, although I wouldn't put too much code in there myself. Depending on the component, sometimes we do not show the rendered component in Experience Editor mode. The sample below allows the values to be edited in EE mode but renders the script block and component if values have been set:
#model Sitecore.Mvc.Presentation.RenderingModel
#if (!Model?.Item?.TemplateID=="guid" ?? true)
{
#Html.Raw("<div class=\"error\">Incorrect Datasource</div>")
return;
}
#if (Sitecore.Context.PageMode.IsExperienceEditor)
{
<!-- This allows component settings to be edited in EE mode -->
<div>
Longitude: #Html.Sitecore().Field("lng", Model.Item)
Latitude: #Html.Sitecore().Field("lat", Model.Item)
Zoon: #Html.Sitecore().Field("zoom", Model.Item)
</div>
}
else
{
string lng = Model.Item["lng"],
lat = Model.Item["lat"],
zoom = Model.Item["zoom"];
if (!string.IsNullOrEmpty(lng) && !string.IsNullOrEmpty(lat) && !string.IsNullOrEmpty(zoom))
{
<script>
var options = { lat: #lat, lng: #lng, zoom: #zoom }
</script>
<div>
set up the component in normal mode
</div>
}
}
There are lots of different ways to achieve the above including making checks in the JavaScript itself and how you invoke the code for your component, but I've tried to keep it self-contained and simple for example.