Angular.js factory object not refreshed in view - javascript

I have single view displaying investments + two others which are modals to register new investment which show up when user clicks 'add' (two modals because of two steps of registration). I created factory which is used in step1 and then in step2 in order to keep information regarding investment being registered - it works when you switch between step1 and step2, back and forth.
The problem is that within first view displaying investments I have icon "edit" and within its handler (edit method) I assign selected investment to factory but no change is reflected in step1 view, alas.
View displaying investments:
var module = angular.module("application", []);
module.controller("investmentsController", function ($scope, investmentsFactory, newInvestmentFactory) {
$scope.edit = function (id) {
for (var i = 0; i < $scope.Investments.length; i++) {
if ($scope.Investments[i].Id == id) {
newInvestmentFactory.update($scope.Investments[i]);
}
}
$("#addInvestmentStep1Modal").modal("show");
};
});
View step1 of registration
var module = angular.module("application");
module.factory("newInvestmentFactory", function () {
return {
investment: {
Name: "",
Description: "",
Category: "",
InterestRate: "",
Duration: "",
AmountRange: "",
Url: "",
Subscription: 0,
PaymentType: "",
Icon: ""
},
update: function (investment) {
this.investment = investment;
}
};
});
module.controller("newInvestmentStep1Controller", function ($scope, newInvestmentFactory) {
$scope.Investment = newInvestmentFactory.investment;
});
View step2 of registration
var module = angular.module("application");
module.controller("newInvestmentStep2Controller", function ($scope, newInvestmentFactory) {
$scope.Investment = newInvestmentFactory.investment;
});
The step1 view displaying registration is following
<form id="newInvestmentStep1Form" class="form-horizontal">
<div class="input-group">
<span class="input-group-addon input-group-addon-register">Name</span>
<input id="Name" name="Name" type="text" class="form-control" ng-model="Investment.Name" required title="Pole wymagane" />
</div>
Assignining new object to factory's object (newInvestmentFactory.investment) does not seem to be working but when I assign brand new value to some property of factory like
newInvestmentFactory.investment.Name = "name"
then it displays value correctly.

I can only suspect newInvestmentFactory's update method code. It is reassigning investment object to new investment object like this.investment = investment. By that line new investment object gets created, and old investment loose the reference. To keep the investment object to not create a new variable in update method, you could use angular.extend/angular.merge method. This method will not create a new reference of an object, but it ensures that all object property got updated.
update: function (investment) {
angular.extend(this.investment, investment);
}

In your step controllers
$scope.Investment = newInvestmentFactory.investment;
is just one time assignment to $scope variable, this is not two way binding, so even if value of newInvestmentFactory.investment changes scope won't be updated. What you can do is to watch the factory variable newInvestmentFactory.investment and on change update the scope manually.
Hope this helps

Related

Child view model modying a different child viewmodel

