How do I maintain scope when delegating to service? - angularjs

I've built a small service to handle errorMessages in my application. Its publicly available on the rootScope and is able to add new messages to my page as needed. Now the need to have clickable links in the messages have arisen.
Questions:
How do I dynamically add JavaScript that is handled by angular to the messages that are created?
I've added onclicks that work, but ng-click seem to not be handled.
The Js that I would like to run is in the controller that created the message in the first place. How do I make sure that I end up in the correct scope when clicking a link in an error message?
If the function adding a message is another service, how do I solve that?
And the service I'm playing around with:
var myApp = angular.module('myApp', []);
function errorHandlingFactory() {
this.messages = [];
this.addMessage = function (messageText, type) {
var message = this.messages.push({messageText: messageText, type: type, closeable: false});
};
this.getHtmlContent = function(messageId) {
return this.messages[messageId].messageText;
}
this.removeMessage = function (messageId) {
this.messages.splice(messageId, 1);
};
this.clearMessages = function() {
this.messages = [];
};
}
myApp.service('errorHandling', function () {
return new errorHandlingFactory();
});
myApp.run(function($rootScope, errorHandling) {
// Attach global error handling object to our rootScope
$rootScope.errorFactory = errorHandling;
});
// Usage from controller
$rootScope.errorFactory.addMessage('The message to be added', 'warning');
To make it a bit easier to understand, I've created a jsfiddle to look at.
http://jsfiddle.net/kxsmL25h/7/
What I would like to do is when the link in message is clicked, the function desiredCallback is run on the GenericTestController.

Related

Getting a value sent by SignalR with AngularJS

I'm trying to write a very simple message on the screen that when signalr send a value this message gets updated.
I have a very simple Hub:
public class Chat : Hub
{
public Task Send(string message)
{
return Clients.All.InvokeAsync("Send", message);
}
}
On the front-end I have the following html:
<div ng-app="myApp" ng-controller="myCtrl">
<p ng-bind="value"></p>
</div>
And the script is:
var app = angular.module('myApp', []);
app.controller('myCtrl', function ($scope) {
$scope.value = "My Test Value";
});
chatConnection.on('Send', (message) => {
app.scope.value = message;
});
What I am failing to understand is how do I access that value parameter so that I may update it.
EDIT:
The SignalR part works, the message comes through. The problem is that I am not sure how to update the value that is inside of that controller. app.scope.value = message;
Use observer pattern. Basically create a service which should do the following things:
Creates hub connection (handles start, reconnection, ....)
Support for example the following methods:
register for event (adds the caller to a list which contains
interested members.)
unregister for event (removes the caller from the list with the interesed members.)
In the case a new message from server inform all observers
Example:
(Pseudocode extracted from my solution (to big to show all)):
Server (here called hubWrapperService):
var yourHubProxy = $.connection.yourHub;
yourHubProxy.client.yourMethodWhichServerCanCall= function (params){
// Inform observers. Iterate to over all your observers and inform them
notifyObservers(params);
};
// Start connection.
$.connection.hub.start().done(function () {
...
});
}).fail(function (error) {...
});
function registerToEvent(callback) {
$log.debug("ServiceHub: getConnectionEvent called");
// Add caller to you observer list. (here serviceInitObservers)
}
function notifyServiceInitObservers() {
angular.forEach(serviceInitObservers, function (callback) {
callback.callback();
});
}
In your Controller (inject service and register for events):
hubWrapperServer.registerToEvent(function () {
serviceHub.getAllDevices().then(function (params) { // Do something in your controller
});
});
There is also a wrapper service available
https://github.com/JustMaier/angular-signalr-hub
poker.client.showAllCards = function (show) {
$scope.allCardsShowing = show;
$scope.$apply();
};
$scope.$apply(), refresh the Angularjs context, and this working for me.
I get this form article: Consensus: SignalR + AngularJS

AngularJS - Not able to get value from factory service

