I am having an issue with a modal binding ( parent to child )
I have an array which I fill from a web service.
The Array is then passed to modal.
The array is empty on first call, but not on following calls and on click I get [n-1] index value everytime instead of [n] index value.
What can be the reason for this ?
Child Component
car-detail.html
<div class="modal-body">
<input [(ngModel)]="car.Id" type="number">
<input [(ngModel)]="car.make" type="string">
</div>
Car-detail-component.ts
export class CarDetailComponent implements OnInit {
#Input() public car;
constructor(private appService: AppService,
public activeModal: NgbActiveModal,
private route: ActivatedRoute,private location: Location) { }
ngOnInit() {
}
Parent Component
car-component.html
<label *ngFor="let car of carslist" (click)= "getCarDetailsById(car.id);open()">
{{car.make}}
<br/>
</label>
I get the id and pass it to a web service call and I open the modal.
**car.component.ts**
carDetailsList : any =[];
public getCarDetailsById(id:number):Car[]{
this.appService.getCarDetailsById(id).subscribe(
car =>{
this.carDetailsList=car;
for (let i = 0; i < this.carDetailsList.length; i++) {
console.log(car.make);
}
}
);
return this.carDetailsList;
}
On first call, array is empty, not on subsequent calls.
I get the previous car details each time when the modal opens.
Thanks for the help.
You are opening the modal before your service returns the value. You should open the model inside the subscribe method. Thats why you are not getting the value first time, on subsequent calls you always gets the old value. Remove the open() from your parent html template.
public getCarDetailsById(id:number):Car[]{
this.appService.getCarDetailsById(id).subscribe( car =>{
this.carDetailsList=car;
for (let i = 0; i < this.carDetailsList.length; i++) {
console.log(car.make);
}
this.open();
return this.carDetailsList;
});
}
Your problem is actually that you execute a method asynchronous (the subscription is only executed when the server return the data asked) into a function executed synchronously so your function will end and reach the return before the subscription execution is reached that's why when your modal will pop up the array will be empty on the first call.
public getCarDetailsById(id:number):Car[]{
this.appService.getCarDetailsById(id).subscribe(
// 1 when the server return data the code here is executed
car =>{
this.carDetailsList=car;
for (let i = 0; i < this.carDetailsList.length; i++) {
console.log(car.make);
}
}
);
// until server return data the execution continue so the return statement is reached before the code after point 1 is executed
// the variable returned will be null in that case.
return this.carDetailsList;
}
try instead to make like this:
async getCarDetailsById(id:number):Promise<Car[]> {
var asyncResult = await this.appService.getCarDetailsById(id).toPromise();
console.log('this console.log will be executed once the async call is fetched and the value of the returned promise is stored in asyncResult');
return asyncResult;
}
then in the returned promise in the .then function bind the returned value in one of your value of type Car[].
and you call the 'getCarsDetailsRecovered' function when you open your modal.
an usefull link about asynchronous calls in angular: link.
Related
Angular js function updating some record. After updating record i am calling search method to show data on view.
But record does not updated before that search method call that does not get data so show null on view.
I have separate button for search on its ng-click this search method call. After some second if i click that button it shows data on view.
my code is,
vm.Update = function (value)
{
var test = value;
searchCriteria = {
From: vm.From,
To: vm.To,
Region: vm.Region,
City: vm.SelectedCity
}
surveyService.UpdateVisit(searchCriteria,value).then(function (d) {
var Confrm = JSON.parse(d.data.data);
if (d.data.status) {
toastr.success(Updated, {
autoDismiss: false
});
}
else {
toastr.error(errorMsg);
}
});
vm.searchVisit(0);
}
This searchvisit call and service unable to update data in database so i do not get any record on view. When i call this searchvisit method from separate button for searching it shows record with updated data.
Hopes for your suggestions how to pause execution before calling searchvisit method or any alternative that it gets any response than move execution control to searchvisit method.
Thanks
This is due to the asynchronous nature in JS.
From your code, surveyService.UpdateVisit(searchCriteria,value) returns a promise. Thus, when vm.searchVisit(0); is called, surveyService.UpdateVisit(searchCriteria,value) has not been resolved yet, meaning updating is still in progress and have not been completed. There for vm.searchVisit(0); shows records that are not updated.
If your second function is dependent on the values of the first function call, please add it as shown below inside the success callback.
surveyService.UpdateVisit(searchCriteria,value).then(function (d) {
var Confrm = JSON.parse(d.data.data);
if (d.data.status) {
toastr.success(Updated, {
autoDismiss: false
});
}
else {
toastr.error(errorMsg);
}
//Add this here.
vm.searchVisit(0);
});
I want to update my data based on data received from back-end
there are two functions:
function getData(data){
Service.getRequest(url).then(function (_response) {
console.log(_response)
var res = _response
if(res.data.success) {
$scope.$watch('data.photo', function() {
alert('hey, myVar has changed!');
});
data.photo = res.data.result;// this will tell that Image received against the corresponding data
}
else {
ctrl.errorCallBack = true
ctrl.errorFunction = 5
}
$rootScope.showPreloader = false;
});
}
now I have second function that receives the selected data and called when clicked on button , now question is can I update the data inside this function when above Get request is completed:
function syncData(data) {
console.log('image received')
console.log(data)
}
First function is called n times based on number of images , now user click on second function to view Image but user don't see any Image because Image is not received yet but I want a way to update second function as Image is received
examples
ist image is loaded....user doesn't click on second function
2nd image is pending....user clicks on second function or second function is active
2nd image is received....active function image received
https://plnkr.co/edit/zKeDT84W6eF9cc3FpcmG?p=preview
If you use $scope for variables they will be automatically sync with DOM whenever it changes.
I did some changes for you in plnkr i hope solve your problem.
http:// plnkr.co/edit/Ug53a03kB7Ap1EWf2jZK?p=preview
I have two components in my page. Component A called Dashboard and contains a list of elements via ng-repeat, let's call it systems. Each system will be displayed via a component B, a component especially for this system element it's called SystemView. Each system view has a list of log entries and what i now want, is to display a badge with the amount of unread log entries.
If i open a system entry i'm able to set a log entry to read, wo the number of unread log entries should change.
For your understanding: system.logEntries is a list of log entries.
HTML
<some-accordion-control>
<div ng-repeat="system in systems">
<h3>{{system.name}} <span class="badge">{{system.unreadLogEntries}} of {{system.logEntries.length}}</span>
<system-view system="system"></system-view>
</div>
</some-accordion-control>
JS SystemView Controller
module.exports = {
template: 'some.html',
controller: SystemView,
bindings: {
system: '<'
}
};
function SystemView() {
// ... some code
}
SystemView.prototype = {
setRead: function () {
// this is an example that i change the value of unreadLogEntries
// this did not work in the dashboard component
system.unreadLogEntries--;
// i tried this and it worked in the dashboard component
system.logEntries.push({
message: "Hello World!"
});
}
};
My problem here is, that the list of log entries could change and gets the new information about it's values. But the simple integer field unreadLogEntries not.
Can someone help me here?
Ok, i did the following to hit this:
I removed the unreadLogEntries field and added a controller function to count the unread log entries in the attached list of log entries.
This in the HTML:
<span class="badge">{{$ctrl.countUnread(system.logEntries)}} of {{system.logEntries.length}}</span>
And as part of my controller:
SystemView.prototype = {
setRead: function () {
// ...
},
countUnread: function (logEntries) {
var unread = 0;
for(var i = 0; i < logEntries.length; i++) {
if(logEntries[i].isUnread) { unread++; }
}
return unread;
}
};
This worked for me. Maybe there is a better solution for that, but this solved my problem.
I have a class written in ES6 and I have a directive "action" which needs to access a controller value called "selected". This controller value "selected" is updated by another directive "grid". ( 2 way binding)
I need to pass "selected" value from the controller that has been updated by Directive "grid" to Directive "actions" on-select . I have tried to pass by doing a "bind" but i get an type error as "cannot read actionHandler of undefined"
I am not sure what is the best way to handle this , such that when the "selected" value has been updated by the "grid" directive, the actionEvent is triggered with the updated value from the controller. The directives are working correctly and i am able to see that it breaks on breakpoints.
Here is what i have in HTML
<div class="col-xs-9">
<action options="ctrl.Actions" on-select="ctrl.actionEvent">
</action>
</div>
<div class="col-xs-12">
<grid config="ctrl.gridOptions" data="ctrl.data" selected="ctrl.selected"></grid>
</div>
In the Controller,
class CheckC {
constructor($scope) {
this.$scope = $scope;
this.selected = this.$scope.selected;
}
actionEvent() {
this.actionHandler.bind(this);
}
actionHandler(item, event) {
let selection;
let model = this.selected;
if(model) {
item.id = 1;
}
}
}
First of all, don't be confused between .bind() and .call().
First returns a new function instance, that can be called later, but with preserved this.
Second calls function immediately, but modifies context of this only for this call.
Read this answer for more information
You are passing a reference to actionEvent method. At the moment of call, the reference to original controller object is already lost.
To preserve the reference, you need to save it first in constructor
class CheckC {
constructor($scope) {
this.$scope = $scope;
this.selected = this.$scope.selected;
//bind method here
this.actionEvent = this.actionEvent.bind(this);
}
actionEvent(item, event) {
// this here will be always contain a referene to class instance
this.actionHandler(item, event);
}
actionHandler(item, event) {
let selection;
let model = this.selected;
if(model) {
item.id = 1;
}
}
}
Also in your code actionEvent method seems redundant. Consider to recfactor code and pass actionHandler directly. (Bu don't forget to update .bind() call, it should bind actionHandler after).
<form>
...
<button ng-click...>Save</button>
</form>
<overlay ng-if="saving" style="background-color: #ff0000">
<pindicator size="2em" color="red"></pindicator>
</overlay>
void handleOnSaveClicked(final html.Event e) {
_logger.info("Ready to save:",_tempJob);
saving = true;
// Angular starts to register Listener
_proxy.save(tempJob).then((_) {
// Template/Component has already received "detach"
_logger.info("Job saved!");
editable = false;
saving = false;
});
}
...
void detach() {
_logger.info("Detached!");
}
proxy.save saves tempJob into a list, this list is observed by Angular.
If this list changes Angular updates all the UI-Elements attached to the list.
saving=true should show a message but if proxy.save is so fast that Angular has no chance to register a Listener for "saving" (https://github.com/angular/angular.dart/blob/master/lib/core/scope.dart - Line 352). the whole UI-Block is remove while Angular tries to register the Listener.
This leads to
JobEdit: (15:19:43.096) Ready to save:
JobEdit: (15:19:43.119) Job saved!
JobEdit: (15:19:43.123) Detached!
'package:angular/core/scope.dart': Failed assertion: line 352 pos 12: 'isAttached' is not true.
Any hints to avoid this kind of "race condition"?
I haven't tried it but I think to remember that you should use VMTurnZone to execute async operations that affect the model.
class YourComponent implements ScopeAware {
Scope scope;
VmTurnZone zone;
YourComponent(this.zone);
void handleOnSaveClicked(final html.Event e) {
_logger.info("Ready to save:",_tempJob);
saving = true;
// Angular starts to register Listener
zone.run(() {
return _proxy.save(tempJob).then((_) {
// Template/Component has already received "detach"
_logger.info("Job saved!");
editable = false;
saving = false;
});
});
}
}
The main problem is replacing the list item with a new instance of your class (new object)
Angular tests the objects hashCode if you replace a list item.
If the hashCode is different it deletes the according component, but if the hashCode is the same
it updates the vars in your html-file.
Here it also deletes the component and replaces it with the new device:
...
// hashCode of devices[42] changes
devices[42] = new Device(new Location(40.123,10.567));
But this updates! your component:
...
// hashCode of devices[42] stays the same
final Device device = devices[42];
device.location = new Location(40.123,10.567);