AngularJS - Dynamic ngModel with "key.key.key..value" - javascript

I know this looks like a duplicate question, but I wasn't able to find anything pertaining this?
I have a variable such as "settings.page.header.title"
I would like to set the ngModel of an input to the above dynamically, as it will change.
I've tried the following without success:
1)
$scope.getDynamicModel = function(str) {
var levels = str.split(".");
var model = $scope;
for (var i = 0, i < levels.length; i++) {
var level = levels[i];
model = model[level];
}
return model;
}
.
<input ng-model="getDynamicModel('settings.page.header.title')">
2)
<input ng-model="{{ 'settings.page.header.title' }}">
Any ideas?

Wrote a directive that might help:
app.directive('myModel', function($compile){
return {
link: function(scope,elm,attrs){
elm.removeAttr('my-model');
scope.$watch(attrs.myModel, function(value){
if (value)
$compile(elm.attr('ng-model',value))(scope)
})
}
}
})
This will add a watcher that will update the ng-model for the element, and recompile. Removing my-model is done to avoid compiling the element infinitely.
DEMO

html
<input ng-model="settings.page.header.title" type="text">
js
$scope.settings = {
page: {
header: {
title: 'myTitle'
}
}
}
http://plnkr.co/edit/mrcBUNrq90awHaqPYOq4?p=preview

A little modifications to prevent failing:
$scope.getDynamicModel = function(str) {
var levels = str.split(".");
var model = $scope;
for (var i = 0; i < levels.length; i++) {
var level = levels[i];
model = model[level];
if (model == null) {
return model;
}
}
return model;
}
See fiddle

Related

How to share a $scope object between multiple controllers

I am facing problem while sharing a $scope object between 2 controllers.
In controller 'IssueBookCtrl',I am updating the books object element value like this.
$scope.books[i].issued = true;
Then I am using $emit service to share the $scope object with controller 'BookListCtrl_Librarian'.
$scope.$emit('update_parent_controller', $scope.books);
But when I run the view which is using the controller 'BookListCtrl_Librarian',i don't see the updated object.
controller.js
Controllers.controller('BookListCtrl_Librarian', ['$scope','$http','$location','$rootScope','BookData',
function ($scope, $http ,$location, $rootScope , BookData) {
$http.get('data/books.json').success(function(data) {
if($rootScope.books==='undefined'){
$rootScope.books = BookData.getData();
}
$scope.books = data;
});
$scope.options = ['bookId', 'cost'];
$scope.$on("update_parent_controller", function(event, books){
$scope.books = books;
});
$scope.issue = function(bookId) {
for (var i = 0, len = $scope.books.length; i < len; i++) {
if ($scope.books[i].bookId == bookId) {
$rootScope.book = $scope.books[i];
break;
}
}
$location.path('/issue/'+bookId);
}
$scope.return = function (bookId) {
for (var i = 0, len = $scope.books.length; i < len; i++) {
if ($scope.books[i].bookId == bookId) {
$rootScope.book = $scope.books[i];
break;
}
}
$location.path('/return/'+bookId);
}
$scope.backToLogin = function() {
$location.path("/main");
}
}]);
Controllers.controller('IssueBookCtrl', ['$scope','$rootScope','$http','$routeParams','$location',
function ($scope,$rootScope, $http, $routeParams, $location) {
var Id=$routeParams.bookId;
$http.get('data/books.json').success(function(data) {
$scope.books = data;
});
$scope.issue = function(Id) {
alert("issued");
for (var i = 0, len = $scope.books.length; i < len; i++) {
if ($scope.books[i].bookId === Id) {
$scope.books[i].issued = true;
$scope.$emit('update_parent_controller', $scope.books);
$location.path('/home/librarian');
break;
}
}
}
}]);
Please guide me,any help is much appreciated.
Thanks
hi u have to use rootscope instead of scope emit
$rootScope.$emit('update_parent_controller', $scope.books);
and in other controler
$rootScope.$on('update_parent_controller', function(event, books){
$scope.books = books;
});
You could try forcing a digest after altering a model:
$scope.$apply();
But I would recommended that you build a Books service to hold those shared models and logic. You can learn about creating your own custom services here.
Or you could nest your controllers (put one inside the other) so that the inner controller can reference the outer controller's models by using $parent variable.
By using either you should not have any problems with object updating as AngularJs runs dirty-checks when scope variables are changed.

