Accessing directive scope in controller using service - angularjs

I was trying to access a value returned by a directive in my controller. I tried with a service. It's not working; it seems like the updated directive's return value is not picked up by the controller.
Here's my code:
HTML
<html ng-app="myApp">
<body ng-controller="MainCtrl">
<input type="file" file-reader="fileContent" />
<div>{{fileContent}}</div>
</body>
</html>
Controller
myApp.controller('MainCtrl', function($scope,sharedTrialService) {
$scope.newValue = sharedTrialService.TrialInfo;
$scope.newData = $scope.newValue.data;
});
Directive
myApp.directive('fileReader', function(sharedTrialService) {
return {
scope: {
fileReader:"="
},
link: function(scope, element) {
$(element).on('change', function(changeEvent) {
var files = changeEvent.target.files;
if (files.length) {
var r = new FileReader();
r.onload = function(e) {
var contents = e.target.result.replace(/\r\n|\r/g,'\n');
scope.$apply(function () {
var lines=contents.split('\n');
scope.fileReader = lines;
sharedTrialService.TrialInfo.data=scope.fileReader;
});
};
r.readAsText(files[0]);
}
});
}
};
Service
myApp .factory('sharedTrialService', function () {
return {
TrialInfo: {
data: " "
}
};
})
});
How can I access the directive's sharedTrialService.TrialInfo.data from my controller?

With $scope.newValue = sharedTrialService.TrialInfo.data; you are just assign a string to newValue, but you need to assign a reference, so you need to assign an object or an array. So change it to $scope.newValue = sharedTrialService.TrialInfo; and access your data via newValue.data.
EDIT
Use $watch in your controller for debugging:
$scope.$watch('newValue', function() {
console.log('newValue:', $scope.newValue);
}, true);
$scope.$watch('newData', function() {
console.log('newData:', $scope.newData);
}, true);
I think, $scope.newValue should change as it's an object reference, but $scope.newData is just a string value and is the same as stated above, it won't change. So, you always need to access it via $scope.newValue.data.
From console
newValue: Object {data: " "}
newData:
newValue: Object {data: Array[6]}
$scope.newData won't update, because it has still just the reference to that string value.
In your service you define your string value " ", TrialInfo.data holds only a reference to that string value. With $scope.newData = $scope.newValue.data; you copy that reference to $scope.newData. In your directive you copy the reference of the filereader to sharedTrialService.TrialInfo.data, but $scope.newData has still the reference to that string value. $scope.newData and TrialInfo.data hold different references.
$scope.newValue holds a reference to the object TrialInfo. You can do everything with its properties, but the reference to that object remains unchanged. So, if the data property changed you can still access it via $scope.newValue.data.
Maybe I'm not the best to explain it.

Related

Why does my scope binding to a service array not update when the array gets replaced

