I have setup an RXJS observable. I have two components which subscribe to a subject in service factory. How do I unsubscribe a selected component to the subject so that a button is pressed it stops listening to the subject broadcast?
See my jsfiddle Unsubscribe App
My code:
<div ng-app="myApp" ng-controller="mainCtrl">
<script type="text/ng-template" id="/boxa">
BoxA - Message Listener: </br>
<strong>{{boxA.msg}}</strong></br>
<md-button ng-click='boxA.unsubcribe()' class='md-warn'>Unsubscribe A</md-button>
</script>
<script type="text/ng-template" id="/boxb">
BoxB - Message Listener: </br>
<strong>{{boxB.msg}}</strong></br>
<md-button ng-click='boxB.unsubcribe()' class='md-warn'>Unsubscribe B</md-button>
</script>
<md-content class='md-padding'>
<h3>
{{name}}
</h3>
<label>Enter Text To Broadcast</label>
<input ng-model='msg'/></br>
<md-button class='md-primary' ng-click='broadcastFn()'>Broadcast</md-button></br>
<h4>
Components
</h4>
<box-a></box-a></br>
<box-b></box-b>
</md-content>
</div><!--end app-->
var app = angular.module('myApp', ['ngMaterial']);
app.controller('mainCtrl', function($scope,msgService) {
$scope.name = "Observer App Example";
$scope.msg = 'Message';
$scope.broadcastFn = function(){
msgService.broadcast($scope.msg);
}
});
app.component("boxA", {
bindings: {},
controller: function(msgService) {
var boxA = this;
boxA.msgService = msgService;
boxA.msg = '';
boxA.msgService.subscribe(function(obj) {
console.log('Listerner A');
boxA.msg = obj;
});
boxA.unsubscribe=function(){
};
},
controllerAs: 'boxA',
templateUrl: "/boxa"
})
app.component("boxB", {
bindings: {},
controller: function(msgService) {
var boxB = this;
boxB.msgService = msgService;
boxB.msg = '';
boxB.msgService.subscribe(function(obj) {
console.log('Listerner B');
boxB.msg = obj;
});
boxB.unsubscribe=function(){
};
},
controllerAs: 'boxB',
templateUrl: "/boxb"
})
app.factory('msgService', ['$http', function($http){
var msgSubject = new Rx.ReplaySubject();
return{
subscribe:function(subscription){
msgSubject.subscribe(subscription);
},
broadcast:function(msg){
console.log('success');
msgSubject.onNext(msg);
}
}
}])
As per my comment above the new RxJs 5 Beta now changed from subscription.dispose() to subscription.unsubscribe() Please refer to here https://github.com/ReactiveX/rxjs/blob/master/MIGRATION.md#subscription-dispose-is-now-unsubscribe
Please see updated fiddle: here
the subscribe function returns a Disposable to work with and you must first return the subscription from your factory (line 60):
subscribe: function(subscription){
return msgSubject.subscribe(subscription);
}
This will let you store your subscription in each controller to work with in the future. (line 21 & 42)
var boxASubscription = boxA.msgService.subscribe(function(obj) {
console.log('Listerner A');
boxA.msg = obj;
});
You can then call the dispose method on the subscription when you want to unsubscribe:
boxA.unsubscribe = function(){
console.log('Unsubscribe A');
boxASubscription.dispose();
};
n.b.
For some reason I couldn't get your demo to work with <md-button> so I changed this to <button> for the sake of the demo.
Related
I have a website which uses angular.js. The ng-click is working fine on laptop/desktop but not on mobile devices. From my research, I learned that i need to use ngTouch and I undertand that. My problem is that I am not a programmer and does not know how to do it.
I am hoping that there is someone who can help me or provide me with the right step or code. this is my code:
<div class="container" ng-controller="MessageBoardCtrl">
<div class="span6">
<div class="row-fluid item" ng-repeat="item in items" ui-animate>
<div class="span2"><img src="../images/post.png" width="48px" height="48px"/></div>
<div class=" well well-small">
<p>{{item.message}}</p>
</div>
</div>
</div>
<div class="span6">
<div class='well'>
<button class="btn btn-primary" ng-click="sendMessage()">Share</button>
Here is the javascript:
<script src="../templates/js/jquery.js"></script>
<script src="../templates/js/angular.js"></script>
<script src="../templates/js/angular-ui.js"></script>
<script src="../templates/js/angular-touch.js"></script>
<script src="../templates/js/angular-touch.min.js"></script>
<script>
function MessageBoardCtrl($scope, $http, $timeout) {
$scope.items = [];
$scope.message = '';
$scope.email = '';
$scope.lastTime = 0;
$scope.refreshMessages = function() {
$http.get('../templates/faucet.php/messages?time=' + $scope.lastTime).success(function(data) {
for(id in data) {
item = data[id];
$scope.items.unshift(item);
if($scope.lastTime<item.time)
$scope.lastTime = item.time;
}
});
}
$scope.sendMessage = function() {
if(!$scope.message)
return;
$http.post('../templates/faucet.php/add_message', {message: $scope.message, email: $scope.email}).success(function() {
$scope.message = '';
});
}
$scope.periodicRefresh = function() {
$scope.refreshMessages();
$timeout($scope.periodicRefresh, 5000, true);
}
$scope.refreshMessages();
}
</script>
Can someone give me a clean code based on the above that will work for ngtouch and instruction as well. Thanks in advance.
You can write your own directive for touch event. Below is an example directive to handle touch events. The directive below only fire the event in case of touch/long touch. scope.isMoved will prevent firing event when user tap on screen and move they finger around.
function directive($timeout) {
var dir = {
link: link,
restrict: 'A',
scope: {
onTouch: '&'
}
};
return dir;
function link(scope, element) {
scope.isMoved = false;
$timeout(function () {
// user start tap on screen
element.bind('touchstart', function () {
scope.isMoved = false;
});
element.bind('touchend click', function (evt) {
if (!scope.isMoved) {
scope.onTouch(evt);
}
});
//
element.bind('touchmove', function () {
scope.isMoved = true;
});
});
}
}
In HTML:
<a on-touch="someFunction()"> Touch</a>
I am writing an angularjs app. The requirement is to display the user's data once the user logs in. So when an user successfully logs in, he/she is routed to the next view. My application is working fine upto this point. Now as the next view loads I need to display the existing records of the user. However at this point I see a blank page, I can clearly see in the console that the data is being returned but it is not binding. I have used $scope.$watch, $scope.$apply, even tried to call scope on the UI element but they all result in digest already in progress. What should I do? The page loads if I do a refresh
(function () {
"use strict";
angular.module("app-newslist")
.controller("newsController", newsController);
function newsController($http,$q,newsService,$scope,$timeout)
{
var vm = this;
$scope.$watch(vm);
vm.news = [];
vm.GetTopNews = function () {
console.log("Inside GetTopNews");
newsService.GetNewsList().
then(function (response)
{
angular.copy(response.data, vm.news);
}, function () {
alert("COULD NOT RETRIEVE NEWS LIST");
});
};
var el = angular.element($('#HidNews'));
//el.$scope().$apply();
//el.scope().$apply();
var scpe = el.scope();
scpe.$apply(vm.GetTopNews());
//scpe.$apply();
}
})();
Thanks for reading
you don't show how you're binding this in your template.. I tried to recreate to give you a good idea.
I think the problem is the way you're handling your promise from your newsService. Try looking at $q Promises. vm.news is being updated by a function outside of angular. use $scope.$apply to force refresh.
the original fiddle is here and a working example here
(function() {
"use strict";
var app = angular.module("app-newslist", [])
.controller("newsController", newsController)
.service("newsService", newsService);
newsController.$inject = ['$http', 'newsService', '$scope']
newsService.$inject = ['$timeout']
angular.bootstrap(document, [app.name]);
function newsController($http, newsService, $scope) {
var vm = this;
vm.news = $scope.news = [];
vm.service = newsService;
console.warn(newsService)
vm.message = "Angular is Working!";
vm.GetTopNews = function() {
console.log("Inside GetTopNews");
newsService.GetNewsList().
then(function(response) {
$scope.$apply(function() {
$scope.news.length > 0 ? $scope.news.length = 0 : null;
response.data.forEach(function(n) {
$scope.news.push(n)
});
console.log("VM", vm);
})
}, function() {
alert("COULD NOT RETRIEVE NEWS LIST");
});
};
}
function newsService($timeout) {
return {
GetNewsList: function() {
return new Promise(function(res, rej) {
$timeout(function() {
console.log("Waited 2 seconds: Returning");
res({
data: ["This should do the trick!"]
});
}, 2000);
})
}
}
}
})();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.9/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.18/angular-ui-router.min.js"></script>
<body>
<div class="main">
<div class="body" ng-controller="newsController as vm">
Testing: {{ vm.message }}
<br>{{ vm.news }}
<br>{{ vm }}
<br>
<button class="getTopNewsBtn" ng-click="vm.GetTopNews()">Get News</button>
<br>
<ul class="getTopNews">
<li class="news-item" ng-repeat="news in vm.news track by $index">
{{ news | json }}
</li>
</ul>
</div>
</div>
</body>
I am new to angular-js. I have two controllers (welcomeContoller,productController) and both handling the same model within the factory.
When the model getting updating by one controller(productController) it should reflect the update in another controller. (welcomeContoller)
But its not happening now.
HTML code :
<body ng-app="myApp">
<div ng-controller="welcomeContoller">
{{totalProductCnt}}
</div>
<div ng-controller="productController">
<div class="addRemoveCart">
<span class="pull-left glyphicon glyphicon-minus" ng-click="removeProduct()"></span>
<span class="pull-right glyphicon glyphicon-plus" ng-click="addProduct(1)"></span>
</div>
</div>
JS code
var myApp = angular.module("myApp", ['ui.bootstrap']);
myApp.factory("productCountFactory", function() {
return {
totalProducts:0
};
});
myApp.controller("welcomeContoller", function($scope, productCountFactory)
{
$scope.totalProductCnt = productCountFactory.totalProducts;
});
myApp.controller("productController", function($scope, productCountFactory) {
$scope.addProduct = function() {
productCountFactory.totalProducts++;
alert(productCountFactory.totalProducts);
};
$scope.removeProduct = function() {
if(productCountFactory.totalProducts >=1)
productCountFactory.totalProducts--;
alert(productCountFactory.totalProducts);
};
});
Even after the addProduct is called the totalProductCnt is displaying as zero. I want to display the value for each increment.
Plunkr Link
Put the factory object reference on scope:
myApp.controller("welcomeContoller", function($scope, productCountFactory) {
$scope.productCountFactory = productCountFactory;
});
Watch the property of the object.
{{productCountFactory.totalProducts}}
The DEMO on PLNKR.
By putting a reference on scope, on every digest cycle the watcher looks up the value of the property and updates the DOM if there is a change.
The totalProductCnt from your welcomeController isn't updated because it is assigned only once when the controller is created.
You can use several solutions to refresh the displayed value. Use a getter for your totalProducts in the factory :
myApp.factory("productCountFactory", function() {
var totalProducts = 0;
return {
getTotalProducts: function() {
return totalProducts;
},
addProduct: function() {
totalProducts++;
},
removeProduct: function() {
totalProducts--;
}
};
});
myApp.controller("welcomeContoller", function($scope, productCountFactory) {
$scope.getTotalProducts = productCountFactory.getTotalProducts;
});
myApp.controller("productController", function($scope, productCountFactory) {
$scope.addProduct = function() {
productCountFactory.addProduct();
};
$scope.removeProduct = function() {
if (productCountFactory.getTotalProducts() >= 1)
productCountFactory.removeProduct();
};
});
And update the view accordingly:
<div ng-controller="welcomeContoller">
{{getTotalProducts()}}
</div>
Plunkr Link
I'm somewhat new to the JS world, so I'm struggling a bit as to what I did wrong. My sample data from wordpress API is not working. Any ideas what I did wrong:
app.controller('FeedCtrl', function($http, $scope, $ionicLoading) {
console.log("Loading FeedCtrl");
$scope.stories = [];
function loadStories(params, callback) {
$http.get('http://public-api.wordpress.com/rest/v1/freshly-pressed/', {params: params})
.success(function(response) {
var stories = [];
angular.forEach(response.data.children, function(child) {
stories.push(child.data);
});
callback(stories);
});
}
$scope.loadOlderStories = function() {
var params = {};
if ($scope.stories.length > 0) {
params['after'] = $scope.stories[$scope.stories.length - 1].name;
}
loadStories(params, function(olderStories) {
$scope.stories = $scope.stories.concat(olderStories);
$scope.$broadcast('scroll.infiniteScrollComplete');
});
};
$scope.loadNewerStories = function() {
var params = {'before': $scope.stories[0].name};
loadStories(params, function(newerStories) {
$scope.stories = newerStories.concat($scope.stories);
$scope.$broadcast('scroll.refreshComplete');
});
};
I've made a simplified example with your data.
Click the 'Load more' button to retrieve some posts. You should see a list with the title and the author of a post.
EDIT: There appears to be some cross-domain request issues, that's why the 'Load stories' button won't work. Just try to reflect this code inside your controller, it should work.
var app = angular.module('myApp', []);
app.controller('feedCtrl', function ($scope, $http) {
$scope.stories = [];
$scope.loadStories = function loadStories() {
console.log('loading stories');
$http.get('http://public-api.wordpress.com/rest/v1/freshly-pressed/')
.then(function onSuccess(response) {
console.log(response);
$scope.stories = response.data.posts;
}, function onFailed(error) {
console.error('Error:', error)
});
}
});
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
</head>
<body ng-app="myApp">
<div ng-controller="feedCtrl">
<button data-ng-click="loadStories()">Load stories</button>
<ul>
<li data-ng-repeat="story in stories">Title: {{ story.title }} - {{ story.author.first_name }} {{ story.author.last_name }}</li>
</ul>
</div>
</body>
</html>
Normally we wouldn't handle $http calls in our angular.controller. This needs to be done in an angular.service.
I have a trobule about using $rootScope.$broadcast and $scope.$on
I have a one module and two controller(Controller1 & Controller2).
var app = angular.module("app",[]);
app.controller("Controller1",function ($scope,$rootScope){
$scope.$on("msgUpdated",function (event,data){
console.log(data.message);
})
app.controller("Controller2",function ($scope,$rootScope){
$scope.msg = "Hi!";
$rootScope.$broadcast("msgUpdated",{message:msg});
});
This above is my code.
The problem is that my Controller1's $scope.$on is not working.
Why? I don't get it.
and, How can I fix it to fire Controller1's $scope.$on ?
<body ng-app="app">
<div ng-controller="Controller1">
<h1>{{msg1}}</h1>
<input ng-model="test" ng-blur="sendMsg()"/>
</div>
<div ng-controller="Controller2">
<h1>{{msg2}}</h1>
<input ng-model="test" ng-blur="sendMsg()"/>
</div>
</body>
var app = angular.module('app',[])
.controller('Controller1',['$rootScope','$scope',function($rootScope,$scope){
$scope.msg1 = "Start";
$scope.sendMsg = function() {
$rootScope.$emit('msg',$scope.test);
};
var cleanup = $rootScope.$on('msg2', function (event,data) {
$scope.msg1 = data;
});
$scope.$on('$destroy', cleanup);
}])
.controller('Controller2',['$rootScope','$scope',function($rootScope,$scope){
$scope.msg2 = "Start2";
$scope.sendMsg = function() {
$rootScope.$emit('msg2',$scope.test);
};
var cleanup = $rootScope.$on('msg', function (event,data) {
$scope.msg2 = data;
});
$scope.$on('$destroy', cleanup);
}]);
Here is fiddler:
I always use $rootScope.$emit and clean up.
http://jsfiddle.net/hbqsbLyg/