Angular directive ng-model working for arrays but not string - javascript

The ng-model directive seems to be lacking a reference to the actual object within the JavaScript, but only for string values. Using the list of dictionary objects and looping over the elements with ng-repeat as shown below though, it works.
I can only think that it may be due to returning the array acts like returning a reference to the object, whereas returning the string variable is simply returning the literal string, neutralizing the Angular's ability to do it's two-way data binding and leaving me with a variable that still holds a value of undefined.
Why is my service module below unable to pull the updated value from the view for the variable gitRelease?
In a service module I have this functionality:
(function () { //start iife
'use strict';
angular.module('gms.autoDeploy')
.factory('AutoDeployService', ["$http", "$q", "$log", "$cookies", "APP_CONFIGS", "SweetAlert", "$timeout", "GridSettingsService", "APP_USER", AutoDeployService]);
function AutoDeployService($http, $q, $log, $cookies, APP_CONFIGS, $timeout, SweetAlert, GridSettingsService, APP_USER) {
var tibcoCopyJobs = [];
var gitRelease = "";
function addNewTibcoCopyJob() {
tibcoCopyJobs.push({
sourceServer: "",
sourcePath: "",
destinationServer: "",
destinationPath: ""
});
}
function getTibcoCopyJobs() { return tibcoCopyJobs; }
function getGitRelease(){ return gitRelease; }
function extractFormData() {
console.log(gitRelease);
for (var i = 0; i < tibcoCopyJobs.length; i++) {
console.log(tibcoCopyJobs[i]);
}
}
return {
addNewTibcoCopyJob: addNewTibcoCopyJob,
getTibcoCopyJobs: getTibcoCopyJobs,
getGitRelease: getGitRelease,
extractFormData: extractFormData
};
} //end AutoDeployService
}()); //end iife
Using it with this controller:
angular.module("gms.autoDeploy").controller('AutoDeployController', ['$scope', '$compile', 'AutoDeployService',
function ($scope, $compile, AutoDeployService) {
var model = this;
init();
function init() {
model.tibcoCopyJobs = AutoDeployService.getTibcoCopyJobs();
model.gitRelease = AutoDeployService.getGitRelease();
}
function btn_addNewTibcoCopy() { AutoDeployService.addNewTibcoCopyJob(); }
function btn_extractFormData() { AutoDeployService.extractFormData(); }
model.btn_addNewTibcoCopy = btn_addNewTibcoCopy;
model.btn_extractFormData = btn_extractFormData;
}
]);
To give functionality to this view:
<div ng-controller="AutoDeployController as autoDeploy">
<div class="container-fluid">
<div class="row">
<div class="col-md-2">
<input type="text" class="form-control" ng-model="autoDeploy.gitRelease" placeholder="MM-DD-YYYY">
</div>
</div>
<div class="row">
<fieldset class="col-md-2" style="margin-bottom: 10px" ng-repeat="item in autoDeploy.tibcoCopyJobs track by $index">
<legend>Copy</legend>
<input type="text" class="form-control" placeholder="Source Server..." ng-model="item.sourceServer">
<br/>
<input type="text" class="form-control" placeholder="Source Path..." ng-model="item.sourcePath">
<br/>
<input type="text" class="form-control" placeholder="Destination Server..." ng-model="item.destinationServer">
<br/>
<input type="text" class="form-control" placeholder="Destination Path..." ng-model="item.destinationPath">
</fieldset>
</div>
<button ng-click="autoDeploy.btn_extractFormData()">extract</button>
<button ng-click="autoDeploy.btn_addNewTibcoCopy()">TIBCO copy</button>
</div>
</div>

I think you have explained why in your question. Array is returned by reference, whereas string is just copied by value. But I will try to make it a bit more clear.
When you do
model.gitRelease = AutoDeployService.getGitRelease();
the model object will create property getRelease like this:
{getRelease: "", ... (more properties from the ctrl)}
so whatever you update in the view it will just update the getRelease in the controller.
One possible fix is like what Jags mentioned in the comment.
Or you can make a reference to your service in the ctrl
var model = this;
model.autoDeployService = AutoDeployService;
In your view
<input type="text" class="form-control" ng-model="autoDeploy.autoDeployService.gitRelease" placeholder="MM-DD-YYYY">
that should work.

Related

Pass value from input to controller without $scope