I have a scope binding to an array stored in a service.
When the array changes, the scope notices the change and updates the values in my template.
However, when the Array gets replaced by another array, the scope doesn't seem to recognize a change and won't update the list.
I know that this is a common behaviour of angularjs and that it's probably intended to be like this, but I don't get why.
In my understanding, the scope variable should update, whenever the bound reference changes.
Is $scope.myVar = anyOtherVar; not equivalent to a $scope.$watch('anyOtherVar',function(..){//update myVar}); ?
See my fiddle for demonstrating the problem.
http://jsfiddle.net/sL9k7q9L/1/
var myApp = angular.module('myApp',[]);
//myApp.directive('myDirective', function() {});
myApp.factory('myService', function() {
var anyArray = [{"name":"peter"}];
var anyOtherArray = [{"name":"laura"}];
return {
anyArray: anyArray,
newElement: function(){
anyArray.push({"name":"bob"});
},
replaceWholeArray: function(){
anyArray = anyOtherArray;
console.log(anyArray);
}
}
});
function MyCtrl($scope,myService) {
$scope.elements = myService.anyArray;
$scope.newElement = function(){
myService.newElement();
}
$scope.replaceWholeArray = function(){
myService.replaceWholeArray();
}
}
and the corresponding template:
<div ng-controller="MyCtrl">
<button ng-click="newElement()">
newElement()
</button>
<button ng-click="replaceWholeArray()">
replaceWholeArray()
</button>
<ul>
<li ng-repeat="el in elements">{{el.name}}</li>
</ul>
</div>
You are updating variables but that doesn't update any other variable assignments that were made using the original variable.
Thus reference to original array is broken for myService.anyArray
Simple example
var a = 1;
var b = a;
a = 2;
alert(b);// is still 1 due to value of `a` when it was assigned
Instead just update the factory object property but to do that you need to store a reference to the returned object first
myApp.factory('myService', function() {
var anyArray = [...];
var anyOtherArray = [...];
var factoryObject = {
anyArray: anyArray,
newElement: function() {
anyArray.push({"name": "bob"});
},
replaceWholeArray: function() {
// change this part
//anyArray = anyOtherArray;
// To:
factoryObject.anyArray = anyOtherArray;
}
}
return factoryObject
});

Why I dont get array in template?

I have ng-click method showSchedules() that calls AJAX:
$scope.showSchedules = function () {
$scope.loadCalendar = true;
appointmentService.getSchedule().then(function (response) {
calendarService.set(response.data.calendar, function () {
$timeout(function () {
$scope.refleshCalendars();
$scope.loadCalendar = false;
console.log($scope.events);
}, 100);
});
});
};
Inside this method you can see: console.log($scope.events);
It gives me filled array by objects.
When I do {{events}} in trmplate HTML I get [].
Why I get empty array?
HTML code:
<div ng-controller="ScheduleController as vmx">
{{events}}
<mwl-calendar
events="events"
view="vmo.calendarView"
view-title="vmo.calendarTitle"
current-day="vmo.calendarDay"
on-event-click="vmo.eventClicked(calendarEvent)"
on-event-times-changed="vmo.eventTimesChanged(calendarEvent);
calendarEvent.startsAt = calendarNewEventStart;
calendarEvent.endsAt = calendarNewEventEnd"
auto-open="true"
day-view-start="06:00"
day-view-end="23:00"
day-view-split="30"
cell-modifier="vmo.modifyCell(calendarCell)">
</mwl-calendar>
</div>
You want {{vmx.events}} because you are using the controller as annotation
Using controller as makes it obvious which controller you are
accessing in the template when multiple controllers apply to an
element.
ngController documentation (Search for controller as)

Bind javascript method in Angularjs template

My application is using Angularjs at client side. I have five directive which are using same logic. Following are required details of my code
I have one pure javascript class as AppUtilities.js having following method defined
var AppUtilities = {
//Get the guid for given collection
getGUID: function (collectionName) {
var prefix;
var guid;
//get first two character
if (!Utilities.isEmpty(collectionName)) {
prefix = collectionName.substring(0, 2);
//Get timestamp
var timestamp = Utilities.getTimestampId();
//concate prefix to get the guid
guid = prefix + timestamp;
}
return guid;
}
};
I have five different directive in which I need to use "getGUID()" method to bind with template. Since template is only able to bind with scope function therefore I have defined scope method in all these five template as below
scope.getGUID = function (collectionName) {
return AppUtilities.getGUID(collectionName);
}
Then in all the five directive template, this method is bind as scope variable
<h4 class="guid" id="guid" data-ng-bind-template="{{getGUID('goal')}}"></h4>
How can I avoid declaring these method as scope variable and directly use as AppUtilities.getGUID(collectionName) in the HTML template?
There are multiple ways, but honestly, it seems like more effort than its worth, since you can just do:
scope.getGUID = AppUtilities.getGUID;
Of course, you could use $rootScope, but to me personally it feels wrong - I like when things are explicitly declared and do not magically appear.
Alternatively, if you only need to render the GUID in the UI, create a GUID directive. For example:
.directive("guid", function(){
return {
template: "<span>{{getGUID()}}</span>",
link: function(scope, element, attrs){
scope.getGUID = function(){
return AppUtilities.getGUID(attrs.guid || attrs.name);
};
}
}
});
and use as:
<h4 class="guid"><guid name="goal"></guid></h4>
Without manipulating the individual scopes or the root scope, you could simply define a filter which is usable in all templates. Note that, for all the good reasons, I'd still define and inject AppUtilities, even if it is a global, as it's own service.
app.filter('toGUID', ['AppUtilities', function (AppUtilities) {
return function (input) {
return AppUtilities.getGUID(input);
};
}]);
// <pre>{{ 'goal'|toGUID }}</pre>
app.service('AppUtilities', function () {
return AppUtilities;
});
(function (app, ng) {
'use strict';
app.filter('toGUID', ['AppUtilities', function (AppUtilities) {
return function (input) {
return AppUtilities.getGUID(input);
};
}]);
app.service('AppUtilities', function () {
return AppUtilities;
});
var Utilities = {
isEmpty: function (collectionName) {
return false;
},
getTimestampId: function () {
return '_something';
}
};
var AppUtilities = {
//Get the guid for given collection
getGUID: function (collectionName) {
var prefix;
var guid;
//get first two character
if (!Utilities.isEmpty(collectionName)) {
prefix = collectionName.substring(0, 2);
//Get timestamp
var timestamp = Utilities.getTimestampId();
//concat prefix to get the guid
guid = prefix + timestamp;
}
return guid;
}
};
})(angular.module('app', []), angular);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0-rc.2/angular.min.js"></script>
<div data-ng-app="app">
<pre>{{ 'goal'|toGUID }}</pre>
</div>
Hide it in a div:
<div id="hidden" data-ng-model="hidden" style="display:none"></div>
<script>
$('#hidden').html(AppUtilities.getGUID(collectionName));
</script>
Then, ng-bind to the div's contents:
<div id="realDiv" data-ng-bind-html="hidden"></div>

Backbone is interpolating query string in model URL. How do I stop it?

I have a Backbone model that looks like this
...
var Address = Backbone.Model.extend({
urlRoot: '/address/'
});
return { address: Address }
});
I have a template that prints out an address in a form. The template is rendered by a view that is passed an address id in it's 'render' function. The view is reached by a route like this 'address/:id'.
The view looks like this:
var AddressView = Backbone.View.extend({
el: $('#myclass'),
render: function(options) {
var that = this;
var addr = new A.address({id: options.aid});
addr.fetch({
reset: true,
success: function(address) {
var template = _.template(ATemplate, {address: address});
that.$el.html(template);
}
});
return this;
},
events: {
'submit .edit-address-form': 'editAddress'
},
editAddress: function(ev) {
//serializeObject creates JSON object from form data
var addressDetails = $(ev.currentTarget).serializeObject();
addr.save(addressDetails, function(addr) {
R.router.navigate('', {trigger: true});
});
return false;
}
});
return {
addressView: new AddressView()
};
});
There are two problems. The first problem is that the 'editAddress' function is never getting called, even though the class name is correct and the button type = is 'submit'.
The second problem is when I submit the address form the URL is garbled, a query string is interpolated between the base URL and /#/address, as in
http:///ldmanclient/address=2500+Moffitt+Library&address2=University+of+California%2C+Berkeley&city=Berkeley&zipcode=94720&mailcode=6000&aid=1#/address/1
Has anyone seen this type of behavior before? What am I doing wrong?
As mu said, the form is being submitted the standard way before Backbone gets to it. Try preventing the submit action:
editAddress: function(ev) {
ev.preventDefault();
// same code as above
}