angularJS: Dynamic header sorting not working

I am trying to implement a solution to sort a table by clicking its headers, using AngularJS.
I found a good example after doing a Google search: https://scotch.io/tutorials/sort-and-filter-a-table-using-angular
I am able to see the up and down arrows, but the table does not sort when I click them.
I think the problem resides in how the JSON object is formatted in my situation. I have not been able to figure it out, and I am hoping that with the information that I am providing on this post, I can get some help to understand what I am doing incorrectly.
Here is a copy of the JavaScript:
(function (define, angular) {
'use strict';
define(function () {
var opportunityController = function ($scope, Metadata, Factory) {
var vm = this;
//set the default sort type
vm.sortType = 'Products';
//set the default sort order
vm.sortReverse = false;
Factory.Data(caller.sp, caller.filter).then(function (payload) {
var data = angular.fromJson(payload.data).Table;
ProcessData(data);
});
function ProcessData(data) {
if (angular.isDefined(data)) {
var counter = 0;
vm.products = [];
vm.productsSet = FindByAsObjectArray(function (x) {
return (x.TypeName == "Product");
}, data);
for (var index = 0, length = vm.productsSet.length; index < length; index++) {
vm.products[index] = {
data: vm.productsSet[index]
};
}
}
}
};
return ['$scope','Metadata','Factory',opportunityController];
});
})(define, angular);
I got it to work, final version: https://jsfiddle.net/itortu/nhhppf53/
Many thanks.
You are using controllerAs
ng-controller="Company:OpportunityController as opportunity"
therefor you have to reference sortType and sortReverse in your ng-click like so:
ng-click="opportunity.sortType = 'Products'; opportunity.sortReverse = !opportunity.sortReverse"

Dynamically set data on Angular-chart.js

To be honest I am a bit new to angularjs, so this may be problem with my fundamental understanding of angular, rather than angular-charts.
I have two controllers (PieItemsCtrl and PieCtrl) and I would like to communicate between them by using a factory service (called pieItems)
On the one hand the pieItems works as designed in the PieItemsCtrl.
ie:
$scope.slices = pieItems.list();
Whenever something changes in the pieItems service (ie another element is added), then the HTML is automatically updated via a repeater :
<div ng-repeat="(key, val) in slices">
However in the PieCtrl I have this line, and i would expect the pie chart to update automatically :
$scope.labels = pieItems.labelsItems();
$scope.data = pieItems.data();
It seems to set these data values upon loading/initialisation of the PieCtrl and that's it. Whenever the pieItems data changes these scope values are not updated.
The source of the two controller and factory object are below. And I also made an unworkable fiddle, incase that helps
PieItemsCtrl :
app.controller('PieItemsCtrl', function($scope, $http, $rootScope, pieItems) {
$scope.slices = pieItems.list();
$scope.buttonClick = function($event) {
pieItems.add(
{
Name: $scope.newSliceName,
Percent: $scope.newSlicePercent,
Color: $scope.newSliceColor
}
)
}
$scope.deleteClick = function(item, $event) {
pieItems.delete(item);
}
}
)
PieCtrl :
app.controller("PieCtrl", function ($scope, $timeout, pieItems) {
$scope.labels = pieItems.labelsItems();
$scope.data = pieItems.data();
});
pieItems :
app.factory('pieItems', function() {
var items = [];
var itemsService = {};
itemsService.add = function(item) {
items.push(item);
};
itemsService.delete = function(item) {
for (i = 0; i < items.length; i++) {
if (items[i].Name === item.Name) {
items.splice(i, 1);
}
}
};
itemsService.list = function() {
return items;
};
itemsService.labelsItems = function() {
var a = ['x', 'y'];
for (i = 0; i < items.length; i++) {
a.push(items[i].Name);
}
return a;
};
itemsService.data = function() {
var a = [50,50];
for (i = 0; i < items.length; i++) {
a.push(items[i].Percent);
}
return a;
};
return itemsService;
});
The controller doesn't notice when the value in your factory changes. To include your item-Array in an Angular digest-cycle, tell Angular to $watch that Array.
If you don't want to expose the Array, create a getter:
itemsService.get = function() { return items; }
Then you can include that getter in your $watch expression in your controller:
$scope.$watch(getItems, watcherFunction, true);
function getItems() {
return pieItems.get();
}
The getItems-Function gets called on digest cycle and fires the watcherFunction if the value changed and has the newData as argument. true as 3rd argument creates a deep watch.
function watcherFunction(newData) {
console.log(newData);
// do sth if array was changed
}
For more complex objets, you can use a $watchCollection.

