angular service not manipulating values as expected - angularjs

I have an angular service to handle my promises, but I am not sure how to manipulate the values on $scope from within the service. I understand what I am doing is wrong, but don't understand what I need to do to be right.
In my service:
.service('resolvePromiseService', function(){
var publicInterface = {
resolvePromise: resolvePromise
}
function resolvePromise(promise, resultObject, callBackFunction, spinner){
spinner ++;
promise.then(function(result){
resultObject = result;
if(callBackFunction){
callBackFunction();
}
});
promise['catch'(function(error){
//generic error handling
});
promise['finally'(function(){
spinner--;
});
}
and in my controller which calls the service
var getInfoPromise = dataAccessService.getInfoByLocationId(locationId).$promise;
resolvePromiseService.resolvePromise(getInfoPromise, $scope.locationInfo, $scope.setUpLocation, $scope.loadingSpinner);
Inside the resolvePromise function, I am seeing the values come in as expected and being updated as expected, but I think I am misunderstanding the way $scope is passed around. I believe I am replacing the angular objects with vanilla javascript objects.

The best option would be for you to pass in your scope object or rethink how you are handling promises.
The reason this is happening is to do with modifying references.
In your example
$scope.loadingSpinner = 5; // ref1 - val 5
function resolvePromise(promise, resultObject, callBackFunction, spinner){
// when we enter the function both $scope.loadingSpinner and spinner are both
// referencing the same variable
spinner; // ref1 - val5
// after we increment spinner the local reference is no longer pointing
// at the reference
spinner; // ref2 - val6
// therefore we are not actually updating the $scope.loadingSpinner
//reference just the local spinner one
}
I have included a snippet to demonstrate this point - you need to keep in mind that you are reassigning the reference which is not what you intend to do in this instance.
angular
.module('Test',[])
.service('IncrementService', incrementService)
.controller('MyController', myController)
function incrementService() {
this.increment = function(valToInc) {
valToInc++;
console.log(valToInc);
}
this.incrementScopeField = function($scope, field) {
$scope[field]++;
console.log($scope[field]);
}
}
myController.$inject = ['$scope', 'IncrementService'];
function myController($scope, IncrementService) {
$scope.number = 5;
$scope.inc = function() {
IncrementService.increment($scope.number);
}
$scope.inc2 = function() {
IncrementService.incrementScopeField($scope, 'number');
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='Test' ng-controller='MyController'>
<button ng-click='inc()'>increment value</button><button ng-click='inc2()'>increment scope</button>
<br/>{{ number }}
</div>

Related

How do I pass the variable in a function into my controller?

I'm trying to pass the videoUrl variable in the showResponse function into my controller. I've been trying to figure out a solution without success. Can anyone guide me in the right direction?
var myApp = angular.module('myApp', []);
myApp.controller('mainCtrl', ['$scope', function($scope){
$scope.videoUrl = videoUrl;
}])
// Helper function to display JavaScript value on HTML page.
function showResponse(response) {
var videoUrl = [];
for (prop in response.items) {
videoUrl[prop] = "https://www.youtube.com/embed/" + response.items[prop].snippet.resourceId.videoId;
}
}
// Called automatically when JavaScript client library is loaded.
function onClientLoad() {
gapi.client.load('youtube', 'v3', onYouTubeApiLoad);
}
// Called automatically when YouTube API interface is loaded
function onYouTubeApiLoad() {
gapi.client.setApiKey('#######');
search();
}
function search() {
// Use the JavaScript client library to create a search.list() API call.
var request = gapi.client.youtube.playlistItems.list({
part: 'snippet',
playlistId: '########'
});
// Send the request to the API server,
// and invoke onSearchRepsonse() with the response.
request.execute(onSearchResponse);
}
// Called automatically with the response of the YouTube API request.
function onSearchResponse(response) {
showResponse(response);
}
It would probably better/easier if you could get this stuff into angular, so that it's all happening within services. That's how data sharing is supposed to happen in angular. But maybe that's challenging due to the nature of onClientLoad. The dirty way to do it is:
Get the controller's scope directly and set it on that scope. Assuming you've got something defined like:
<div ng-controller="mainCtrl"></div>
you can get that controller's scope using jQuery:
function showResponse(response) {
var videoUrl = [];
for (prop in response.items) {
videoUrl[prop] = "https://www.youtube.com/embed/" + response.items[prop].snippet.resourceId.videoId;
}
var scope = $('[ng-controller="mainCtrl"]').scope();
scope.videoUrl = videoUrl;
}
Note that this will cause angular purists to weep and gnash their teeth.

AngualrJS: Why can only bind to reference values but primitive values of services?

I have created a plunker to test binding $scope on services. And I found that I can only bind to primitive values but reference values of a service.
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.7/angular.min.js"></script>
<body ng-app="BindToService">
<div ng-controller="BindToServiceCtrl as ctrl">
// I wonder why I could only bind on reference but primitive values of a service.
counterFactoryObj: {{counterFactoryObj}}<br/>
counterFactoryVal: {{counterFactoryVal}}<br/>
counterFactoryFun(): {{counterFactoryFun()}}<br/>
</div>
<script type="text/javascript">
var app = angular.module("BindToService", []);
app.controller("BindToServiceCtrl", function ($scope, CounterFactory) {
$scope.counterFactoryObj = CounterFactory.obj;
$scope.counterFactoryVal = CounterFactory.val;
$scope.counterFactoryFun = CounterFactory.fun;
});
app.factory("CounterFactory", function ($interval) {
var obj = [0];
var val = 0;
var fun = function () { return val; };
var addCounter = function () {
obj[0] += 1;
val += 1;
};
$interval(addCounter, 1000);
return {
obj: obj,
val: val,
fun: fun
};
});
</script>
</body>
In this demo, only counterFactoryObj and counterFactoryFun() change by time. I wonder how AngularJS binds values.
There are a few things here that I think are causing confusion.
First, when you return an object with:
return {
val: someVal
}
the property val of the object is set at the time you return it to whatever the value was of someVal. So, when you return your CounterFactory service, CounterFactory.val === 0 - always - and it's not related to Angular's binding.
Second, with CounterService service, your CounterService instance has this.val and in fact it is being updated with $interval (try it with console.log). The problem there is that you assign its value in a particular point in time (e.g. when controller function runs) to the $scope.counterServiceVal variable. Here, although CounterService.val changes, the following remains true: $scope.counterServiceVal === 0.
Btw, both .service and .factory return a singleton service instance as far as Angular is concerned, except in one your return a new-able Function and in the other - the actual instance, but that is just related to what Angular does to obtain the actual service instance - after the service is injected it behaves the same way.
And, finally, if you want to bind to a value - you can, but you need to actually change that value and bind to that changing variable. Here's how you'd do this with your CounterFactory, as an example:
app.factory("CounterService", function ($interval) {
var svc = { val: 0 };
function addCounter() {
svc.val += 1;
};
$interval(addCounter, 1000);
return svc;
});
// in controller
$scope.counterService = CounterService;
<!-- in the view -->
<span>{{counterService.val}}</span>

Reusable components in angular controller

I am trying to create a reusable component for my controllers which can be used multiple times in different controllers.
See plunker: http://plnkr.co/edit/Lc4z4L?p=preview
The problem shown in the plunker is, that in the FirstCtrl the same message is shown than in the SecondCtrl.
How can I achieve some kind of isolated scope with the service?
Or am I using the wrong concepts?
While it's true a service only has a single instance, you can also return a function which you can then new in your controller which will give you an individual instance of that function:
app.service('alertService', function($timeout) {
return function () {
// assign this to service only because I'm lazy
var service = this;
var timeout;
// start with empty array holding the alerts.
service.alert_list = [];
// method to add an alert
// alert_obj is a object with members type = ( success | info | warning | danger )
// and msg which is the message string
service.addAlert = function (alert_obj) {
service.alert_list = [];
service.alert_list.push(alert_obj);
$timeout.cancel(timeout);
timeout = $timeout(service.clearAlerts, 5000);
};
service.clearAlerts = function clearAlerts() {
service.alert_list = [];
};
}
});
Your updated controller would now look like this:
app.controller('SecondCtrl', function($scope, alertService, $timeout) {
$scope.alertService = new alertService();
$scope.alertService.addAlert({"type": "info", "msg": "Infomessage II"});
$scope.name = 'World II';
});
Updated plunker: http://plnkr.co/edit/RhJbbxj4XxdwY6GAest9?p=preview

Running AngularJS initialization code when view is loaded

When I load a view, I'd like to run some initialization code in its associated controller.
To do so, I've used the ng-init directive on the main element of my view:
<div ng-init="init()">
blah
</div>
and in the controller:
$scope.init = function () {
if ($routeParams.Id) {
//get an existing object
});
} else {
//create a new object
}
$scope.isSaving = false;
}
First question: is this the right way to do it?
Next thing, I have a problem with the sequence of events taking place. In the view I have a 'save' button, which uses the ng-disabled directive as such:
<button ng-click="save()" ng-disabled="isClean()">Save</button>
the isClean() function is defined in the controller:
$scope.isClean = function () {
return $scope.hasChanges() && !$scope.isSaving;
}
As you can see, it uses the $scope.isSaving flag, which was initialized in the init() function.
PROBLEM: when the view is loaded, the isClean function is called before the init() function, hence the flag isSaving is undefined. What can I do to prevent that?
When your view loads, so does its associated controller. Instead of using ng-init, simply call your init() method in your controller:
$scope.init = function () {
if ($routeParams.Id) {
//get an existing object
} else {
//create a new object
}
$scope.isSaving = false;
}
...
$scope.init();
Since your controller runs before ng-init, this also solves your second issue.
Fiddle
As John David Five mentioned, you might not want to attach this to $scope in order to make this method private.
var init = function () {
// do something
}
...
init();
See jsFiddle
If you want to wait for certain data to be preset, either move that data request to a resolve or add a watcher to that collection or object and call your init method when your data meets your init criteria. I usually remove the watcher once my data requirements are met so the init function doesnt randomly re-run if the data your watching changes and meets your criteria to run your init method.
var init = function () {
// do something
}
...
var unwatch = scope.$watch('myCollecitonOrObject', function(newVal, oldVal){
if( newVal && newVal.length > 0) {
unwatch();
init();
}
});
Since AngularJS 1.5 we should use $onInit which is available on any AngularJS component. Taken from the component lifecycle documentation since v1.5 its the preferred way:
$onInit() - Called on each controller after all the controllers on an
element have been constructed and had their bindings initialized (and
before the pre & post linking functions for the directives on this
element). This is a good place to put initialization code for your
controller.
var myApp = angular.module('myApp',[]);
myApp.controller('MyCtrl', function ($scope) {
//default state
$scope.name = '';
//all your init controller goodness in here
this.$onInit = function () {
$scope.name = 'Superhero';
}
});
Fiddle Demo
An advanced example of using component lifecycle:
The component lifecycle gives us the ability to handle component stuff in a good way. It allows us to create events for e.g. "init", "change" or "destroy" of an component. In that way we are able to manage stuff which is depending on the lifecycle of an component. This little example shows to register & unregister an $rootScope event listener $on. By knowing, that an event $on bound on $rootScope will not be unbound when the controller loses its reference in the view or getting destroyed we need to destroy a $rootScope.$on listener manually.
A good place to put that stuff is $onDestroy lifecycle function of an component:
var myApp = angular.module('myApp',[]);
myApp.controller('MyCtrl', function ($scope, $rootScope) {
var registerScope = null;
this.$onInit = function () {
//register rootScope event
registerScope = $rootScope.$on('someEvent', function(event) {
console.log("fired");
});
}
this.$onDestroy = function () {
//unregister rootScope event by calling the return function
registerScope();
}
});
Fiddle demo
Or you can just initialize inline in the controller. If you use an init function internal to the controller, it doesn't need to be defined in the scope. In fact, it can be self executing:
function MyCtrl($scope) {
$scope.isSaving = false;
(function() { // init
if (true) { // $routeParams.Id) {
//get an existing object
} else {
//create a new object
}
})()
$scope.isClean = function () {
return $scope.hasChanges() && !$scope.isSaving;
}
$scope.hasChanges = function() { return false }
}
I use the following template in my projects:
angular.module("AppName.moduleName", [])
/**
* #ngdoc controller
* #name AppName.moduleName:ControllerNameController
* #description Describe what the controller is responsible for.
**/
.controller("ControllerNameController", function (dependencies) {
/* type */ $scope.modelName = null;
/* type */ $scope.modelName.modelProperty1 = null;
/* type */ $scope.modelName.modelPropertyX = null;
/* type */ var privateVariable1 = null;
/* type */ var privateVariableX = null;
(function init() {
// load data, init scope, etc.
})();
$scope.modelName.publicFunction1 = function () /* -> type */ {
// ...
};
$scope.modelName.publicFunctionX = function () /* -> type */ {
// ...
};
function privateFunction1() /* -> type */ {
// ...
}
function privateFunctionX() /* -> type */ {
// ...
}
});

AngularJS access scope from outside js function

I'm trying to see if there's a simple way to access the internal scope of a controller through an external javascript function (completely irrelevant to the target controller)
I've seen on a couple of other questions here that
angular.element("#scope").scope();
would retrieve the scope from a DOM element, but my attempts are currently yielding no proper results.
Here's the jsfiddle: http://jsfiddle.net/sXkjc/5/
I'm currently going through a transition from plain JS to Angular. The main reason I'm trying to achieve this is to keep my original library code intact as much as possible; saving the need for me to add each function to the controller.
Any ideas on how I could go about achieving this? Comments on the above fiddle are also welcome.
You need to use $scope.$apply() if you want to make any changes to a scope value from outside the control of angularjs like a jquery/javascript event handler.
function change() {
alert("a");
var scope = angular.element($("#outer")).scope();
scope.$apply(function(){
scope.msg = 'Superhero';
})
}
Demo: Fiddle
It's been a while since I posted this question, but considering the views this still seems to get, here's another solution I've come upon during these last few months:
$scope.safeApply = function( fn ) {
var phase = this.$root.$$phase;
if(phase == '$apply' || phase == '$digest') {
if(fn) {
fn();
}
} else {
this.$apply(fn);
}
};
The above code basically creates a function called safeApply that calles the $apply function (as stated in Arun's answer) if and only Angular currently isn't going through the $digest stage. On the other hand, if Angular is currently digesting things, it will just execute the function as it is, since that will be enough to signal to Angular to make the changes.
Numerous errors occur when trying to use the $apply function while AngularJs is currently in its $digest stage. The safeApply code above is a safe wrapper to prevent such errors.
(note: I personally like to chuck in safeApply as a function of $rootScope for convenience purposes)
Example:
function change() {
alert("a");
var scope = angular.element($("#outer")).scope();
scope.safeApply(function(){
scope.msg = 'Superhero';
})
}
Demo: http://jsfiddle.net/sXkjc/227/
Another way to do that is:
var extScope;
var app = angular.module('myApp', []);
app.controller('myController',function($scope, $http){
extScope = $scope;
})
//below you do what you want to do with $scope as extScope
extScope.$apply(function(){
extScope.test = 'Hello world';
})
we can call it after loaded
http://jsfiddle.net/gentletech/s3qtv/3/
<div id="wrap" ng-controller="Ctrl">
{{message}}<br>
{{info}}
</div>
<a onClick="hi()">click me </a>
function Ctrl($scope) {
$scope.message = "hi robi";
$scope.updateMessage = function(_s){
$scope.message = _s;
};
}
function hi(){
var scope = angular.element(document.getElementById("wrap")).scope();
scope.$apply(function() {
scope.info = "nami";
scope.updateMessage("i am new fans like nami");
});
}
It's been a long time since I asked this question, but here's an answer that doesn't require jquery:
function change() {
var scope = angular.element(document.querySelector('#outside')).scope();
scope.$apply(function(){
scope.msg = 'Superhero';
})
}
Here's a reusable solution: http://jsfiddle.net/flobar/r28b0gmq/
function accessScope(node, func) {
var scope = angular.element(document.querySelector(node)).scope();
scope.$apply(func);
}
window.onload = function () {
accessScope('#outer', function (scope) {
// change any property inside the scope
scope.name = 'John';
scope.sname = 'Doe';
scope.msg = 'Superhero';
});
};
You can also try:
function change() {
var scope = angular.element( document.getElementById('outer') ).scope();
scope.$apply(function(){
scope.msg = 'Superhero';
})
}
The accepted answer is great. I wanted to look at what happens to the Angular scope in the context of ng-repeat. The thing is, Angular will create a sub-scope for each repeated item. When calling into a method defined on the original $scope, that retains its original value (due to javascript closure). However, the this refers the calling scope/object. This works out well, so long as you're clear on when $scope and this are the same and when they are different. hth
Here is a fiddle that illustrates the difference: https://jsfiddle.net/creitzel/oxsxjcyc/
I'm newbie, so sorry if is a bad practice. Based on the chosen answer, I did this function:
function x_apply(selector, variable, value) {
var scope = angular.element( $(selector) ).scope();
scope.$apply(function(){
scope[variable] = value;
});
}
I'm using it this way:
x_apply('#fileuploader', 'thereisfiles', true);
By the way, sorry for my english
<input type="text" class="form-control timepicker2" ng-model='programRow.StationAuxiliaryTime.ST88' />
accessing scope value
assume that programRow.StationAuxiliaryTime is an array of object
$('.timepicker2').on('click', function ()
{
var currentElement = $(this);
var scopeValues = angular.element(currentElement).scope();
var model = currentElement.attr('ng-model');
var stationNumber = model.split('.')[2];
var val = '';
if (model.indexOf("StationWaterTime") > 0) {
val = scopeValues.programRow.StationWaterTime[stationNumber];
}
else {
val = scopeValues.programRow.StationAuxiliaryTime[stationNumber];
}
currentElement.timepicker('setTime', val);
});
We need to use Angular Js built in function $apply to acsess scope variables or functions outside the controller function.
This can be done in two ways :
|*| Method 1 : Using Id :
<div id="nameNgsDivUid" ng-app="">
<a onclick="actNgsFnc()"> Activate Angular Scope</a><br><br>
{{ nameNgsVar }}
</div>
<script type="text/javascript">
var nameNgsDivVar = document.getElementById('nameNgsDivUid')
function actNgsFnc()
{
var scopeNgsVar = angular.element(nameNgsDivVar).scope();
scopeNgsVar.$apply(function()
{
scopeNgsVar.nameNgsVar = "Tst Txt";
})
}
</script>
|*| Method 2 : Using init of ng-controller :
<div ng-app="nameNgsApp" ng-controller="nameNgsCtl">
<a onclick="actNgsFnc()"> Activate Angular Scope</a><br><br>
{{ nameNgsVar }}
</div>
<script type="text/javascript">
var scopeNgsVar;
var nameNgsAppVar=angular.module("nameNgsApp",[])
nameNgsAppVar.controller("nameNgsCtl",function($scope)
{
scopeNgsVar=$scope;
})
function actNgsFnc()
{
scopeNgsVar.$apply(function()
{
scopeNgsVar.nameNgsVar = "Tst Txt";
})
}
</script>
This is how I did for my CRUDManager class initialized in Angular controller, which later passed over to jQuery button-click event defined outside the controller:
In Angular Controller:
// Note that I can even pass over the $scope to my CRUDManager's constructor.
var crudManager = new CRUDManager($scope, contextData, opMode);
crudManager.initialize()
.then(() => {
crudManager.dataBind();
$scope.crudManager = crudManager;
$scope.$apply();
})
.catch(error => {
alert(error);
});
In jQuery Save button click event outside the controller:
$(document).on("click", "#ElementWithNgControllerDefined #btnSave", function () {
var ngScope = angular.element($("#ElementWithNgControllerDefined")).scope();
var crudManager = ngScope.crudManager;
crudManager.saveData()
.then(finalData => {
alert("Successfully saved!");
})
.catch(error => {
alert("Failed to save.");
});
});
This is particularly important and useful when your jQuery events need to be placed OUTSIDE OF CONTROLLER in order to prevent it from firing twice.

Resources