Angularjs: ngDialog only binds and modifies with objects; not basic variables. - javascript

I've already found a "solution" to this problem; I was just hoping someone might be able to provide a reason why it works.
This jsFiddle demonstrates the problem:
http://jsfiddle.net/s1ca0h9x/137/
HTML
<div data-ng-app="myApplication">
<div data-ng-controller="MainController">
Click Here
<input type="text" ng-model="accountNum" />
<span>{{accountNum}}</span>
</div>
</div>
ANGULARJS
var myApplication = angular.module('myApplication', ['ngDialog']);
myApplication.controller('MainController', function ($scope, ngDialog) {
$scope.accountNum = 'test';
$scope.ShowNgDialog = function () {
ngDialog.open({
template: '<div><input type="text" ng-model="accountNum"/></div>',
plain: true,
scope:$scope
});
}
});
When I try and manipulate a scope variable (in this case: $scope.accountNum = 'test') from the dialog, it doesn't bind/save it back to the model.
...However, when I change that variable into an object, things just magically work, as shown in this demo: http://jsfiddle.net/s1ca0h9x/138/
HTML
<div data-ng-app="myApplication">
<div data-ng-controller="MainController">
Click Here
<input type="text" ng-model="FormData.accountNum" />
<span>{{FormData.accountNum}}</span>
</div>
</div>
ANGULARJS
var myApplication = angular.module('myApplication', ['ngDialog']);
myApplication.controller('MainController', function ($scope, ngDialog) {
$scope.FormData={accountNum: ''};
$scope.ShowNgDialog = function () {
ngDialog.open({
template: '<div><input type="text" ng-model="FormData.accountNum"/></div>',
plain: true,
scope:$scope
});
}
});
I also tested both options using a template linking to a file, and not using plain:true, in addition to trying ngDialog.openConfirm, etc. I essentially rebuilt the solution found here ngDialog $scope variables not being updated by ngModel fields in $dialog when using scope: $scope piece by piece, and finally the only change that seemed to work was using an object instead of a basic scope variable.
Am I approaching this wrong, or missing some fundamental aspects of data binding?