I am very much new in AngularJS and due to which I am facing an issue and not able to understand the exact problem. The code which I tried is worked in normal javascript code but now I want to use custom service (factory function). Actually, I have a textarea where user can input their text and then they can do the formating by selecting any of the text. (discovertheweb.in/woweditor - this is existing site which I have created now I want to apply angular in this code). Now, I have created a custom service to check whether the user select any content or not. I have used the below code in my custom services and try to get the selection start, end point so that I can get the selected text from the textarea field. But, the problem is that I am getting 0 value for both selection start and end point. When i used the same code inside directive it works but try to get the value through service it is showing 0 for both. Please find the below code and let me know the code which I missed out here.
Custom Service:
(function(){
"use strict";
var wsApp = angular.module("WorkApp");
wsApp.factory("InputCheckService", function(){
var defaultText = document.getElementById("input-text-area");
var selStart = defaultText.selectionStart;
var selEnd = defaultText.selectionEnd;
var selectedText;
if(selStart != selEnd){
selectedText = defaultText.value.substring(selStart, selEnd);
}else{
selectedText = null;
}
return {
selStart: selStart,
defaultText: defaultText,
selEnd: selEnd,
selectedText: selectedText
};
});
}());
The directive where I called this services. I already included the service inside the main controller in different file.
(function(){
"use strict";
var wsApp = angular.module("WorkApp");
wsApp.directive("generalDirective", generalDirective);
function generalDirective(){
return {
scope: true,
controller:function($scope, InputCheckService){
$scope.collapsed = false;
$scope.CollpasePanel = function(){
$scope.collapsed = !$scope.collapsed;
};
$scope.updatePara = function(){
alert(InputCheckService.defaultText+"Selection start: "+InputCheckService.selStart+" Selection End: "+ InputCheckService);
/**
* defaultText: defaultText,
selStart: selStart,
selEnd: selEnd,
selectedText: selectedText
*/
}
},
templateUrl: 'directive/general-directive.html'
};
}
}());
If you need any more details, please let me know.
Thanks in advance for your time and suggestion.
Regards,
Robin
You should not use service to manipulate DOM element. You should manipulate DOM only at directives. Your problem is you DONT have anywhere to listen to TEXTAREA SELECTION EVENT and your service will not update the data inside. I have created a fiddle for your problem. The watchSelection directive is based on this answer from stackoverflow. Something you should notice :
I use service only to store data. Something like selStart, selEnd or paragraphContent and provide some API to retrieve the data
.factory("InputCheckService", function () {
return {
setSelStart: function (start) {
selStart = start;
},
.....
},
});
On the watchSelection directive, you watch for the mouseup event and will perform update the service so that it will store value you need and later you can retrieve it from other directives or controllers.
elem.on('mouseup', function () {
var start = elem[0].selectionStart;
//store it to the service
InputCheckService.setSelStart(start);
});
In your generalDirective directive you can get value from your service and angular will auto update the view for you.
Hope it helps.

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.

Fire event from a service without "polluting" the $rootScope

I'm building an app in angularjs, where I have a central notification queue. Any controller can push into the queue and digest the messages.
I have built a service like:
angular.module('app').factory('notificationSvc', ['translateSvc', notification]);
function notification(translate) {
var notificationQ = [];
var service = {
add: add,
getAll: getAll
};
return service;
function add(message, type) {
notificationQ.push({
message: message,
type: type
});
}
function getAll() {
return notificationQ;
}
}
(One of the problems with this is that the notificationQ can be modified unsafely by calling svc.getAll()[3].message = "I have changed a message"; or something similar. I originally wanted a "push only" service with immutable messages, but this problem is outside of the scope of this question.)
If I digest this queue in a controller like:
$scope.notifications = svc.getAll();
$scope.current= 0; // currently visible in the panel
And use it like:
<div ng-repeat="notification in notifications" ng-show="$index == current">
<p>{{notification.message}}</p>
</div>
I can bind to it, see it changing and all is well. I can cycle through past notifications by changing the variable current.
The question:
When the queue gets a new element I want the $scope.index variable to change to notifications.length - 1. How do I do that?
I have seen examples using $rootScope.$broadcast('notificationsChanged'); and $scope.$on('notificationsChanged', function() { $scope.index = $scope.notifications.length - 1; });, but I did not really like the pattern.
I have a controller that knows about the service, has a direct reference to it, and yet we use $rootScope to communicate? Everything else sees the $rootScope, and all the events from different services will clutter up there.
Can't I just put the event on the service instead? Something like this.$broadcast('notificationsChanged') in the service and svc.$on('notificationsChanged', function() { ... }); in the controller.
Or would it be cleaner to watch the data directly? If yes, how? I don't like this as I was not planning on exposing the full array directly (I was planning on get(index) methods) it just sort of happened along the lines where I had no idea what I was doing and was happy that at least something works.
You could just manage events yourself. For example (untested):
function EventManager() {
var subscribers = [];
var service = {
subscribe: subscribe;
unsubscribe: unsubscribe;
publish: publish
}
return service;
function subscribe(f) {
subscribers.push(f);
return function() { unsubscribe(f); };
}
function unsubscribe(f) {
var index = subscribers.indexOf(f);
if (index > -1)
subscribers.splice(index, 1);
}
function publish(e) {
for (var i = 0; i < subscribers.length; i++) {
subscribers[i](e);
}
}
}
function notification(translate) {
var notificationQ = [];
var addEvent = new EventManager();
var service = {
add: add,
getAll: getAll,
onAdded: addEvent.subscribe;
};
return service;
function add(message, type) {
var notification = {
message: message,
type: type
};
notificationQ.push(notification);
addEvent.publish(notification);
}
function getAll() {
return notificationQ;
}
}
Then, from your controller:
...
var unsubscribe = notificationSvc.onAdded(function(n) { /* update */ });
Caveat: using this method the service will maintain a reference to the subscriber function that is passed to it using subscribe, so you have to manage the subscription using $scope.$on('$destroy', unsubscribe)
The notification approach would definitely work. Depending on your implementation it would be the right solution.
Another approach would be to watch the notifications array in your controller, like this:
$scope.$watchCollection('notifications', function(newValue, oldValue) {
$scope.index = newValue.length - 1;
});
This should work, because your controller receives a direct reference to the notifications array and therefore can watch it directly for changes.
As runTarm pointed out in the comments, you could also directly $watch the length of the array. If you're only interested in length changes this would be a more memory saving approach (since you don't need to watch the whole collection):
$scope.$watch('notifications.length', function (newLength) {
$scope.index = newLength - 1;
});