Proper place for data-saving logic in AngularJS

App design question. I have a project which has a very large number of highly customized inputs. Each input is implemented as a directive (and Angular has made this an absolute joy to develop).
The inputs save their data upon blur, so there's no form to submit. That's been working great.
Each input has an attribute called "saveable" which drives another directive which is shared by all these input types. the Saveable directive uses a $resource to post data back to the API.
My question is, should this logic be in a directive at all? I initially put it there because I thought I would need the saving logic in multiple controllers, but it turns out they're really happening in the same one. Also, I read somewhere (lost the reference) that the directive is a bad place to put API logic.
Additionally, I need to introduce unit testing for this saving logic soon, and testing controllers seems much more straightforward than testing directives.
Thanks in advance; Angular's documentation may be… iffy… but the folks in the community are mega-rad.
[edit] a non-functional, simplified look at what I'm doing:
<input ng-model="question.value" some-input-type-directive saveable ng-blur="saveModel(question)">
.directive('saveable', ['savingService', function(savingService) {
return {
restrict: 'A',
link: function(scope) {
scope.saveModel = function(question) {
savingService.somethingOrOther.save(
{id: question.id, answer: question.value},
function(response, getResponseHeaders) {
// a bunch of post-processing
}
);
}
}
}
}])
No, I don't think the directive should be calling $http. I would create a service (using the factory in Angular) OR (preferably) a model. When it is in a model, I prefer to use the $resource service to define my model "classes". Then, I abstract the $http/REST code into a nice, active model.
The typical answer for this is that you should use a service for this purpose. Here's some general information about this: http://docs.angularjs.org/guide/dev_guide.services.understanding_services
Here is a plunk with code modeled after your own starting example:
Example code:
var app = angular.module('savingServiceDemo', []);
app.service('savingService', function() {
return {
somethingOrOther: {
save: function(obj, callback) {
console.log('Saved:');
console.dir(obj);
callback(obj, {});
}
}
};
});
app.directive('saveable', ['savingService', function(savingService) {
return {
restrict: 'A',
link: function(scope) {
scope.saveModel = function(question) {
savingService.somethingOrOther.save(
{
id: question.id,
answer: question.value
},
function(response, getResponseHeaders) {
// a bunch of post-processing
}
);
}
}
};
}]);
app.controller('questionController', ['$scope', function($scope) {
$scope.question = {
question: 'What kind of AngularJS object should you create to contain data access or network communication logic?',
id: 1,
value: ''
};
}]);
The relevant HTML markup:
<body ng-controller="questionController">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="saveModel(question)" />
</body>
An alternative using only factory and the existing ngResource service:
However, you could also utilize factory and ngResource in a way that would let you reuse some of the common "saving logic", while still giving you the ability to provide variation for distinct types of objects / data that you wish to save or query. And, this way still results in just a single instantiation of the saver for your specific object type.
Example using MongoLab collections
I've done something like this to make it easier to use MongoLab collections.
Here's a plunk.
The gist of the idea is this snippet:
var dbUrl = "https://api.mongolab.com/api/1/databases/YOURDB/collections";
var apiKey = "YOUR API KEY";
var collections = [
"user",
"question",
"like"
];
for(var i = 0; i < collections.length; i++) {
var collectionName = collections[i];
app.factory(collectionName, ['$resource', function($resource) {
var resourceConstructor = createResource($resource, dbUrl, collectionName, apiKey);
var svc = new resourceConstructor();
// modify behavior if you want to override defaults
return svc;
}]);
}
Notes:
dbUrl and apiKey would be, of course, specific to your own MongoLab info
The array in this case is a group of distinct collections that you want individual ngResource-derived instances of
There is a createResource function defined (which you can see in the plunk and in the code below) that actually handles creating a constructor with an ngResource prototype.
If you wanted, you could modify the svc instance to vary its behavior by collection type
When you blur the input field, this will invoke the dummy consoleLog function and just write some debug info to the console for illustration purposes.
This also prints the number of times the createResource function itself was called, as a way to demonstrate that, even though there are actually two controllers, questionController and questionController2 asking for the same injections, the factories get called only 3 times in total.
Note: updateSafe is a function I like to use with MongoLab that allows you to apply a partial update, basically a PATCH. Otherwise, if you only send a few properties, the entire document will get overwritten with ONLY those properties! No good!
Full code:
HTML:
<body>
<div ng-controller="questionController">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="save(question)" />
</div>
<div ng-controller="questionController2">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="save(question)" />
</div>
</body>
JavaScript:
(function() {
var app = angular.module('savingServiceDemo', ['ngResource']);
var numberOfTimesCreateResourceGetsInvokedShouldStopAt3 = 0;
function createResource(resourceService, resourcePath, resourceName, apiKey) {
numberOfTimesCreateResourceGetsInvokedShouldStopAt3++;
var resource = resourceService(resourcePath + '/' + resourceName + '/:id',
{
apiKey: apiKey
},
{
update:
{
method: 'PUT'
}
}
);
resource.prototype.consoleLog = function (val, cb) {
console.log("The numberOfTimesCreateResourceGetsInvokedShouldStopAt3 counter is at: " + numberOfTimesCreateResourceGetsInvokedShouldStopAt3);
console.log('Logging:');
console.log(val);
console.log('this =');
console.log(this);
if (cb) {
cb();
}
};
resource.prototype.update = function (cb) {
return resource.update({
id: this._id.$oid
},
angular.extend({}, this, {
_id: undefined
}), cb);
};
resource.prototype.updateSafe = function (patch, cb) {
resource.get({id:this._id.$oid}, function(obj) {
for(var prop in patch) {
obj[prop] = patch[prop];
}
obj.update(cb);
});
};
resource.prototype.destroy = function (cb) {
return resource.remove({
id: this._id.$oid
}, cb);
};
return resource;
}
var dbUrl = "https://api.mongolab.com/api/1/databases/YOURDB/collections";
var apiKey = "YOUR API KEY";
var collections = [
"user",
"question",
"like"
];
for(var i = 0; i < collections.length; i++) {
var collectionName = collections[i];
app.factory(collectionName, ['$resource', function($resource) {
var resourceConstructor = createResource($resource, dbUrl, collectionName, apiKey);
var svc = new resourceConstructor();
// modify behavior if you want to override defaults
return svc;
}]);
}
app.controller('questionController', ['$scope', 'user', 'question', 'like',
function($scope, user, question, like) {
$scope.question = {
question: 'What kind of AngularJS object should you create to contain data access or network communication logic?',
id: 1,
value: ''
};
$scope.save = function(obj) {
question.consoleLog(obj, function() {
console.log('And, I got called back');
});
};
}]);
app.controller('questionController2', ['$scope', 'user', 'question', 'like',
function($scope, user, question, like) {
$scope.question = {
question: 'What is the coolest JS framework of them all?',
id: 1,
value: ''
};
$scope.save = function(obj) {
question.consoleLog(obj, function() {
console.log('You better have said AngularJS');
});
};
}]);
})();
In general, things related to the UI belong in a directive, things related to the binding of input and output (either from the user or from the server) belong in a controller, and things related to the business/application logic belong in a service (of some variety). I've found this separation leads to very clean code for my part.

Resources