Load data before rendering subcomponent - javascript

I have a parentComponent called dashboardComponent. This contains a table.
When selecting a row in that table I display it's subcomponent, which is companyAdmin, and I pass the rowID to it.
CompanyAdmin on it's turn contains 4 subComponents. The task of the companyAdminComponent is to do a GET request with the rowId it received from it's parentComponent, the dashboardComponent. Then send distribute that data to it's 4 subComponents.
The problem I am having is, this is all on 1 page and so can't use a resolver. So when I am rendering the subComponent of companyAdminComponent it crashes as it didn't receive the data back yet from the GET request. and getting the error
Cannot read property 'name' of undefined
So basically how do I show the subComponent of companyAdmin, only after the data has been loaded.
The code
dashboard.html (Parent component)
Only showing the relevant code for brevity
// ap-company-admin only is being shown when a row is selected in the table
<div class="row extraRowSpace" *ngIf="companySelected">
<div class="col-xl-12">
<ap-company-admin [companyId]="company.id"></ap-company-admin>
</div>
</div>
CompanyAdmin HTML (subComponent of dashboardComponent)
<ap-company-details [companyDetails]="companyDetails"></ap-company-details>
CompanyAdminComponent TS (subComponent of dashboardComponent)
ngOnInit() {
this.companyService.getAllCompanyDetails(this.companyId).subscribe(
response => {
this.allCompanyDetails = response;
this.companyDetails = {
cardsName: this.allCompanyDetails.cardsName,
name: this.allCompanyDetails.name
};
}
);
}
CompanyDetailsComponent (subComponent of companyAdminComponent)
#Input() companyDetails;
companyName: FormControl;
cardsName: FormControl;
ngOnInit() {
this.companyName = new FormControl(this.companyDetails.name);
this.cardsName = new FormControl(this.companyDetails.cardsName);
// Basically it is crashing here because on init of this component the data of companyDetails hasn't come back yet from the GET call
}
All the things I found are things to do with resolver, but as I am not navigating but only show and hiding the companyAdminComponent when selecting a row, I don't think I can do this with a resolver. Any advice?

Related

Saving Values to Backend from TextBoxes using React Flux Pattern

I have several text boxes and a save button
Each text box value is loaded using the following approach
{
this.getElement('test3lowerrangethreshold', 'iaSampling.iaGlobalConfiguration.test3lowerrangethreshold',
enums.IASamplingGlobalParameters.ModerationTest3LowerThreshold)
}
private getElement(elementid: string, label: string, globalparameter: enums.IASamplingGlobalParameters): JSX.Element {
let globalParameterElement =
<div className='row setting-field-row' id={elementid}><
span className='label'>{localeHelper.translate(label)}</span>
<div className="input-wrapper small">
<input className='input-field' placeholder='text' value={this.globalparameterhelper.getDataCellContent(globalparameter, this.state.globalParameterData)} />
</div>
</div>;
return globalParameterElement;
}
Helper Class
class IAGlobalParametesrHelper {
public getDataCellContent = (globalparameter: enums.IASamplingGlobalParameters, configdata: Immutable.List<ConfigurationConstant>) => {
return configdata?.find(x => x.key === globalparameter)?.value;
}
}
This works fine. Now the user is allowed to update these text values.And on click of save the changes should be reflected by calling a web api .
I have added an onlick event like this
<a href='#' className='button primary default-size' onClick={this.saveGlobalParameterData}>Save</a>
Now inorder to save the data i need a way to identify the text element which has changed.For that i have added an update method within the Helper class
public updateCellValue = (globalparameter: enums.IASamplingGlobalParameters, configdata: Immutable.List<ConfigurationConstant>,updatedvalue:string) => {
let itemIndex = configdata.findIndex(x => x.key === globalparameter);
configdata[itemIndex] = updatedvalue;
return configdata;
}
and return the updated configdata ,and i plan to call this method in the onchange event of every text box like this
<input className='input-field' placeholder='text' onchange={this.setState({ globalParameterData: this.globalparameterhelper.updateCellValue(globalparameter, this.state.globalParameterData, (document.getElementById(elementid) as HTMLInputElement).value})}
But this does not seem like a correct approach as there are number of syntactical errors. I initially got the data using an actioncreator like this.Please advice.
samplingModerationActionCreator.getGlobalParameters();
samplingModerationStore.instance.addListener(samplingModerationStore.SamplingModerationStore
.IA_GLOBAL_PARAMETER_DATA_GET_EVENT,
this.getGlobalParameterData);
}