AngularJS broadcast from one service not triggering a call to second service

I have defined two AngularJS services ... one is for the YouTube Player API, and other for the YouTube iFrame Data API. They look like this:
angular.module('myApp.services',[]).run(function() {
var tag = document.createElement('script');
tag.src = "//www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
})
.factory('YtPlayerApi', ['$window', '$rootScope', function ($window, $rootScope) {
var ytplayer = {"playerId":null,
"playerObj":null,
"videoId":null,
"height":390,
"width":640};
$window.onYouTubeIframeAPIReady = function () {
$rootScope.$broadcast('loadedApi');
};
ytplayer.setPlayerId = function(elemId) {
this.playerId=elemId;
};
ytplayer.loadPlayer = function () {
this.playerObj = new YT.Player(this.playerId, {
height: this.height,
width: this.width,
videoId: this.videoId
});
};
return ytplayer;
}])
.factory('YtDataApi', ['appConfig','$http', function(cfg,$http){
var _params = {
key: cfg.youtubeKey
};
var api="https://www.googleapis.com/youtube/v3/";
var yt_resource = {"api":api};
yt_resource.search = function(query, parameters) {
var config = {
params: angular.extend(angular.copy(_params),
{maxResults: 10,
part: "snippet"}, parameters)
};
return $http.get(api + "search?q=" + query, config);
};
return yt_resource;
}]);
(also note that the 'setPlayerId' function of my player service is called by a custom directive ... but that's not important for my question).
So, here's the issue. I need to ensure that the Player API code is loaded before I set the video id and create the player, which is why I have it broadcasting the 'loadedApi' message. And this works great, if I then in my controller pass a hard-coded video id, like this:
function ReceiverCtrl($scope,$rootScope,$routeParams,ytplayer,ytdataapi) {
$scope.$on('loadedApi',function () {
ytplayer.videoId='voNEBqRZmBc';
ytplayer.loadPlayer();
});
}
However, my video IDs won't be determined until I make an API call with the data api service, so I ALSO have to ensure that the results of that call have come back. And that's where I'm running into problems ... if I do something like this:
$scope.$on('loadedApi',function () {
ytdataapi.search("Mad Men", {'topicId':$routeParams.topicId,
'type':'video',
'order':'viewCount'})
.success(function(apiresults) { // <-- this never gets triggered
console.log(apiresults); // <-- likewise, this obviously doesn't either
});
});
Then the interaction with the data service never happens for some reason. I know the data service works just fine, for when I un-nest it from the $on statement, it returns the api results. But sometimes latency makes it so that the results don't come back fast enough to use them in the player service. Any thoughts on what I can do to make the data search after receiving the message that the player API is ready, but still keep the two services as two separate services (because other controllers only use one or the other, so I don't want them dependent on each other at the service level)?
Figured it out; I had to call $scope.$apply(), like this:
function ReceiverCtrl($scope,$rootScope,$routeParams,ytplayer,ytdataapi) {
$scope.$on('loadedApi',function () {
ytdataapi.search("",{'topicId':$routeParams.topicId,'type':'video','maxResults':1,'order':'viewCount'}).success(function(apiresults) {
ytplayer.videoId=apiresults.items[0].id.videoId;
ytplayer.loadPlayer();
});
$scope.$apply();
});
}
Is there anyone who could shed light on why this works, though? $scope.$digest() also works ... but I thought those methods were only used when you need to update bindings because of some javascript code that Angular isn't aware of. Is the nesting I've got here doing that (I wouldn't think it should, as my ytdataapi service is using $http)?

Resources