AngularJs - service does not update controller - angularjs

I am trying to implement this simple Plunker with no success.
I have a simple service (factory) :
(function () {
'use strict';
angular
.module('app.core')
.factory('PropertiesService', PropertiesService);
function PropertiesService() {
var sefl = this;
self.name = 'Properties Service'
self.properties = {};
self.setProperties = function (prop)
{
self.properties = prop;
}
return self;
}
})();
I am calling this service from a directive :
var prop = { 'top': top, 'left': left, 'position': position };
service.setProperties(prop);
The contoller being updated is :
(function () {
'use strict';
angular
.module('app.properties')
.controller('Properties', Properties);
Properties.$inject = ['PropertiesService'];
function Properties(PropertiesService) {
var vm = this;
vm.collection = {};
setCollection();
function setCollection() {
for (var key in PropertiesService.properties) {
if (PropertiesService.properties.hasOwnProperty(key)) {
vm.collection[key] = PropertiesService.properties[key];
}
}
};
}
})();
Lastly , I am showing the data in html like this :
<div ng-controller="Properties as vm">
<ul class="list-group">
<li class="list-group-item" ng-repeat="(key, value) in vm.collection">
<span>{{key}}</span> : <span>{{value}}</span>
</li>
</ul>
</div>
I understand why i'm getting result only on initialization but I can not find the way to solve it,to make the controller updated when the service is changed (and it does) .
Thanks

Just to note something.
In your plunker you use a service and you try the same code with a factory. This cannot work. there are some differences between the two providers.
Either you use a factory:
function PropertiesFactory() {
// facory doens't get invoked with the new keyword
// so there is no this
var self = {};
self.name = 'Properties Factory'
self.properties = {};
self.setProperties = function (prop)
{
self.properties = prop;
}
return self;
}
or a service:
function PropertiesService() {
var sefl = this;
self.name = 'Properties Service'
self.properties = {};
self.setProperties = function (prop)
{
self.properties = prop;
}
// there is no need for return
// since th service gets invoked with the new keyword
}
form the angularjs docs about providers:
The Service recipe produces a service just like the Value or Factory
recipes, but it does so by invoking a constructor with the new
operator. The constructor can take zero or more arguments, which
represent dependencies needed by the instance of this type.

As it turn out i was hiding an important part of the code , not deliberately but from lack of understanding.
My directive is listening on DOM events and acting upon their changed data.
After reading this article (and help from my CTO...) I understood i needed to manually call $scope.$apply().
Now I have an element i can resize,drag etc. and have its changed properties eflect on the screen.
Now the binding is working again I can revert the controller code to look like this:
function Properties(PropertiesService) {
var vm = this;
vm.collection = PropertiesService.data;
}
Everything is in sync.
Sorry for the partial data , and thanks for the help.
Gilad

Related

Unit testing a scoped function used as a filter