I have problem with following code. I can solve the issue with $scope, but this time request to do it without using $scope in controller. I am using "controller as" to control the view.
<body ng-app="appModule" >
<div ng-controller="calculatorController as calc">
<input type="number" name="firstDigit" placeholder="insert num" ng-model="calc.firstDigit">
<input type="number" name="secondDigit" placeholder="insert num" ng-model="calc.secondDigit">
<span>{{calc.result}}</span>
</div>
</body>
(function(){
angular
.module("calculatorModule")
.controller("calculatorController", calculatorController)
function calculatorController(){
var calc = this;
calc.result = calc.firstDigit + calc.secondDigit;
}
})();
Well, you have two options - you can do it with watchers, or with a function to get the result. I prefer the latter, but it's up to you. Here's an example of how you can get it to work:
Side note - learn the controller as syntax, it will save you thousands of headaches down the road with nested scoping and child-parent relationship issues with $scope -- here's a great article explaining controller as
(function () {
angular.module("calculatorModule", [])
.controller("calculatorController", [function() {
var calc = this;
calc.getResult = function() {
return calc.firstDigit + calc.secondDigit;
}
calc.result = calc.getResult();
}]);
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="calculatorModule" ng-controller="calculatorController as calc">
<input type="number" name="firstDigit" placeholder="insert num" ng-model="calc.firstDigit">
<input type="number" name="secondDigit" placeholder="insert num" ng-model="calc.secondDigit">
<span>{{calc.getResult()}}</span>
</div>
Your module definition is false!
Should be
module("calculatorModule", [])

Strange binding permanence between controllers

I've got a project in which you write a note in a formulary. Then, you submit that note into an information container (now it's just an array for testing purposes, but it's intended to be a DB later).
The formulary has the following controller:
app.controller('controlFormulario', ['$scope', 'SubmitService', function($scope, submitService) {
$scope.formData = {
"titulo":"",
"texto":"",
"fecha": new Date()
};
$scope.submit = function() {
var temp = $scope.formData;
submitService.prepForBroadcast(temp);
}
// more things we don't need now
... which is bound to this part of the DOM, which is added into it, via a directive:
<form ng-controller="controlFormulario as formCtrl">
<div class="element">
<div class="form-group" ng-class="{'has-error': formData.titulo.length > 50 }">
<label for="inputTitulo">Título</label>
<input type="titulo" class="form-control" id="inputTitulo" ng-model="formData.titulo">
<span ng-show="formData.titulo.length > 50" id="helpBlock" class="help-block">El título no puede exceder los 50 caracteres.</span>
</div>
<div class="form-group">
<label for="inputTexto">Texto</label>
<textarea class="form-control" id="inputTexto" ng-model="formData.texto"></textarea>
</div>
<div class="form-group">
<label for="fecha">Fecha</label>
<input type="fecha" class="form-control" id="fecha" ng-model="formData.fecha" disabled>
</div>
<div class="form-group" >
<button class="btn btn-primary" style="height:35px;width:100px;float:right;" id="submit"
ng-disabled="isDisabled()" ng-click="submit()">
Enviar
</button>
</div>
</div>
<div class="note" ng-show="formData.titulo.length > 0">
<div class="title" ng-model="formData.titulo" class="title">{{formData.titulo | limitTo:50}}</div>
<div class="text" ng-model="formData.texto" class="text">{{formData.texto}}</div>
<div class="date" ng-model="formData.fecha" class="date">{{formData.fecha | date}}</div>
</div>
</form>
This is my directive (I don't think it's really needed, but just in case):
app.directive('formulario', [function() {
return {
restrict: 'E', // C: class, E: element, M: comments, A: attributes
templateUrl: 'modules/formulario.html',
};
}]);
I use a service for passing the data between the previous controller, and the note controller (which controls the note objects of the array). This is the service:
app.factory('SubmitService', function($rootScope) {
var data = {};
data.prepForBroadcast = function(recvData) {
data.data = recvData;
this.broadcastItem();
};
data.broadcastItem = function() {
$rootScope.$broadcast('handleBroadcast');
};
return data;
});
... and I receive it in this part of my note controller:
app.controller('noteController', ['$scope', 'SubmitService', function($scope, submitService) {
var nc = this;
$scope.$on('handleBroadcast', function() {
nc.pruebaNota.push(submitService.data);
$scope.formData.titulo = "";
$scope.formData.texto= "";
$scope.formData.fecha = new Date();
});
// more things, the array, etc...
Ok. This should work, and it does, but something strange happens: as you can see, the preview note is binded with ng-model to the form. That works great, ok. But when I submit the form, the new note object keeps bound to the form (so if I delete the form text, the note appears in blank, and if I write something, it gets automatically updated both in the preview note, and the new note), when there isn't any relation between them. The new note, which appears dynamically on the screen, shouldn't be bound to anything.
Am I doing something wrong? Some help would be really nice!
You are forgetting something really important. Memory address. So, the rought idea is something like: imagine that $scope.formData is in the address 123123. You first create a temp var pointing to 123123 then you send it to the service and the service holds the same address 123123 into data.data.
Then in your second controller you say: hey, I want to work with that data.data (AKA your data in 123123) you have SubmitService.
Now when you modify $scope.formData again, you are updating what you have in that 123123 and everything that is "looking" into that address will be updated.
That is the rough idea. To point it simple, you're modifying the same piece of information everywhere.
See it here: http://plnkr.co/edit/zcEDQLHFWxYg4D7FqlmP?p=preview
As a AWolf suggested, to fix this issue, you can use angular.copy like this:
nc.pruebaNota.push(angular.copy(submitService.data));

How to access data from multiple instances of the same child controller?

I have two types of controllers:
The first type of controller only appears once on an html page ("SingleChildController").
The second type of controller appears twice on the same page ("MultipleChildController") and is differentiated by the value of its property "instance".
Both of these are nested within my ParentController (see code below).
How can I access data from any of my 3 child controller instances in the ParentController?
I have tried adding two services, one for the SingleChildController and one for the MultipleChildController, but it seems like by doing so I am duplicating a lot of code. This is especially true in my actual code where each controller has many properties to monitor. Is there a better way to accomplish this?
EDIT: For further clarification, this view is for a form and I want to get all of my data back up into the ParentController so that I can submit my form.
(function() {
var app = angular.module('myApp', []);
app.controller('ParentController', ['$scope',
function($scope) {
var parent = this;
parent.singleName = "";
parent.multiple1Name = "";
parent.multiple2Name = "";
}
]);
app.controller('SingleChildController', ['$scope',
function($scope) {
var single = this;
single.name = "";
}
]);
app.controller('MultipleChildController', ['$scope',
function($scope) {
var multiple = this;
multiple.instance = "";
multiple.name = "";
}
]);
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="ParentController as parent">
<!-- I would like these to show the data that was inputted below -->
ParentSingleName: {{parent.singleName}} <br />
ParentMultiple1Name: {{parent.multiple1Name}} <br />
ParentMultiple2Name: {{parent.multiple2Name}} <br /><br />
<div ng-controller="SingleChildController as single">
<input type="text" ng-model="single.name" />: {{single.name}}
</div>
<div ng-controller="MultipleChildController as multiple1" ng-init="multiple1.instance='first'">
<input type="text" ng-model="multiple1.name" />: {{multiple1.instance}} - {{multiple1.name}}
</div>
<div ng-controller="MultipleChildController as multiple2" ng-init="multiple2.instance='second'">
<input type="text" ng-model="multiple2.name" />: {{multiple2.instance}} - {{multiple2.name}}
</div>
</div>

Angularjs - How to 'ng-repeat' a div for 'value in ng-model'-times?

Using Angularjs, I want to create say 10 <div> if user inputs '10' in a text box.
<input type="text" id="gcols" placeholder="Number of Columns" ng-model="cols"/>
So whatever value user enters in this box, those many shall be created. So my generalized question is, how to 'ng-repeat' a <div> for 'ng-model' times?
UPDATE:
Appreciating for all of your answers, I did something like following, by referring your answers. And that is working as of now, but tell me if any other logic is more efficient than this.
$scope.divs = new Array();
$scope.create=function(){ //I added a button & invoked this function on its ng-click
var a = $scope.cols;
for(i=0;i<a;i++)
{
$scope.divs.push(a);
}
alert($scope.divs);
};
You need to create an array to iterate on it.
In your controller:
app.controller('ctrl', function ($scope) {
$scope.cols = 0;
$scope.arr = [];
$scope.makeArray = function () {
$scope.arr.length = 0;
for (var i = 0; i < parseInt($scope.cols) ; i++) {
$scope.arr.push(i);
}
}
});
In your view:
<div ng-controller="ctrl">
<input ng-model="cols" type="text" ng-change="makeArray()" />
<div ng-repeat="o in arr">hi</div>
</div>
JSFiddle
DEMO: http://jsfiddle.net/JsFUW/
In your module/app/controller
$scope.cols =4; /* a default */
$scope.divs =[]; /* whatever these are */
In the template
<input type="text" id="gcols" placeholder="Number of Columns" ng-model="cols"/>
<div ng-repeat="div in divs | limitTo:cols" >
...
</div>
As comment question , try this :
In the controller
$scope.cols
In the template
<input type="text" id="gcols" placeholder="Number of Columns" ng-model="cols"/>
<h3>cols: {{cols}} </h3>
Do you see that value change on the page as you type ? If not your $scope.cols is not in the template scope.
In your view:
<input ng-model="cols" ng-change="colsChanged()"/>
<div ng-repeat="item in items track by $index">{{$index}} {{item}}</div>
In your controller:
app.controller('MyCtrl', function($scope) {
$scope.cols = 0;
$scope.colsChanged = function () {
$scope.items = new Array(+$scope.cols);
};
//optional: if you're starting with 1 or more "cols"
$scope.colsChanged();
});
Alternatively, you could roll your own directive that used transclusion to repeat itself multiple times, but that could get involved and probably isn't going to gain you much.

Dynamically assign ng-model

I'm trying to generate a set of check-boxes from an object array. I'm aiming to have the check-boxes dynamically map their ng-model to a property of the new object that will be submitted into the array.
What I had in mind is something like
<li ng-repeat="item in items">
<label>{{item.name}}</label>
<input type="checkbox" ng-model="newObject.{{item.name}}">
</li>
This doesn't work as can be seen on this JSFiddle:
http://jsfiddle.net/GreenGeorge/NKjXB/2/
Can anybody help?
This should give you desired results:
<input type="checkbox" ng-model="newObject[item.name]">
Here is a working plunk: http://plnkr.co/edit/ALHQtkjiUDzZVtTfLIOR?p=preview
EDIT
As correctly noted in the comments using this with ng-change requires a "dummy" ng-model to be present beforehand. It should however be noted that apparently with 1.3 the required options have been provided by the framework. Please check out https://stackoverflow.com/a/28365515/3497830 below!
/EDIT
Just in case you are like me stumbling over a simple case while having a more complex task, this is the solution I came up with for dynamically binding arbitrary expressions to ng-model: http://plnkr.co/edit/ccdJTm0zBnqjntEQfAfx?p=preview
Method: I created a directive dynamicModel that takes a standard angular expression, evaluates it and links the result to the scope via ng-model and $compile.
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.data = {};
$scope.testvalue = 'data.foo';
$scope.eval = $scope.$eval;
});
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.data = {};
$scope.testvalue = 'data.foo';
$scope.eval = $scope.$eval;
});
app.directive('dynamicModel', ['$compile', function ($compile) {
return {
'link': function(scope, element, attrs) {
scope.$watch(attrs.dynamicModel, function(dynamicModel) {
if (attrs.ngModel == dynamicModel || !dynamicModel) return;
element.attr('ng-model', dynamicModel);
if (dynamicModel == '') {
element.removeAttr('ng-model');
}
// Unbind all previous event handlers, this is
// necessary to remove previously linked models.
element.unbind();
$compile(element)(scope);
});
}
};
}]);
Usage is simply dynamic-model="angularExpression" where angularExpression results in a string that is used as the expression for ng-model.
I hope this saves someone the headache of having to come up with this solution.
Regards,
Justus
With Angular 1.3, you can use ng-model-options directive to dynamically assign the model, or bind to an expression.
Here is a plunkr: http://plnkr.co/edit/65EBiySUc1iWCWG6Ov98?p=preview
<input type="text" ng-model="name"><br>
<input type="text" ng-model="user.name"
ng-model-options="{ getterSetter: true }">
More info on ngModelOptions here: https://docs.angularjs.org/api/ng/directive/ngModelOptions
This is my approach to support deeper expression, e.g. 'model.level1.level2.value'
<input class="form-control" ng-model="Utility.safePath(model, item.modelPath).value">
where item.modelPath = 'level1.level2' and
Utility(model, 'level1.level2') is the utility function that returns model.level1.level2
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<body>
<div ng-app="myApp" ng-controller="myCtrl">
<form name="priceForm" ng-submit="submitPriceForm()">
<div ng-repeat="x in [].constructor(9) track by $index">
<label>
Person {{$index+1}} <span class="warning-text">*</span>
</label>
<input type="number" class="form-control" name="person{{$index+1}}" ng-model="price['person'+($index+1)]" />
</div>
<button>Save</button>
</form>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function ($scope) {
$scope.price = [];
$scope.submitPriceForm = function () {
//objects be like $scope.price=[{person1:value},{person2:value}....]
console.log($scope.price);
}
});
</script>
</body>
</html>

Categories

Resources