I think this has nothing to do with the binding. I will explain what I did understood when I dig into the code of ngDialog and AngularJS.
I think the first case is not working as you expect, because $scope.accountNum = 'test'; is a simple string which is a primitive type and is not mutable(ref) or in other words is immutable:
Mutable is a type of variable that can be changed. In JavaScript, only
objects and arrays are mutable, not primitive values.
(You can make a variable name point to a new value, but the previous
value is still held in memory. Hence the need for garbage collection.)
A mutable object is an object whose state can be modified after it is
created.
Immutables are the objects whose state cannot be changed once the
object is created.
String and Numbers are Immutable.
So, in short words, this was the reason why the first variant is not working as you want :)
Now let's have a look on this code of ngDialog, which is a part of open() method:
var scope;
scopes[dialogID] = scope = angular.isObject(options.scope) ? options.scope.$new() : $rootScope.$new();
in your case we are calling options.scope.$new(), because you specified scope in options when opening the dialog.
Now let's go and check this angular code:
$new: function (isolate, parent) {
var child;
parent = parent || this;
if (isolate) {
child = new Scope();
child.$root = this.$root;
} else {
if (!this.$$ChildScope) {
this.$$ChildScope = createChildScopeClass(this); // <---- WE ARE COMING HERE NOW
}
child = new this.$$ChildScope();
}
...
function createChildScopeClass looks like:
function createChildScopeClass(parent) {
function ChildScope() {
this.$$watchers = this.$$nextSibling =
this.$$childHead = this.$$childTail = null;
this.$$listeners = {};
this.$$listenerCount = {};
this.$$watchersCount = 0;
this.$id = nextUid();
this.$$ChildScope = null;
}
ChildScope.prototype = parent; /* <--- They simply assign the derived scope
as prototype of the new one (which is going to be the scope of the ngDialog) */
return ChildScope;
}
We can see that function createChildScopeClass() simply assigns the parent scope's prototype to the new one (which is going to be the scope of the opened ngDialog)
And a sample that is demonstrating mutability and immutability:
var test = 'test';
var test2 = test;
test2 = 'new value';
console.log('test = ' + test + ' // test2 = ' + test2);
var testObj = {test: 'test'};
var test2Obj = testObj;
test2Obj.test = 'new value';
console.log('testObj.test = ' + testObj.test + ' // test2Obj.test = ' + test2Obj.test);
Conclusion
Use objects or arrays in your parent scope if you want binding to work in the derived scope. Sample using AngularJS:
var app = angular.module('sample', []);
app.controller('AppController', ['$scope', function($scope) {
$scope.primitive = 'test';
$scope.obj = {
test: 'test initial'
};
$scope.newScope = $scope.$new();
$scope.newScope.primitive = 'test 2';
$scope.newScope.obj.test = 'updated value';
}]);
app.run();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="sample">
<div ng-controller="AppController">
<table>
<thead><tr><th>Property</th><th>Value</th><th></th></tr></thead>
<tbody>
<tr>
<td>primitive</td>
<td>{{ primitive }}</td>
<td><input type="text" ng-model="primitive"></td>
</tr>
<tr>
<td>obj.test</td>
<td>{{ obj.test }}</td>
<td><input type="text" ng-model="obj.test"></td>
</tr>
<tr>
<td>newScope.primitive</td>
<td>{{ newScope.primitive }}</td>
<td><input type="text" ng-model="newScope.primitive"></td>
</tr>
<tr>
<td>newScope.obj.test</td>
<td>{{ newScope.obj.test }}</td>
<td><input type="text" ng-model="newScope.obj.test"></td>
</tr>
</tbody>
</table>
</div>
</div>

For me what worked, is to create a function in the base controller, and call the function from ngDialog controller.
Ex:
myApplication.controller('MainController', function ($scope, ngDialog) {
$scope.accountNum = 'test';
$scope.ShowNgDialog = function () {
ngDialog.open({
template: '<div><input type="text" ng-model="accountNum"/></div>',
plain: true,
scope:$scope,
controller: ['$scope',
function ($scope) {
$scope.updateVar();
}]
});
};
$scope.updateVar = function(){
$scope.accountNum = "changed";
}
});

Related

AngularJS ng-if directive using a function not returning a value

I'm still pretty new to AngularJS. Following multiple tutorials online, I used a javascript function inside a ng-if directive to validate whether a group name already exists in an array. If it does, the ng-if block is skipped for the next ng-repeat iteration. If it doesn't, add the group name to an array and create the ng-if block. This is what the HTML code looks like in the partial:
HTML
<span ng-if="checkGroups(service.group.name)">
<!--Make nested list-->
</span>
This is a simplified version of the javascript:
JAVASCRIPT
(function () {
'use strict';
MainApp.controller('MainController', [
'$scope',
'Filters',
'helper',
'$timeout',
'$filter',function(
$scope,
Filters,
helper,
$timeout,
$filter) {
var MainCtrl = this;
//Function to check group array variable
$scope.usedGroups = [];
$scope.checkGroups = function(name) {
var isValid = true;
for(var i = 0; i < $scope.usedGroups.length; i++) {
if($scope.usedGroups[i] == name){
isValid = false;
break;
}
}
if(isValid == true){
$scope.usedGroups.push(name);
console.log($scope.usedGroups);
}
return isValid;
}
}
]);
})();
I've used console.log() to return the values and I do get an array with the group names inside as well as a true or false value being returned. The issue is the ng-if function seems to only return false. If I switch the directive function to "checkGroups(service.group.name) == false", it will keep creating the HTML block regardless. Any ideas what I can do to fix this?
I've replaced your service with just a simple array of objects in the controller, since I don't want to create it. But below should do the trick, notice that I flipped the logic around. This will display it only once and will not display it/insert if the object exists already.
var app = angular.module("MyApp", []);
var MyController = function($scope) {
// Somewhere in your service
$scope.service = {
group: [{
name: "Foo"
}
]
}
//Function to check group array variable
$scope.usedGroups = [];
$scope.checkGroups = function(name) {
for (var i = 0; i < $scope.usedGroups.length; i++) {
if ($scope.usedGroups[i] == name) {
console.log(name + " exists already!", $scope.usedGroups);
return true;
}
}
$scope.usedGroups.push(name);
console.log(name + " doesn't exist!\n", $scope.usedGroups);
return false;
}
}
app.controller(MyController, "[$scope, MyController]");
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
</head>
<body ng-app="MyApp">
<div ng-controller="MyController">
<div ng-repeat="item in service.group"> <!-- This is ran three times, just like calling it with three different names, but I put it in the controller to make it accessible -->
<span ng-if="checkGroups(item.name)">
{{ item.name }}
<!--Make nested list-->
</span>
</div>
</div>
</body>
</html>
Use the controller reference:
MyController.checkGroups(service.group.name)

AngularJs accessing scope outside from another function in javascript

I want to change the value of variables in html with the help of AngularJS. For that I know that onclick function can be called which will enable us to change the value:
The javascript code:
var app = angular.module('graphApp', []);
app.controller('graphAppCtrl', function($scope) {
$scope.count = 0;
$scope.myFunction = function() {
$scope.names= {0:{'Name':'John','Country':'albania'}};
$scope.count++;
keyword_type = 1
check();
}
});
Now I want to change the value of variable names from outside the function after performing some calculations. What is the procedure for that?
HTML :
<div id="search-container" ng-app="graphApp" ng-controller="graphAppCtrl">
<tabs id="mainTabs">
<pane title="Name search">
<p>Name: <input type="text" ng-model="name"></p>
<button ng-click="myFunction()">Search</button>
<table>
<tr>
<td>Index</td>
<td>Name</td>
<td>ID</td>
</tr>
<tr ng-repeat="x in names">
<td>{{ x.Name }}</td>
<td>{{ x.Country }}</td>
</tr>
</table>
<p ng-bind="check"></p>
</pane>
getting value from this function :
function WebSocketTest(keyword_or_query,keywordtype) {
var object_of_each_pair = [];
if ("WebSocket" in window) {
//messageContainer.innerHTML = "WebSocket is supported by your Browser!";
var ws = new WebSocket("ws://localhost:8888/websocket");
ws.onopen = function() {
var message=[]
message = keyword_or_query + 'XXXX' + keywordtype
ws.send(message);
};
ws.onmessage = function (evt) {
var received_msg = evt.data;
node_id = received_msg
}
(fiddle)
Angular environment:
app.controller('graphAppCtrl', function($scope) {
$scope.some_info = 'foo';
});
This is how you retrieve scope from outside Angular environment:
// in your case
var where_you_put_ng_app = document.getElementById("search-container");
var scope = angular.element(where_you_put_ng_app).scope();
// from here you can modify your $scope variables
scope.some_info = '.....';
if i understand correctly you want to change your controller's variable values from outside angularjs , in javascript.
if that so, you should get the current scope and $apply the changes, like this:
var scope = angular.element($("any class name from your DOM")).scope();
//in order to update the new values to the controller , '$apply'
scope.$apply(function () {
//here you can call and change any variable from your controller
scope.names= {0:{'Name':'xxxx','Country':'xxxx'}};
});
in case your $scope.myFunction() and WebSocketTest() both are in same controller then just after getting response you can assign value to $scope.names
In case both are in different controller then after getting value in WebSocketTest() you can use $rootScope.$broadcast like this
suppose you got your value in
currentVariable//just name of variable
then
$rootScope.$broadcast("broadCastonmessage",currentVariable)
//from here we will broadcast once we got the value
now in you controller
$rootScope.$on('broadCastonmessage',function($event,Value){
//this part will be executed whenever broadcast is done
//now value can be assigned to scope variable
$scope.names =value
})
here is one simple example showing how to use broadcast to assign a changed variable to some other variable in other scope hope it will prove helpful to you
http://codepen.io/vkvicky-vasudev/pen/Eyyzvx

One time binding not working inside custom AngularJS Directive

I'm trying to wrap my head around the reason that the one-time bound value (obj.value) inside the directive in this code example is being updated?
Updating the first field will update the bound value inside the directive only once, as expected. Afterwards, inside the directive, when clicking "edit", it will also update the one-time bound value AND also update the parent scope. Updating the first field again will not change the value inside the directive.
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular.min.js"></script>
</head>
<body ng-app="myApp" ng-controller="myCtrl" ng-model-options="{updateOn: 'blur'}">
Enter value here first, then press edit:<br>
<input type="text" ng-model="t.value"><br>
<br>
Press edit, change the value and press copy:
<my-directive obj="t"></my-directive><br><br>
<script>
var myApp = angular.module('myApp', []);
myApp.directive('myDirective', function() {
var directive = {};
directive.restrict = 'E';
directive.template = '<div ng-switch="edit">\
<div ng-switch-default>[{{ ::obj.value }}]<button ng-click="toggle()">edit</button></div>\
<div ng-switch-when="true">\
<input type="text" ng-model="clone.value">\
<button ng-click="copy()">copy</button>\
</div>\
</div>';
directive.scope = {
obj: '='
};
directive.controller = function($scope) {
$scope.edit = false;
$scope.toggle = function() {
$scope.edit = true;
$scope.clone = angular.copy($scope.obj);
}
$scope.copy = function() {
$scope.obj = angular.copy($scope.clone);
$scope.edit = false;
}
}
return directive;
});
myApp.controller('myCtrl', function(){
});
</script>
</body>
http://plnkr.co/edit/tbC3Ji6122gdqt4XbZpI?p=preview
In 1.3 they added a new syntax for helping with one-way binding, "::". So you just need to change your directive implementation to obj="::t".
Here's an update to your plnkr: http://plnkr.co/edit/7lsiX1ItPiQoVpJcQ6iW?p=preview
Here's a nice article that explains a bit more
It is because of ng-switch. Every time it's expression is recalculated the directive is 'redrawn'. And every time is does that the one time expression is also recalculated.
If you change your template to:
directive.template = '{{::obj | json}}<div ng-switch="edit">
etc...
you will see it won't change because it is outside of the ng-switch.

AngularJS template with brackets in it

Why AngularJS doesn't accept brackets inside a ng-template content? I need it to create an input that's going to be an array, but I get this error:
"Error: Syntax Error: Token ']' not a primary expression at column 15 of the expression [form.interval[]] starting at []]."
angular.module("main", []).controller("MyCtrl", function($scope) {
}).directive("ngPortlet", function ($compile) {
return {
template: '<input class="form-control" type="text" placeholder="Interval" ng-model="form.interval[]" />',
restrict: 'E',
link: function (scope, elm) {
scope.add = function(){
console.log(elm);
elm.after($compile('<ng-portlet></ng-portlet>')(scope));
}
}
};
});
<div ng-app="main">
<div ng-controller="MyCtrl">
<div id="container">
<button ng-click="add()" >Add</button>
<ng-portlet></ng-portlet>
</div>
</div>
</div>
jsfiddle:
http://jsfiddle.net/7kcrrapm/1/
EDIT:
Now that I better understand what you're trying to accomplish, here is a different approach:
angular.module("main", []).controller("MyCtrl", function($scope) {
}).directive("ngPortlet", function ($compile) {
return {
template: '<input class="form-control" type="text" placeholder="Interval" ng-model="interval" />',
restrict: 'E',
link: function (scope, elm) {
var intervals = [];
scope.add = function(){
intervals.push(parseInt(scope.interval, 10));
console.log(intervals);
}
}
};
});
Now you have access to an array (intervals) that contains a list of all intervals added.
ORIGINAL:
form.interval[] is not valid JavaScript and thus not a valid scope property. If you need the property to be an array you can simply declare it in your controller ("MyCtrl"):
$scope.form.interval = [];
If you don't create the scope property in the controller your self, it will be implicitly created by the ng-model directive. You can find more info in the docs. I might also suggest this great read about Scopes in the official Angular Wiki
From what I understand, what you really want is ng-repeat.
<span ng-repeat="hour in form.interval">
<input class="form-control" type="text" placeholder="Interval" ng-model="hour" />
</span>
Declare the variable inside the controller or directive:
$scope.form.interval = [];
When you do add() to get another input, add a blank entry to the array in the controller or directive:
$scope.form.interval.push('');
Call add() when you create the variable if you want to start with one empty input box.
The reason it's not working, is because [] is invalid JavaScript syntax on a variable reference.
interval = [1, 2, 3]; // Ok.
interval = []; // Also Ok.
var foo = interval[]; // This isn't valid!
Take those square brackets off, or if you're wanting to do a ng-repeat setup you might consider some of the other given answers.

Instantiate and initialize controller in AngularJS

I have a problem instanciating controller with Angular. I have a main controller AlkeTypeDefListController from which I want to dynamically create/remove controllers of type AlkeTypeDefController, so I have done that :
Code of AlkeTypeDefListController:
// Create main controller
Alke.controller('AlkeTypeDefListController', ['$scope', '$controller', function($scope, $controller)
{
var primitives =
[
];
// Add some properties to the scope
angular.extend($scope,
{
typedefs : primitives,
addTypeDef : function()
{
var controller = $controller("AlkeTypeDefController", {$scope:$scope.$new()});
$scope.typedefs.push(controller);
}
});
}]);
Code of AlkeTypeDefController:
// Create main controller
Alke.controller('AlkeTypeDefController', ['$scope', '$controller', function($scope, $controller)
{
// Add some properties to the scope
angular.extend($scope,
{
name : "New Type",
fields : [],
addField : function()
{
}
});
}]);
The html code is this one:
<div id="typedefs-editor" ng:controller="AlkeTypeDefListController">
<button ng:click="addTypeDef()">Add</button>
<button>Remove</button>
<div id="typedef-list">
<ul class="list">
<li ng:repeat="typedef in typedefs">{{typedef.name}}</li>
</ul>
</div>
</div>
The problem does not really come from the instantiation (which works fine), but from the initialization. In fact, when the new "li" appears when I push the "Add" button, the text "New type" (initialized in the controller) does not appear.
I think it is about the scope or something like that, but I can't really find how to fix this.
I wanted to know if this method seems correct, and also how could I fix the problem I have.
Thanks
Reading the code, I understand that you want to create typedefs dynamically and those typedef items have to be controlled by an AlkeTypeDefController.
In that case I would create AlkeTypeDefController using ng:controller directive, so you don't need to create the controller programmatically, because then you would need to attached it to the view and that's just what the ngController directive does for you.
Notice AlkeTypeDefListController does not create a AlkeTypeDefController controller, this is done in the view
Demo on Plunker
Controllers:
.controller('AlkeTypeDefListController', ['$scope', function($scope) {
var primitives = [];
$scope.typedefs = primitives;
$scope.addTypeDef = function() {
var typeDef = { name: 'New Type' };
$scope.typedefs.push(typeDef);
}
}])
.controller('AlkeTypeDefController', ['$scope', function($scope) {
$scope.addField = function() {
alert('add Field');
}
}]);
View (notice how ng-controller directive is specified in li element):
<div id="typedefs-editor" ng:controller="AlkeTypeDefListController">
<button ng:click="addTypeDef()">Add</button>
<button>Remove</button>
<div id="typedef-list">
<ul class="list">
<li ng:repeat="typedef in typedefs" ng:controller="AlkeTypeDefController">
{{typedef.name}}
</li>
</ul>
</div>
In the code above, ngRepeat is going to create a new $scope for each typedef. AlkeTypeDefController then decorates that scope with functions and values.
I hope it helps
When you call $controller("AlkeTypeDefController") it will essentially call new on the AlkeTypeDefController constructor and give you back the return value not the scope. You are assign the name attrubute to the scope though so it is not being accessed in your html when you have typedef.name.
Try changing your AlkeTypeDefController to this:
Alke.controller('AlkeTypeDefController', function() {
this.name = "New Type";
this.fields = [];
this.addField = function() {};
});
Then you can instantiate it with: var controller = $controller("AlkeTypeDefController"); and you shouldn't need to worry about creating nested scopes.
If I get what you're saying correctly then I think I'd try to leverage the power of a custom directive here instead of dynamically generating controllers.
plunker
Controller:
Alke.controller('alkeTypeDefListController', ['$scope', '$controller',
function($scope, $controller) {
var primitives = [];
var addTypeDef = function() {
$scope.typedefs.push({
name: 'new name'
});
};
var removeTypeDef = function(){
$scope.typedefs.pop();
};
var properties = {
typedefs: primitives,
addTypeDef: addTypeDef,
removeTypeDef: removeTypeDef
};
// Add some properties to the scope
angular.extend($scope, properties);
}
]);
Directive:
Alke.directive('alkeTypeDef', function() {
return {
restrict: 'A',
scope: {
typeDef: '=alkeTypeDef'
},
template: '{{typeDef.name}}',
link: function(scope, element, attr) {
var properties = {
fields: [],
addField: function() {
}
};
angular.extend(scope, properties);
}
};
});
HTML:
<div ng-app='Alke'>
<div id="typedefs-editor" ng-controller="alkeTypeDefListController">
<button ng-click="addTypeDef()">Add</button>
<button ng-click="removeTypeDef()">Remove</button>
<div id="typedef-list">
<ul class="list">
<li alke-type-def='typedef' ng-repeat="typedef in typedefs"></li>
</ul>
</div>
</div>
</div>
If you want a controller then you can use one in the directive instead of a linking function.

Categories

Resources