I need to write a scoped function within my controller that acts as a filter.
$scope.filterR = function(s) {
return function(c) {
for (var prop in c) {
if (c[prop].toSomething() >= 0) {
return c;
break;
}
}
}
}
This is how I'm using it.
<div ng-repeat="c in cus | filter: filterR(s)">
</div>
The trouble I'm facing is how to test this specific routine in karma-jasmine.
My current test looks like this,
describe('check x', function () {
it('check abc', function () {
var $scope = {};
var controller = $controller('SController', {
$scope: $scope
});
expect($scope.filterResults('some_data')).toEqual(
some_data
);
});
The filter works fine but writing test for it giving me an issue. Any help is appreciated.
It is always preferable to use real scopes for testing, unless it is known that it's not:
var $scope = $rootScope.$new();
filterResults is expected to return a function. So it is
var filterResultsFn = $scope.filterResults('some_data');
expect(filterResultsFn).toEqual(jasmine.any(Function));
expect(filterResultsFn(...)).toEqual(...);

Why factory won't Work? (Angular.js)

If i pass the factory name inside my controller parameter, the function inside it seems not work, also in the view i see the render of brackets....
What i am doing wrong?
HERE WHAT I SEE:
instead, If i don't pass the service inside the controller, it seems to work
JS
var LandingApp = angular.module('LandingApp',[]);
LandingApp.factory('PreventivoTotaleFront',function(){
var voci = {};
voci.lista = [];
AggiungiVoce.add = function(voce){
voci.lista.push({
id: voci.lista.length,
costo: voce
})
};
return voci;
});
//CONTROLLER
LandingApp.controller('numberpages',function($scope,PreventivoTotaleFront){
$scope.primapagina = 150;
$scope.altrepagine = 90;
$scope.numeroaltrepagine = 0;
$scope.TotaleEuroPagine = 0;
$scope.CalcolaTotaleEuroPagine = function(){
return $scope.TotaleEuroPagine = $scope.altrepagine * $scope.numeroaltrepagine + $scope.primapagina;
AggiungiVoce.add(TotaleEuroPagine);
alert(TotaleEuroPagine);
};
});
The HTML
<body ng-app="LandingApp">
<div class="container" ng-controller="numberpages">
<form>
<label>N° Pagine interne: </label><input type="number" min="0" ng-model="numeroaltrepagine" ng-change="CalcolaTotaleEuroPagine()"></input>
<br/>{{TotaleEuroPagine | currency:""}}€<br/>
</form>
<br/><br/>
<ul>
<li ng-repeat="VociPreventivo in lista.voci">{{voci.id}} : {{voci.costo}}</li>
</ul>
</div>
</body>
You are not using factory properly. You need to return a object containing methods.
var LandingApp = angular.module('LandingApp', []);
LandingApp.factory('PreventivoTotaleFront', function () {
var voci = {};
voci.lista = [];
return {
add: function (voce) {
voci.lista.push({
id: voci.lista.length,
costo: voce
})
}
};
});
As use factory in the controller i.e. PreventivoTotaleFront.add()
//CONTROLLER
LandingApp.controller('numberpages', function ($scope, PreventivoTotaleFront) {
$scope.CalcolaTotaleEuroPagine = function () {
PreventivoTotaleFront.add(TotaleEuroPagine);
};
});
In your factory PreventivoTotaleFront, you return voci object if you put your function add inside this returning object like
voci: {
add: function() {}
}
and then you can call it from your controller like
PreventivoTotaleFront.add()
You're not creating your AggiungiVoce variable(with var) before you use it(in AggiungiVoce.add). In such a case, JavaScript looks in parent scope for the existence of that variable AggiungiVoce all the way up to global scope. There it's assigned. So that is why you're able to use it when the factory is not injected.
In the factory, you should create an object of methods and return that. Since factories are only created once, you are able to access those methods anywhere you inject the factory.

Angularjs - how to watch for changes in DOM and apply a new style to it?

I have a very simple angular app that pushes data in without refreshing the page using setInterval. Now, how can I listen or watch for new data/changes, so that if the new value/data differ from the previous one a new css style will be applied to that particular new value (for example it will change the font color to red).
My code is below:
view:
<h1>{{title}}</h1>
<ul>
<li ng-repeat="friend in friends"><strong>Name: </strong>{{friend.name}} : {{friend.username}}</li>
</ul>
data:
angular
.module ('myApp')
.factory ('Friends', ['$http', function ($http) {
return {
get: function () {
return $http.get ('users.json').then (function (response) {
return response.data;
});
}
};
}]);
Controller:
angular
.module ('myApp')
.controller ('summaryCtrl', ['$scope', 'Friends', function ($scope, Friends) {
$scope.title = "Friends";
$scope.loadData = function () {
Friends.get ().then (function (data) {
$scope.friends = data;
});
};
//initial load
$scope.loadData();
var timer = setInterval(function(){
$scope.loadData();
},5000);
}]);
many thanks
Use $interval instead of setInterval, since it triggers a digest loop it will update your data automatically
angular
.module ('myApp')
.controller ('summaryCtrl', ['$scope', 'Friends', '$interval' function ($scope, Friends, $interval) {
$scope.title = "Friends";
$scope.loadData = function () {
Friends.get ().then (function (data) {
$scope.friends = data;
});
};
//initial load
$scope.loadData();
var timer = $interval(function(){
$scope.loadData();
},5000);
}]);
My recommendation would be to manually compare each friend item and assign a changeFlag whenever the data has changed.
To start, keep a reference to the old data and whenever new data comes in, compare the two, like this:
var oldData = undefined; // Somewhere in initialization.
...
Friends.get().then(function (response) {
var newData = response;
if (oldData && JSON.stringify(oldData) != JSON.stringify(newData))
{
$scope.friends = newData;
$scope.$apply(); // Force the entire page to be redrawn. You can do style bindings to change a style.
}
oldData = response;
}
This will get you half-way to your goal. You will only be refreshing the page whenever something has changed, but there is no indication as to which friend has changed. I imagine this is what you are attempting to accomplish. You want to highlight those friends that have changed.
To do this we could simply create a comparison function that applies a flag to each object that has changed. However, this code assumes that some property on each friend remains fixed. This is normally why an id property is given to each item in a database. I'm going to assume you have an id property for each friend that never changes regardless if their name, age, email, etc. does.
var changeFlagFriendsObjects = function(oldData, newData) {
var idToOldDataMap = {};
oldData.forEach(function (friend) {
idToOldDataMap[friend.id] = friend;
});
newData.forEach(function (friend) {
var oldFriendData = idToOldDataMap[friend.id];
friend.changeFlag = JSON.stringify(oldFriendData) != JSON.stringify(friend);
});
};
// You would call changeFlagFriendsObjects in the other example above. I'm sure this would be easy to figure out how to place.
Regarding binding styles in the HTML to properties, see here.
An example would be like the following:
<!-- Apply the 'highlight' style when changeFlag is true -->
<li ng-repeat="friend in friends" ng-style="highlight={changeFlag: true}"><strong>Name: </strong>{{friend.name}} : {{friend.username}}</li>

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

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