[RxJS + AngularJS] Sync Requests with RxJS and Angular
When you implement a search bar, the user can make several different queries in a row. With a Promise based implementation, the displayed result would be what the longest promise returns. This is the problem which we want to solve.
<!DOCTYPE html> <html> <head> <script src="https://cdn.rawgit.com/lodash/lodash/3.0.1/lodash.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/less.js/1.3.3/less.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/2.3.22/rx.all.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script> <meta charset="utf-8"> <title>JS Bin</title> </head> <body ng-app="APP"> <div> <p>Click on "Pets" then on "Nothing" in each form</p> <p>Because pets' promise take long to resolve (longer than nothing's), it ends up with inconsistent output in Basic setup: nothing should be displayed but pets arrive!</p> <p>RxJs is an elegant way to prevent this concurrency problems from appearing.</p> </div> <div ng-controller="StandardController as standard" style="margin-top:50px;"> <p>Basic setup</p> <input id="s-pets" name="standardEntityType" type="radio" ng-model="standard.filters.entityType" value="pets"> <label for="s-pets">Pets</label> <input id="s-colors" name="standardEntityType" type="radio" ng-model="standard.filters.entityType" value="colors"> <label for="s-colors">Colors</label> <input id="s-nothing" name="standardEntityType" type="radio" ng-model="standard.filters.entityType" value="nothing"> <label for="s-nothing">Nothing</label> <p> {{ standard.filters | json }} </p> <ul> <li ng-repeat="entity in standard.entities"> {{ entity.name }} </li> </ul> </div> </body> </html>
console.clear(); angular.module('APP', []) .service('dataService', function($q, $timeout, $rootScope){ var colors = [ { name: 'red' }, { name: 'blue' }, { name: 'white' } ]; var pets = [ { name: 'cat' }, { name: 'dog' } ]; function fakeAjax(returnValue, delay){ return $q(function(resolve){ $timeout(function() { resolve(returnValue); }, delay); }); } function getColors(){ return fakeAjax(colors, 1500); } function getPets(){ return fakeAjax(pets, 3000); } function nullResponse(){ return fakeAjax([], 1); } var mapping = { colors: getColors, pets: getPets }; return { getEntities: function(type){ if (mapping[type]) return mapping[type](); else return nullResponse(); } }; }) .controller('StandardController', function($scope, dataService){ this.entities = []; this.filters = {}; var controller = this; function searchEntities(type){ dataService.getEntities(type) .then(function(entities){ controller.entities = entities; }); } $scope.$watch(function(){ return controller.filters.entityType; }, function(newVal){ searchEntities(newVal); }); });
Solution 1: Add lodash _.debounce method to add some delay.
.controller('StandardController', function($scope, dataService){ this.entities = []; this.filters = {}; var controller = this; function _searchEntities(type){ dataService.getEntities(type) .then(function(entities){ controller.entities = entities; }); } var searchEntities = _.debounce(_searchEntities, 1500); $scope.$watch(function(){ return controller.filters.entityType; }, function(newVal){ searchEntities(newVal); }); });
The problem here is we have to assume a correct time. Too long, and the UI is not responsible enough, and too short, and we may encounter weirdness again.
Solution 2: Using RxJS
.controller('StandardController', function($scope, dataService){ this.entities = []; this.filters = {}; var controller = this; var Observable = Rx.Observable; var source = Observable.create(function(observe){ $scope.$watch(function(){ return controller.filters.entityType; }, function(newType){ observe.onNext(newType); }); }).flatMapLatest(function(type){ return Observable.fromPromise(dataService.getEntities(type)); }); var sub = source.subscribe(function(entities){ controller.entities = entities; }); });
No matter what order we click the radio buttons, we'll always get the expected outcome. RxJS will handle that for us. The main benefit of RxJS over mere promises is we always get the latest query results. You can see, this implementation of Rx has a way to cancel promises.
Two methos to apply:
.controller('StandardController', function($scope, dataService){ this.entities = []; this.filters = {}; var controller = this; var Observable = Rx.Observable; var source = Observable.create(function(observe){ $scope.$watch(function(){ return controller.filters.entityType; }, function(newType){ observe.onNext(newType); }); }).debounce(500).flatMapLatest(function(type){ return Observable.fromPromise(dataService.getEntities(type)); }); var sub = source.subscribe(function(entities){ controller.entities = entities; }); $scope.$on('destory', function(){ sub.dispose(); }); });
The first thing is to clean after yourself. On each destroy event on the scope, so basically, whenever you use a router, it could be on route change, you have to dispose the listener so Rx knows it can get rid of everything linked to it.
The second thing is we don't want to put too much pressure on our server, so we are going to use debounce again. It's very important to understand here that debounce is not a way to avoid UI issues. It's a way to avoid useless server queries.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具