Recompute Computed Observable without resetting the objects

I have recently started playing with Knockout and I have hit a problem. I have tried Googling this in all sort of ways but I couldn't find any applicable results.
Let's say that I have this model:
var model = new function () {
var that = this;
this.parameterRegex = ko.observable(/\##{1}\w+/ig);
this.query = ko.observable('SELECT ##par1 from ##par2');
this.parameterNames = ko.computed(function () {
var allParameters = that.query().match(that.parameterRegex());
return (allParameters == undefined) ? [] : jQuery.unique(allParameters);
});
this.parameters = ko.computed(function () {
return ko.utils.arrayMap(that.parameterNames(), function (item) {
return {
Name: ko.observable(item),
Example: ko.observable()
}
});
});
};
In the HTML I am binding with the Parameters computed observable, but every time the Query observable changes and the Parameters observable recomputes, I lose all the state of the items in that computed.
What I mean by this is that if I bind a foreach in HTML with Parameters and I have some input boxes in that foreach, such as:
<textarea name="query" class="form-control" data-bind="value: query, valueUpdate:'afterkeydown'" rows="10" style="margin-bottom:20px"></textarea>
<div data-bind="foreach: parameters">
<p data-bind="text: Name"></p>
<input type="text"></input>
</div>
Any text that the user has typed in the input will be lost once the Computed Observeable is recalculated.
How would I go about solving this?
The solution is to keep a separate array with the objects in them and then re-use the objects if they exist in the array instead of re-creating them each time.
var parameters = [];
this.parameters = ko.computed(function () {
var newParams = [];
for (var i = 0; i < that.parameterNames().length; i++) {
var name = that.parameterNames()[i];
var result = $.grep(parameters, function(p){ return p.Name() == name; });
var param;
if (result.length === 0) {
param = {
Name: ko.observable(name),
Example: ko.observable()
};
}
else {
param = result[0];
}
newParams.push(param);
}
parameters = newParams;
return newParams;
});
jsfiddle

Getting crazy with angular directive and scope update

I'm trying for 7 hours now to get something so work and currently I'm pulling my hair out!!!!
Unbelievable!
I just want to update an controller variable from an isolated directive.
And the crazy thing is, in the view everything works perfect, but in the controller he does not recognize the change!
The controller looks like this, because he is generated by TypeScript.
/*
ANGULAR CONTROLLER
*/
var CustomerdataTabCtrl = (function () {
function CustomerdataTabCtrl($scope) {
$scope.AddressAutocompleteOptions = {};
$scope.AddressAutocompleteOptions.watchEnter = true;
$scope.AddressAutocompleteDetails = '';
$scope.AddressAutocompleteAddress = null;
$scope.onRoleChange = function () {
if ($scope.Role == 'Provider') {
$('#orders-tab-header').css('display', 'none');
} else {
$('#orders-tab-header').css('display', 'block');
}
};
$scope.onAddressAutocompleteChange = function () {
alert('asd');
};
$scope.$watch('Role', function () {
return $scope.onRoleChange();
}, true);
$scope.$watch('AddressAutocompleteAddress', function () {
return $scope.onAddressAutocompleteChange();
}, true);
}
return CustomerdataTabCtrl;
})();
/*
ANGULAR DIRECTIVE
*/
angular.module("ngAutocomplete", [])
.directive('ngAutocomplete', function () {
return {
require: 'ngModel',
scope: {
ngModel: '=',
options: '=',
details: '=',
address: '='
},
link: function (scope, element, attrs, controller) {
//options for autocomplete
var opts;
var watchEnter = false;
//convert options provided to opts
var initOpts = function () {
opts = {};
if (scope.options) {
if (scope.options.watchEnter !== true) {
watchEnter = false
} else {
watchEnter = true
}
if (scope.options.types) {
opts.types = [];
opts.types.push(scope.options.types);
scope.gPlace.setTypes(opts.types)
} else {
scope.gPlace.setTypes([])
}
if (scope.options.bounds) {
opts.bounds = scope.options.bounds;
scope.gPlace.setBounds(opts.bounds)
} else {
scope.gPlace.setBounds(null)
}
if (scope.options.country) {
opts.componentRestrictions = {
country: scope.options.country
};
scope.gPlace.setComponentRestrictions(opts.componentRestrictions);
} else {
scope.gPlace.setComponentRestrictions(null);
}
}
};
if (scope.gPlace == undefined) {
scope.gPlace = new google.maps.places.Autocomplete(element[0], {});
}
google.maps.event.addListener(scope.gPlace, 'place_changed', function () {
var result = scope.gPlace.getPlace();
if (result !== undefined) {
if (result.address_components !== undefined) {
scope.$apply(function () {
scope.address = parseGoogleResponse(result.address_components);
scope.details = result;
controller.$setViewValue(element.val());
});
}
else {
if (watchEnter) {
getPlace(result)
}
}
}
});
//function to get retrieve the autocompletes first result using the AutocompleteService
var getPlace = function (result) {
...
};
var parseGoogleResponse = function(components) {
var result = {};
for (var i = 0; i < components.length; i++) {
var addressType = components[i].types[0];
result[addressType] = components[i]['long_name'];
}
return result;
};
controller.$render = function () {
var location = controller.$viewValue;
element.val(location);
};
//watch options provided to directive
scope.watchOptions = function () {
return scope.options
};
scope.$watch(scope.watchOptions, function () {
initOpts()
}, true);
}
};
});
HTML
<div ng-controller="CustomerdataTabCtrl">
<input type="text" id="customerdata_quick_auto_address" name="customerdata_quick_auto_address" class="form-control input-sm" ng-autocomplete ng-model="AddressAutocomplete" options="AddressAutocompleteOptions" details="AddressAutocompleteDetails" address="AddressAutocompleteAddress" />
{{AddressAutocompleteAddress.route}}
</div>
Like I said the view outputs the correct result, but the controller watch does only fire on init once, but then never again, but it is the same fu.... variable. The view {{AddressAutocompleteAddress.route}} uses the controller scope variable, I'm getting really crazy!
Kind regards
Like I mentioned in my comment, this is a problem with the scoping of the variables. The directive's isolate scope is making it's own instance of the AddressAutocompleteAddress variable since it is a "primitive" variable. The way to fix this is to use the "." (dot) notation.
What I would probably do to your code is to create an object called AddressAutocomplete and then add your other variables to that object.
function CustomerdataTabCtrl($scope) {
$scope.AddressAutocomplete = {};
$scope.AddressAutocomplete.Text = '';
$scope.AddressAutocomplete.Options = {};
$scope.AddressAutocomplete.Options.watchEnter = true;
$scope.AddressAutocomplete.Details = '';
$scope.AddressAutocomplete.Address = null;
...
}
And in your html:
<div ng-controller="CustomerdataTabCtrl">
<input type="text" id="customerdata_quick_auto_address"
name="customerdata_quick_auto_address"
class="form-control input-sm" ng-model="AddressAutocomplete.Text"
ng-autocomplete options="AddressAutocomplete.Options"
details="AddressAutocomplete.Details" address="AddressAutocomplete.Address" />
{{AddressAutocomplete.Address.route}}
</div>
Just remember to update the way you use the variables in your controller. Also I don't see where you use the ng-model variable in your directive so I'm not sure why you are using it there.
One more thing, you really should not use the ng- prefix for your directives, that is a reserved prefix for directives from the AngularJs library.

Categories