I have a main View Model for my screen. It consists of 2 child view models.
One handles the registration section.
One handles the login section.
One handles the menu section (If authenticated and what menu items can appear, as well as the "Welcome "Username" type stuff).
$(document).ready(function () {
// Create the main View Model
var vm = {
loginVm: new LoginViewModel(),
registerVm: new RegisterViewModel(),
layoutVm: new LayoutViewModel()
}
// Get the Reference data
var uri = '/api/Reference/GetTimezones';
$.getJSON({ url: uri, contentType: "application/json" })
.done(function (data) {
vm.registerVm.Timezones(data);
});
// Bind.
ko.applyBindings(vm);
});
Once my Login model's "Login" method completes, I want to set the "IsAthenticated" value within the Menu model, as well as some other user info.
So in my login model, I have a SignIn method.
$.post({ url: uri, contentType: "application/json" }, logindata)
.done(function (data) {
toastr[data.StatusText](data.DisplayMessage, data.Heading);
if (data.StatusText == 'success') {
alert($parent.layoutVm.IsAuthenticated());
}
else {
}
})
.fail(function () {
toastr['error']("An unexpected error has occured and has been logged. Sorry about tbis! We'll resolve it as soon as possible.", "Error");
});
The alert code is my testing. I am hoping to access (and set) the IsAuthenticated property of the layoutVm model. That's one of the child models on my main View model.
However, "$parent" is not defined.
How can I update values in the layoutVm, from my loginVm?
$parent is part of the binding context, which is only available during the evaluation of the data-bind (i.e. to the binding handler).
In your viewmodel structure, you'll have to come up with a way to communicate between models yourself. For example, by passing parent view models, or by passing along shared observables. The problem you're describing can be solved by using data-bind="visible: $root.userVM.IsAuthenticated", like I answered in your previous question.
If you'd like to go with the other approach, here's an example on how to share an observable between viewmodels.
var ChildViewModel = function(sharedObs) {
this.myObs = sharedObs;
this.setObs = function() {
this.myObs(!this.myObs());
}.bind(this);
}
var RootViewModel = function() {
this.myObs = ko.observable(false);
this.vm1 = new ChildViewModel(this.myObs);
this.vm2 = new ChildViewModel(this.myObs);
this.vm3 = new ChildViewModel(this.myObs);
}
ko.applyBindings(new RootViewModel());
div { width: 25%; display: inline-block; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="with: vm1">
<h4>vm1</h4>
<p data-bind="text: myObs"></p>
<button data-bind="click: setObs">
flip
</button>
</div>
<div data-bind="with: vm2">
<h4>vm2</h4>
<p data-bind="text: myObs"></p>
<button data-bind="click: setObs">
flip
</button>
</div>
<div data-bind="with: vm3">
<h4>vm3</h4>
<p data-bind="text: myObs"></p>
<button data-bind="click: setObs">
flip
</button>
</div>
Note that each of the child view models also have write permission, so you'll have to be careful to not accidentally update the observable

Pass Angular typeahead object result to another function in controller

In this scenario I'm using the ui-bootstrap typeahead to capture an object from an external api. Using the select callback I'm getting that object and have the results set in a separate function within my controller.
The issue is that I want to take those results and send them off to a separate api with a click function I already have set up. My question is how do i get the results of the type-ahead into the click function to post? The user flow is as follows.
<div>
<input type="text" placeholder="Find A Game"
typeahead-on-select="setGames($item)"
ng-model="asyncSelected"
typeahead="test.name for test in getGames($viewValue)"
typeahead-loading="loadingLocations" typeahead-min-length="3"
typeahead-wait-ms="500" typeahead-select-on-blur="true"
typeahead-no-results="noResults">
</div>
<div ng-show="noResults">
No Results Found
</div>
<button ng-disabled="!asyncSelected.length"
ng-click="addtodb(asyncSelected)">Add To Database</button>
As you can see the label is set to the items name and this works fine. When the user selects the name I then use typeahead-on-select="setGames($item)" to send off the entire object to its own funtion. From there I want to take the object and pass it to another function that you can see within the button tags ng-click. I currently have it passing the model, but what I really want is to pass the entire object within $item from the select event. So far my controller looks like this:
angular.module('2o2pNgApp')
.controller('GiantCtrl', function ($scope, $http, TermFactory, $window, SaveFactory) {
$scope.getGames = function(val) {
return $http.jsonp('http://www.example.com/api/search/?resources=game&api_key=s&format=jsonp&limit=5&json_callback=JSON_CALLBACK', {
params: {
query: val
}
}).then(function(response){
return response.data.results.map(function(item){
return item;
});
});
};
$scope.setGames = function (site) {
var newsite = site;
};
$scope.addtodb = function (asyncSelected, newsite) {
TermFactory.get({name: asyncSelected}, function(data){
var results = data.list;
if (results === undefined || results.length === 0) {
SaveFactory.save({vocabulary:'5', name:newsite.name, field_game_id:newsite.id}, function(data) {
$window.alert('All Set, we saved '+asyncSelected+' into our database for you!')
});
} else {
// do stuff
});
}
});
No matter what I do I cant seem to pass the entire $item object into this click function to post all the info i need.
Via New Dev in Comments:
$item is only available locally for typeahead-on-select... you can
either assign it to some model within your controller, or, in fact,
make the model of typeahead to be the item: typeahead="test as
test.name for test in getGames($viewValue)" – New Dev

Passing nested knockout view model to controller

This is fleet management app in mvc EF and knockout , there are VEHICLES , DRIVERS and specific ROUTES that the drivers travel.
I have a situation where I would like to gather field values from three models nested within my view models into a single object and post it my backend via ajax. I want this to be triggered by clicking a button.
There is a problem is in my populateFleetInformation function. When I gather the field values from my various models, some of the observables are losing their values and appear as null.
The view model that attempts to make the ajax call
function FleetViewModel() {
var vvm = this;
vvm.regNumber = ko.observable(0);
vvm.make = ko.observable("");
vvm.model = ko.observable("");
vvm.RouteDetail = new RouteViewModel();
vvm.SaveFleetInfo = function(item){
if (!pageViewModel.isAuthenticated()) return;
populateFleetInformation(item);
$.when(postSecureData("/api/Fleet/", ko.toJSON(pageViewModel.FleetViewModel.RouteViewModel.RouteDriverViewModel)))
.done(function () {
document.getElementById("save-Fleet-btn").innerHTML = "Saving...";
setTimeout(function () { document.getElementById("save-fleet-btn").innerHTML = "Save" }, 2500);
$.msgGrowl({
type: 'info',
title: 'Fleet Information',
text: 'fleet information succesfully saved',
position: 'bottom-right',
lifetime: 3000
});
})
}
}
function that gathers data
function PopulateFleetInformation(item)
{
pageViewModel.fleetVM.regNumber(item.regNumber);
pageViewModel.fleetVM.make(item.make);
pageViewModel.fleetVM.model.(item.model);
pageViewModel.fleetVM.routeDetail.routeID(item.routeId);
pageViewModel.fleetVM.routeDetail.routeName(item.routeName);
pageViewModel.fleetVM.routeDriver.nationalId(item.nationalId);
pageViewModel.fleetVM.individualMsisdn.licenseId(item.licenseId);
pageViewModel.fleetVM.individualMsisdn.driverName(item.driverName);
}
The trigger button
<button type="submit" id="save-fleet-btn" class="btn"data-bind="click: $root.fleetVM.sensorDetail.SaveFleetInfo"></button>
This function:
function PopulateFleetInformation(item)
{
pageViewModel.fleetVM.regNumber(item.regNumber);
pageViewModel.fleetVM.make(item.make);
pageViewModel.fleetVM.model.(item.model);
pageViewModel.fleetVM.routeDetail.routeID(item.routeId);
pageViewModel.fleetVM.routeDetail.routeName(item.routeName);
pageViewModel.fleetVM.routeDriver.nationalId(item.nationalId);
pageViewModel.fleetVM.individualMsisdn.licenseId(item.licenseId);
pageViewModel.fleetVM.individualMsisdn.driverName(item.driverName);
}
is not doing what you think it is at all.
itemis coming from the context passed into SaveFleetInfo by knockout when your button is clicked. This function is then setting the values of the fields you are trying to retrieve to whatever is in item; I'm guessing you're getting all sorts of undefined exceptions here.
What you want to do is this:
function PopulateFleetInformation()
{
return {
regNumber: pageViewModel.fleetVM.regNumber();
make: pageViewModel.fleetVM.make();
model: pageViewModel.fleetVM.model.();
routeId: pageViewModel.fleetVM.routeDetail.routeID();
routeName: pageViewModel.fleetVM.routeDetail.routeName();
nationalId: pageViewModel.fleetVM.routeDriver.nationalId();
licenseId: pageViewModel.fleetVM.individualMsisdn.licenseId();
driverName: pageViewModel.fleetVM.individualMsisdn.driverName();
}
}
This function returns the values you are after as an object, then you can post this data like this:
var data = populateFleetInformation();
$.when(postSecureData("/api/Fleet/", ko.toJSON(data)))

edit update existing array in javascript

I am making CRUD app for learning purpose. I need to update existing javascript array on click of edit button. However currently its not updating the existing array rather then its creating new record. Below is the JS code of controller
For Add screen below is the controller code
.controller('addController', ['$scope','$location','$rootScope', function(scope,location,rootScope){
scope.save = function (){
scope.personName = document.getElementById('name').value;
scope.personDesc = document.getElementById('desc').value;
scope.person = {'name' : scope.personName, 'desc' : scope.personDesc};
if(typeof rootScope.crew === 'undefined'){
rootScope.crew = [];
}
rootScope.crew.push(scope.person);
location.path("/");
}
}])
For Edit Screen, below is the code of controller :-
.controller('editController',['$scope','$location','$routeParams','$rootScope', function(scope,location,routeParams,rootScope){
var oldName = scope.crew[routeParams.id].name;
document.getElementById('name').value = scope.crew[routeParams.id].name;
document.getElementById('desc').value = scope.crew[routeParams.id].desc;
scope.editSave = function(){
scope.person = {
'name' : document.getElementById('name').value,
'desc' : document.getElementById('desc').value
}
rootScope.crew.push(scope.person);
location.path("/");
}
}])
Currently I am adding record in existing array rather updating.
Please suggest
The problem is you are pushing a new item to the array. You need to just update the existing person with the person in scope.
.controller('editController',['$scope','$location','$routeParams','$rootScope', function(scope,location,routeParams,rootScope){
var person = scope.crew[routeParams.id]
scope.person = {
name = person.name,
desc = person.desc
};
scope.editSave = function(){
scope.crew[routeParams.id] = scope.person;
location.path("/");
}
}])
In your edit view you would have this:
<input type="text" id="name" ng-model="person.name"/>
<input type="text" id="desc" ng-model="person.desc"/>
It's also worth mentioning that there is no need to have code such as document.getElementById as angular will handle the model binding for you so you don't have to interact with the dom using javascript.
Every object that you are pushing in array must be identified by some id.So assign one id attribute to the person object that you are pushing.
Now come to the edit.html
<tr ng-repeat="p in person">
{{p.name}}
//In button I am passing id which I used in editing the person object
<button ng-click="edit(p.id)"></button>
</tr>
//In controller
edit(id){
//firstly search for the person which is going to be updated
for(i=0;i<arrayInWhichYouArePushingData.length;i++)
{
if(arrayInWhichYouArePushingData[i].id==id)
{
arrayInWhichYouArePushingData[i]=UpdatedData;
break;
}
}
This is just an algorithm to solve this type of problem.You have to modify little bit.

How to update data in Meteor using Reactivevar

I have a page with a form. In this form user can add multiple rows with key and values. There is a restriction that the customFields is created on the fly, not from any subscribed collection.
...html
<template name="main">
{{#each customFields}}
<div>
<input type="text" value="{{key}}"/>
<input type="text" style="width: 300px;" value="{{value}}"/>
</div>
{{/each}}
</template
.... router.js
Router.route 'products.add',
path: '/products/add/:_id'
data:
customFields:[]
....products.js
#using customFieldSet as Reactive Var from meteor package
Template.product.created = ->
#customFieldSet = new ReactiveVar([])
Template.product.rendered = ->
self = this
Tracker.autorun ->
arr = self.customFieldSet.get()
self.data.customFields = arr
Template.product.events(
'click .productForm__addField': (e)->
t = Template.instance()
m = t.customFieldSet.get()
console.log t
m.push(
key: ''
value: ''
)
t.customFieldSet.set m
....
The last event will be trigger when I click the button. And it add another row with key and value empty to the page.
Please advise me why I actually see the reactive variable customFieldSet updated, but there is nothing changed dynamically in html.
P/s: I guess customFields is not updated via Iron router.
Basically, you're doing the thing right. However, you shouldn't be assigning the new reactive data to your template's data context, but rather access it directly from your helpers:
Template.product.helpers({
customFileds: function () {
return Template.instance().customFiledsSet.get();
},
});
Now you can use {{customFields}} in your template code and it should work reactively. Just remember that {{this.customFileds}} or {{./customFileds}} will not work in this case.

Categories