How can i get this function:
$rootScope.$watch(function () {
return $mdMedia('md');
}, function watchCallback(newValue, oldValue) {
$scope.detectScreen = $mdMedia('max-width: 1280px');
})
which at the moment sits in my controller into a service/factory, so i can use it in multiple controllers.
.factory('MyFactory', [
'$rootScope',
'$mdMedia',
function ($rootScope, $mdMedia) {
return {
detectScreen: function (param) {
***///get the function to work from here///***
}
}
}
])
Put the function in the factory without a watch:
app.factory('MyFactory', ['$mdMedia',
function ($mdMedia) {
return {
detectScreen: function () {
return $mdMedia('max-width: 1280px');
};
}
}
]);
In the controllers, use the $doCheck Life-Cycle Hook to keep the scope variable up to date:
app.controller("myController", function(myFactory, $scope) {
this.$doCheck = function () {
$scope.detectScreen = myFactory.detectScreen();
};
});
I solved the issue my self. To whom it may concerns, here is the answer.
put this in your controller:
$scope.$watch(function () {
return CmsFactory.detectScreen();
}, function watchCallback(small) {
$scope.detectScreen = small;
})
And this in your factory:
return {
detectScreen: function () {
return $mdMedia('md');
}
}
Alternatively, you could move the $watch to the app.run() and emit an event when it changes that can be caught by your controllers.
I ran across this answer when I was doing some research. My reputation isn't high enough to post a comment, but to answer jhon dano's question to ookadoo, I think he means this:
if you inject $mdMedia into your controller, you can use a watch to update a scope variable when the screen size changes.
The watch would look like this:
$scope.ScreenIsXs = false;
$scope.$watch(function () {
return $mdMedia('xs');
}, function watchCallback(response) {
//console.log(response); //test code
$scope.ScreenIsXs = response;
});
Then you could use the $scope variable to drive an ng-if in your html:
<div ng-if="ScreenIsXs">I only show when your window is very small! </div>
This would be useful for simple applications that wouldn't required a factory/service. That said, your question directly stated that you needed a service/factory, so I'm not sure how useful the response would have been prior to you finding the solution.
Anyway, I hope this clarifies ookadoo's response for anyone else who stumbles across these answers in the future.
Related
This question already has answers here:
Caching a promise object in AngularJS service
(3 answers)
Closed 4 years ago.
I'm trying to wrap by head around when to use $q in AngularJS. I've been using it in all my services so far so when I call our Web API it works nicely. What I'm trying to do is cut down on all the calls to the API since I need the same data in multiple places and I've just been pinging the API every time I needed the data.
Right now I have a service, which gets the data and a helper file to help with related things about the data.
What I want is to use this helper factory to hold the data that's needed for every body which uses it.
I'm having trouble wrapping my head around assigning the value of data from the helper file if the data hasn't gotten back to me yet when AngularJS runs.
This is what I have so far.
(function() {
var app = angular.module("Test", []);
app.service("githubService", function($http, $q) {
var deferred = $q.defer();
this.getAccount = function() {
return $http.get('https://api.github.com/users/JonDoe');
};
});
app.factory("githubHelper", ["githubService", function(githubService) {
_gitHubInfo = {};
githubService.getAccount().then(function(data) {
_gitHubInfo = data;
});
return {
gitHubInfo: _gitHubInfo
};
}]);
app.controller("Dummy", ["$scope", "githubHelper", "githubService", function($scope, githubHelper, githubService) {
// How do I make it work this way?
$scope.value = githubHelper.gitHubInfo;
// This is what I'm using now
githubService.getAccount().then(function(data) {
$scope.value2 = data;
});
}]);
})();
.box {
border: 1px red solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div ng-app="Test">
<div ng-controller="Dummy">
<div class="box">
{{value}}
</div>
<br />
<div class="box">
{{value2}}
</div>
</div>
</div>
What I want to do is just remove the githubService dependency from the Dummy controller and only have it present in the githubHelper factory so I can remove the dependency on githubService from all my controllers and instead use gitHubHelper.
What do I need to change in Dummy controller to have $scope.value be the data returned from githubService.getAccount()?
I have something like this in my project:
app.factory("githubHelper", ["githubService", function(githubService) {
var promise = null;
function getInfo() {
if (!promise) {
promise = githubService.getAccount();
}
return promise;
}
return {
getInfo: getInfo
};
}]);
githubHelper.getInfo().then(function(data) {})
githubHelper.getInfo().then(function(data) {})
githubHelper.getInfo().then(function(data) {})
...
You're almost there. The problem is that githubinfo doesn't get populated until after you access it. What you should do is almost exactly (see my comment above) what you're doing githubservice.getaccount, but in githubhelper.getaccount, instead. Set githubinfo to a $q.deferred, return githubinfo.promise from getaccount, and resolve the promise in the then
UPDATE: NOW WITH MORE CODE! (now that I'm not on mobile :-D)
(function() {
var app = angular.module("Test", []);
app.service("githubService", function($http, $q) {
this.getAccount = function() {
return $http.get('https://api.github.com/users/JonDoe');
};
});
app.factory("githubHelper", ["githubService", function(githubService) {
return {
gitHubInfo: $q(function(resolve, reject) {
githubService.getAccount().then(function(data) {
resolve(data);
}, reject);
}
};
}]);
app.controller("Dummy", ["$scope", "githubHelper",
function($scope, githubHelper) {
githubHelper.gitHubInfo.then(function(data) {
$scope.value = data;
});
}]);
})();
Now, written as-is I would never approve that as a PR for many reasons (code clarity re: return { someProp: function() { /* a bunch of code */} } .... usage of $scope should be avoided at all costs... and as mentioned, the $cacheFactory can and should handle this) , but you should be able to get the general gist of how something along these lines could work
For custom server side logging I'm wrapping angulars $exceptionHandler like this as descibed at many places including stackoverflow and the angular docs (there are variations but they're basically doing the same thing):
loggingModule.config(function ($provide) {
$provide.decorator('$exceptionHandler', function ($delegate, ExceptionLoggingService) {
return function () {
// Call our own ExceptionLoggingService
ExceptionLoggingService.apply(null, arguments);
// Call the original
$delegate.apply(null, arguments);
};
});
});
This however leads to the $exceptionHandler not throwing any errors during karma/jasmine unit tests.
This can be explemified by using this test taken from the angular docs, which runs fine when the module is not decorated, but which all fail, when it is:
beforeEach(module('logging'));
describe('$exceptionHandlerProvider', function () {
// testing standard behaviour of $exceptionHandler
// see https://docs.angularjs.org/api/ngMock/service/$exceptionHandler
it('should capture log messages and exceptions', function () {
module(function ($exceptionHandlerProvider) {
$exceptionHandlerProvider.mode('log');
});
inject(function ($log, $exceptionHandler, $timeout) {
$timeout(function () {
$log.log(1);
});
$timeout(function () {
$log.log(2);
throw 'banana peel';
});
$timeout(function () {
$log.log(3);
});
expect($exceptionHandler.errors).toEqual([]);
expect($log.assertEmpty());
$timeout.flush();
expect($exceptionHandler.errors).toEqual(['banana peel']);
expect($log.log.logs).toEqual([[1], [2], [3]]);
});
});
});
Any idea how to fix this behaviour? Also check out this plunkr.
I'm using angular#1.3.14
The $exceptionHandler function from ngMock has an error property that points to an array.
The function that your decorator returns does not have that property:
return function () {
ExceptionLoggingService.apply(null, arguments);
$delegate.apply(null, arguments);
};
So this will for example fail:
expect($exceptionHandler.errors).toEqual([]);
Here is an implementation that hopefully should work:
app.config(function($provide) {
$provide.decorator('$exceptionHandler', ['$delegate', 'ExceptionLoggingService',
function($delegate, ExceptionLoggingService) {
var decoratedExceptionHandler = function() {
ExceptionLoggingService.apply(this, arguments);
return $delegate.apply(this, arguments);
};
for (var key in $delegate) {
if (!$delegate.hasOwnProperty(key)) continue;
decoratedExceptionHandler[key] = $delegate[key];
}
return decoratedExceptionHandler;
}
]);
});
As a note, you should always make sure to do this when decorating. The real implementation of $exceptionHandler might not have any properties right now, but you never know if it will have in the future. The $templateRequest service is one example of where you must do this, as it has properties used internally to make view animations work.
I am writing my first AngularJS app and I'm trying to get a directive to update its view when an array it received from the service changed.
My directive looks like this:
angular.module('Aristotle').directive('ariNotificationCenter', function (Notifications) {
return {
replace: true,
restrict: 'E',
templateUrl: 'partials/ariNotificationCenter.html',
controller: function ($scope) {
$scope.notifications = Notifications.getNotifications();
$scope.countUnread = function () {
return Notifications.countUnread();
};
}
};
});
The partial is quite simply:
<p>Unread count: {{countUnread()}}</p>
While my Notifications service looks like this:
function Notification (text, link) {
this.text = text;
this.link = link;
this.read = false;
}
var Notifications = {
_notifications: [],
getNotifications: function () {
return this._notifications;
},
countUnread: function () {
var unreadCount = 0;
$.each(this._notifications, function (i, notification) {
!notification.read && ++unreadCount;
});
return unreadCount;
},
addNotification: function (notification) {
this._notifications.push(notification);
}
};
// Simulate notifications being periodically added
setInterval(function () {
Notifications.addNotification(new Notification(
'Something happened!',
'/#/somewhere',
Math.random() > 0.5
));
}, 2000);
angular.module('Aristotle').factory('Notifications', function () {
return Notifications;
});
The getNotifications function returns a reference to the array, which gets changed by the setInterval setup when addNotification is called. However, the only way to get the view to update is to run $scope.$apply(), which stinks because that removes all the automagical aspect of Angular.
What am I doing wrong?
Thanks.
I believe the only problem with you code is that you are using setInterval to update the model data, instead of Angular built-in service $interval. Replace the call to setInterval with
$interval(function () {
Notifications.addNotification(new Notification(
'Something happened!',
'/#/somewhere',
Math.random() > 0.5
));
}, 2000);
And it should work without you calling $scope.$apply. Also remember to inject the $interval service in your factory implementation Notifications.
angular.module('Aristotle').factory('Notifications', function ($interval) {
$interval internally calls $scope.$apply.
I'm not an expert at Angular yet, but it looks like your problem may be in the partial.
<p>Unread count: {{countUnread()}}</p>
I don't think you can bind to a function's results. If this works, I believe it will only calculate the value once, and then it's finished, which appears to be the issue you are writing about.
Instead, I believe you should make a variable by the same name:
$scope.countUnread = 0;
And then update the value in the controller with the function.
Then, in your partial, remove the parentheses.
<p>Unread count: {{countUnread}}</p>
As long as $scope.countUnread is indeed updated in the controller, the changes should be reflected in the partial.
And as a side note, if you take this approach, I'd recommend renaming either the variable or the function, as that may cause issues, or confusion at the very least.
I'm using a framework called Radiant UI, which is a way to get HTML5 UI into Unreal Engine 4. I'm trying to pick up some modern Javascript while I do that, so I'm building the UI in AngularJS.
My understanding of Angular is still pretty weak though, and I'm a bit confused about what the best practice is here. The extension injects the following Javascript when it sets up.
var RadiantUI;
if (!RadiantUI)
RadiantUI = {};
(function() {
RadiantUI.TriggerEvent = function() {
native function TriggerEvent();
return TriggerEvent(Array.prototype.slice.call(arguments));
};
RadiantUI.SetCallback = function(name, callback) {
native function SetHook();
return SetHook(name, callback);
};
RadiantUI.RemoveCallback = function(name) {
native function RemoveHook();
return RemoveHook(name);
};
})();;
So this is simply pushing RadiantUI into the global namespace. That would be fine if the extension was always there, but it isn't. In the test environment (Chrome), it's not there. It's only there when running in the game engine. That, combined with the fact that globals suck, means I want to encapsulate it.
In the previous iteration of this, I had it wrapped in an AMD module, and it worked well. Like this:
define([], function()
{
if ("RadiantUI" in window)
{
console.log("RadiantUI in global scope already!");
return window.RadiantUI;
}
var RadiantUI;
if (!RadiantUI) {
RadiantUI = {};
RadiantUI.TriggerEvent = function() {}
RadiantUI.SetCallback = function() {}
RadiantUI.RemoveCallback = function() {}
}
console.log("Using fake RadiantUI bindings");
return RadiantUI;
});
So here's what I want to do:
I want to include radiant as a dependency to my app/stateProvider and have it injected, much the same way it would be in AMD. With the stub methods in place if the extension isn't present. What's the proper approach to this? A module? A service provider?
UPDATE: This is the working code using the answer given.
var myapp = angular.module('bsgcProtoApp', ['ui.router' ]);
myapp.value('radiant', window.RadiantUI || {
TriggerEvent: function()
{
console.log("TriggerEvent called");
},
SetCallback: function(name, callback)
{
console.log("Setcallback called");
},
RemoveCallback: function(name)
{
console.log("RemoveCallback called");
}
});
myapp.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider )
{
$urlRouterProvider.otherwise("/mainmenu");
$stateProvider.state('mainmenu',
{
name: "mainmenu",
url: "/mainmenu",
templateUrl: 'templates/mainmenu.html',
controller: ['$scope', 'radiant', function($scope, radiant)
{
$scope.tester = function()
{
radiant.TriggerEvent("DuderDude");
console.log("Duder!");
}
}],
});
}]);
You presumably have an Angular module or app. For the sake of this answer, let's call it MyApp.
Now you can do
MyApp.value("RadiantUI", window.RadiantUI || {
TriggerEvent = function(){},
//... more properties
});
Now to access this value as a dependency in a controller for example, you'd do this
MyApp.controller(["$scope", "RadiantUI", function($scope, RadiantUI){
// ... controller code ...
}]);
So I have this directive called say, mySave, it's pretty much just this
app.directive('mySave', function($http) {
return function(scope, element, attrs) {
element.bind("click", function() {
$http.post('/save', scope.data).success(returnedData) {
// callback defined on my utils service here
// user defined callback here, from my-save-callback perhaps?
}
});
}
});
the element itself looks like this
<button my-save my-save-callback="callbackFunctionInController()">save</button>
callbackFunctionInController is for now just
$scope.callbackFunctionInController = function() {
alert("callback");
}
when I console.log() attrs.mySaveCallback inside my-save directive, it just gives me a string callbackFunctionInController(), I read somewhere that I should $parse this and it would be fine, so I tried to $parse(attrs.mySaveCallback) which gave me back some function, but hardly the one I was looking for, it gave me back
function (a,b){return m(a,b)}
What am I doing wrong? Is this approach flawed from the beginning?
So what seems like the best way is using the isolated scope as suggested by ProLoser
app.directive('mySave', function($http) {
return {
scope: {
callback: '&mySaveCallback'
}
link: function(scope, element, attrs) {
element.on("click", function() {
$http.post('/save', scope.$parent.data).success(returnedData) {
// callback defined on my utils service here
scope.callback(); // fires alert
}
});
}
}
});
For passing parameters back to controller do this
[11:28] <revolunet> you have to send named parameters
[11:28] <revolunet> eg my-attr="callback(a, b)"
[11:29] <revolunet> in the directive: scope.callback({a:xxx, b:yyy})
There are a lot of ways to go about what you're doing. The FIRST thing you should know is that the $http.post() is going to be called as soon as that DOM element is rendered out by the template engine, and that's it. If you put it inside a repeat, the call will be done for each new item in the repeater, so my guess is this is definitely not what you want. And if it is then you really aren't designing things correctly because the existence of DOM alone should not dictate queries to the backend.
Anyway, directly answering your question; if you read the albeit crappy docs on $parse, it returns you an evaluation expression. When you execute this function by passing the scope to evaluate on, the current state of that expression on the scope you passed will be returned, this means your function will be executed.
var expression = $parse(attrs.mySave);
results = expression($scope); // call on demand when needed
expression.assign($scope, 'newValu'); // the major reason to leverage $parse, setting vals
Yes, it's a little confusing at first, but you must understand that a $scope changes constantly in asynchronous apps and it's all about WHEN you want the value determined, not just how. $parse is more useful for a reference to a model that you want to be able to assign a value to, not just read from.
Of course, you may want to read up on creating an isolate scope or on how to $eval() an expression.
$scope.$eval(attrs.mySave);
You can use .$eval to execute a statement in the given scope
app.directive('mySave', function($http) {
return function(scope, element, attrs) {
$http.post('/save', scope.data).success(returnedData) {
// callback defined on my utils service here
// user defined callback here, from my-save-callback perhaps?
scope.$eval(attrs.mySaveCallback)
}
}
});
TD: Demo
If you want to share data between a directive and a controller you can use the two way binding
app.controller('AppController', function ($scope) {
$scope.callbackFunctionInController = function() {
console.log('do something')
};
$scope.$watch('somedata', function(data) {
console.log('controller', data);
}, true);
});
app.directive('mySave', function($http, $parse) {
return {
scope: {
data: '=mySaveData',
callback: '&mySaveCallback' //the callback
},
link: function(scope, element, attrs) {
$http.get('data.json').success(function(data) {
console.log('data', data);
scope.data = data;
scope.callback(); //calling callback, this may not be required
});
}
};
});
Demo: Fiddle
scope: {
callback: '&mySaveCallback'
}
Setting the scope explicitly could be a good solution but if you want the reach other parts of the original scope you can't because you have just overwritten it. For some reason, I needed to reach other parts of the scope too so I used the same implementation as ng-click do.
The use of my directive in HTML:
<div my-data-table my-source="dataSource" refresh="refresh(data)">
Inside the directive (without setting the scope explicitly):
var refreshHandler = $parse(attrs.refresh);
scope.$apply(function () {
refreshHandler( {data : conditions}, scope, { $event: event });
});
With this I can call the function in controller and pass parameters to it.
In the controller:
$scope.refresh= function(data){
console.log(data);
}
And it prints the conditions correctly out.
This worked for me
Inside the view script
<tag mycallbackattrib="scopemethod">
Inside the directive
$scope[attrs.mycallbackattrib](params....);
It is correctly called and params are passed, but maybe is not a best 'angular way' to work.
You should be using ng-click instead of creating your own directive.
app.directive('mySave', function($http, $parse) {
return {
scope: {
data: '=mySaveData',
callback: '&' //the callback
},
link: function(scope, element, attrs) {
$http.get('data.json').success(function(data) {
console.log('data', data);
if (scope.callback()) scope.callback().apply(data);
});
}
};
});