Vue getter returns undefined when page reload

I have a blog with some posts. When you click on the preview you will redirect on the page post.
On the page of the post, I use a getter to load the correct post (I use the find function to return object.name which corresponds to the correct object in the array of objects).
const state = {
ricettario: [], // data that contains all recipes (array of objects)
}
const actions = {
// Bind State and Firestore collection
init: firestoreAction(({ bindFirestoreRef }) => {
bindFirestoreRef('ricettario', db.collection('____').orderBy('data'))
})
const getters = {
caricaRicetta(state) {
console.log('Vuex Getter FIRED => ', state.ricettario)
return nameParamByComponent => state.ricettario.find(ricetta => {
return ricetta.name === nameParamByComponent
})
}
}
In the component, I call the getter in the computed property
computed: {
...mapGetters('ricettaStore', ['caricaRicetta']),
ricetta() {
return this.caricaRicetta(this.slug) // this.slug is the prop of the URL (by Router)
}
}
Anything goes in the right way but when I reload the page in the POST PAGE, the getter will fire 2 times:
1. return an error because the state is null
2. return the correct object
// screen below
So everything works fine from the front but not at all in the console and in the App.
I think the correct way is to call the getters in the created hook. What I've to change? It is a problem with the computed prop, getters or state?
POST PAGE:
<template>
<div v-if="ricetta.validate === true" id="sezione-ricetta">
<div class="container">
<div class="row">
<div class="col s12 m10 offset-m1 l8 offset-l2">
<img
class="img-fluid"
:src="ricetta.img"
:alt="'Ricetta ' + ricetta.titolo"
:title="ricetta.titolo"
/>
</div>
</div>
</div>
</div>
<div v-else>
...
</div>
</template>
You are trying to validate undifined property. So you need to check ricetta first.
Try like this:
<div v-if="ricetta && ricetta.validate === true" id="sezione-ricetta">
Database synchronization is asynchronous, ricettario is initially an empty array. Computed value is recomputed once synchronization is finished and ricettario array is filled, the component is updated.
Even if ricettario weren't empty, find may return undefined if it finds nothing. This needs to be handled where ricetta is used:
<div v-if="ricetta && ricetta.validate" id="sezione-ricetta">
The error log is quite explicit, there is a xxx.validate somewhere in your Ricetta component template, but that xxx is undefined.
Because of this, your app crashes and stops working. I doubt it has anything to do with Vuex

Angular - Update data automatically without Refresh the Page

In my application there is a table that get rows from database.
This is the AJAX CALL (SERVICE)
getPosts(): Observable<Posts[]> {
return this.http.post<Posts[]>(this.myAppUrl + this.myApiPostsUrl, this.authService.getLoggedUserFromSessionStorage())
.pipe(
retry(1),
catchError(this.errorHandler)
);
}
All work perfectly, but my datas dont update automatically, and the user need to refresh the page to see the new rows, how can i do this?
I would like do that the new rows are added in the table dynamically ... without update the page.
This is the table
COMPONENT HTML
<table *ngIf="(posts$ | async)?.length>0" class="table align-items-center table-flush">
.....
<tr *ngFor="let post of (posts$ | async) | filter:authService.filter | paginate: config | orderBy: key : reverse">
<!-- <td>{{posts.id}}</td>-->
<td>{{post.title}}</td>
<td>{{post.body}}</td>
.....
</table>
COMPONENT TS
ngOnInit() {
this.loadPosts();
}
loadPosts() {
this.message = 'Loading....';
this.posts$ = this.postService.getPosts();
if (this.posts$ == null) {
this.message = 'No Posts found';
}
}
Thanks so much.
There are several options. Here is a reactive way of handling this. Any time getPosts is successful, you'll need to refetch the initial data.
To fetch your initial data you will need to wrap your posts$ observable in an action stream:
// create a stream for your post request
private readonly postAction$ = new Subject();
posts$ = this.postAction$.pipe(
startWith(''),
concatMap(()=> {
return this.postService.getPosts(); // this will be your http get request
}),
)
The startWith operator will cause your get request to fire initially without an observable been passed to your postAction observable.
Your getPosts method now will call this.postAction$.next() on success, that will trigger the refetch of your posts$ observable.
getPosts(): Observable<Posts[]> {
return this.http.post<Posts[]>(this.myAppUrl + this.myApiPostsUrl, this.authService.getLoggedUserFromSessionStorage())
.pipe(
retry(1),
catchError(this.errorHandler),
tap(() => this.postAction$.next())
);
}
You can see a demo of this. Check the console, you'll see that the get request is fired every time the button is clicked.
With Interval
posts$ = interval(30000)
.pipe(
startWith(''),
switchMap(() => return this.postService.getPosts();)
)
Interval demo
in Angular, when you want to update your html, you need to use ngFor to update your data automatically.
<table *ngIf="(posts$ | async)?.length>0" class="table align-items-center table-flush">
<div *ngFor="let data of datas; let i = index>
{{ data }}
</div>
</table>
ngFor will loop on datas and update it when it changes

Invoking a ViewComponent within another ViewComponent

I am currently coding within a ViewComponent (ViewComponent1) view. Within this View, I have listed a few items:
As you can see, the channels 11, 12, 13 and 14 are clickable. Each channel has some additional information (OBIS, avtalsid.. etc). What I´m trying to do is to invoke ViewComponent2, within ViewComponent1, and pass along some of the data, based on the clicked item.
What I tried to do is to create another View called "Test" and within that View invoke ViewComponent2 along with its parameters, like this:
<div class="row">
<div class="col-md-2 canalstyle">
<a asp-controller="Customer" asp-action="Test" asp-route-pod="#item.STATION"
asp-route-idnr="#item.IDNR" asp-route-kanal="#item.KANAL" asp-route-start="#Model.start"
asp-route-end="#Model.end"> #Html.DisplayFor(modelItem => item.KANAL)</a>
</div>
</div>
This works, but this method redirects me away from my current View (ViewComponent 1). I don't want that. I want the current view to load the additional information from ViewComponent2.
My function that runs the ajax:
function myFunction() {
var data = JSON.stringify({
'idnr': id,
'start': this.start,
'end': this.end
});
$.ajax({
url: '#Url.Action("Test2","Customer")',
type: 'GET',
data: { idnr: id, start: this.start, end: this.end },
contentType: 'application/json',
success: handleData(data)
})
};
function handleData(data) {
alert(data);
var url = $(this).attr("href");
var $target = $(this).closest("div").find(".details");
$.get(url, function (res) {
$target.html(res);
});
//do some stuff
}
And my Test2 Action:
public async Task<IActionResult> Test2(string idnr, string start, string end)
{
ServiceClient r2s = new R2S.ServiceClient();
R2S.Konstant[] kData = r2s.GetKonstantListAsync(new string[] { "IDNR" }, new string[] { idnr}).Result; // mätarnummer in... --> alla konstanter kopplade till denna.
return ViewComponent("MeterReader2", new { k = kData[0], start = start, end = end });
}
I am trying to target the same DOM.. Any ideas?
Your current code is rendering links (a tags) and normally clicking on a link will do a new GET request, which is what you are seeing , the redirect to the new action method.
If you do not want the redirect, but want to show the result of the second view component in same view, you should use ajax.
For example, If you want to show the result of second view component just below each link, you may add another html element for that. Here i am adding an empty div.
<div class="row">
<div class="col-md-2 canalstyle">
<a class="myClass" asp-controller="Customer" asp-action="DetailsVc"
asp-route-id="#item.Id" > #item.KANAL</a>
<div class="details"></div>
</div>
</div>
Here i just removed all those route params you had in your orignal question and replaced only with on param (id) . Assuming your items will have an Id property which is the unique id for the record(primary key) and using which you can get the entity (from a database or so) in your view component to get the details.
This will generate the link with css class myClass. You can see that, i used asp-action attribute value as "DetailsVc". We cannot directly use the view component name in the link tag helper as attribute value to generate the href value. So we should create a wrapper action method which returns your view component result such as below
public IActionResult DetailsVc(int id)
{
return ViewComponent("DetailsComponent", new { id =id });
}
Assuming your second view components name is DetailsComponent and it accepts an id param. Update the parameter list of this action method and view component as needed. (but i suggest passing just the unique Id value and get details in the server code again)
Now all you have to do is have some javascript code which listen to the click event on those a tags and prevent the normal behavior (redirect) and make an ajax call instead, use the ajax call result to update the details div next to the clicked link.
You can put this code in your main view (or in an external js file without the #section part)
#section Scripts
{
<script>
$(function() {
$("a.myClass").click(function(e) {
e.preventDefault();
var url = $(this).attr("href");
var $target = $(this).closest("div").find(".details");
$.get(url,function(res) {
$target.html(res);
});
});
});
</script>
}

AngularJS - Get printed value from scope inside an attribute?

I'm currently working on an AngularJS project and I got stuck in this specific requirement.
We have a service that has all the data, DataFactoryService. Then, I have a controller called DataFactoryController that is making the magic and then plot it in the view.
<div ng-repeat = "list in collection">
{{list.name}}
...
</div>
Now, we have a requirement that pass multiple data into one element. I thought an "ng-repeat" would do, but we need to have it inside an element attribute.
The scenarios are:
At one of the pages, we have multiple lists with multiple data.
Each data has a unique code or ID that should be passed when we do an execution or button click.
There are instances that we're passing multiple data.
Something like this (if we have 3 items in a list or lists, so we're passing the 3 item codes of the list):
<a href = "#" class = "btn btn-primary" data-factory = "code1;code2;code3;">
Submit
</a>
<a href = "#" class = "btn btn-default" data-factory = "code1;code2;code3;">
Cancel
</a>
In the example above, code1,code2,code3 came from the list data. I tried several approach like "ng-repeat", "angular.each", array, "ng-model" but I got no success.
From all I've tried, I knew that "ng-model" is the most possible way to resolve my problem but I didn't know where to start. the code below didn't work though.
<span ng-model = "dataFactorySet.code">{{list.code}}</span>
{{dataFactorySet.code}}
The data is coming from the service, then being called in the controller, and being plot on the HTML page.
// Controller
$scope.list = dataFactoryService.getAllServices();
The data on the list are being loaded upon initialization and hoping to have the data tags initialized as well together with the list data.
The unique code(s) is/are part of the $scope.list.
// Sample JSON structure
[
{ // list level
name: 'My Docs',
debug: false,
contents: [ // list contents level
{
code: 'AHDV3128',
text: 'Directory of documents',
...
},
{
code: 'AHDV3155',
text: 'Directory of pictures',
...
},
],
....
},
{ // list level
name: 'My Features',
debug: false,
contents: [ // list contents level
{
code: 'AHGE5161',
text: 'Directory of documents',
...
},
{
code: 'AHGE1727',
text: 'Directory of pictures',
...
},
],
....
}
]
How can I do this?
PLUNKER -> http://plnkr.co/edit/Hb6bNi7hHbcFa9RtoaMU?p=preview
The solution for this particular problem could be writing 2 functions which will return the baseId and code with respect to the list in loop.
I would suggest to do it like below
Submit
Cancel
//inside your controller write the methods -
$scope.getDataFactory = function(list){
var factory = list.map( (a) => a.code );
factory = factory.join(";");
return factory;
}
$scope.getDataBase= function(list){
var base= list.map( (a) => a.baseId);
base= base.join(";");
return base;
}
Let me know if you see any issue in doing this. This will definitely solve your problem.
You don't really have to pass multiple data from UI if you are using Angular.
Two-way data binding is like blessing which is provided by Angular.
check your updated plunker here [http://plnkr.co/edit/mTzAIiMmiVzQfSkHGgoU?p=preview]1
What I have done here :
I assumed that there must be some unique id (I added Id in the list) in the list.
Pass that Id on click (ng-click) of Submit button.
You already have list in your controller and got the Id which item has been clicked, so you can easily fetch all the data of that Id from the list.
Hope this will help you... cheers.
So basing from Ashvin777's post. I came up with this solution in the Controller.
$scope.getFactoryData = function(list) {
var listData = list.contents;
listData = listData.map(function(i,j) {
return i.code;
});
return listData.join(';');
}

Categories