Sunday, October 12, 2014

Changes to model done outside of AngularJS' callback doesn't reflect on UI

Callbacks made outside of AngularJS can't be monitored by AngularJS, hence when there are changes on model it will not take effect on UI. An example:
<div ng-app='theApp' ng-controller='SampleController as c'>
    
    <label>Search</label><p><input type='text' ng-model='c.topic'/>    
    
    <button ng-click='c.getTopMatch()'>Get Top Match</button>
    
    <p>
        <span ng-show='c.topMatch.length !=""'>Top Match: {{c.topMatch}}<span> 
    </p>
        
</div>


var app = angular.module('theApp', ['oitozero.ngSweetAlert']);

app.controller('SampleController',['$http', 'SweetAlert', function($http, SweetAlert) {
    var self = this;
    
    self.topic = 'angularjs';

    self.topMatch = '';

    
    self.getTopMatch = function() {
                        
        $http.jsonp('http://ajax.googleapis.com/ajax/services/search/web?v=1.0&callback=JSON_CALLBACK',
                    {
                        params : { 'q' : self.topic }
                    })
        .success(function(data) {        

            self.topMatch = data.responseData.results[0].url;  
            
            SweetAlert.swal({
               title: "Clear Search?",
               text: "Everyone wants a clear textbox",
               type: "success",
               showCancelButton: true,
                cancelButtonText: 'No',
               confirmButtonText: "Yes!"
            }, 
            function(isConfirm){                        
     
                if (!isConfirm) return;
                  
                self.topic = '';
                
                
            });      
            
        });     
        
    };
}]);


We can solve that by wrapping our changes on model inside of AngularJS $q service:
var app = angular.module('theApp', ['oitozero.ngSweetAlert']);

app.controller('SampleController',['$http', '$q', 'SweetAlert', function($http, $q, SweetAlert) {
    var self = this;
    
    self.topic = 'angularjs';

    self.topMatch = '';

    
    self.getTopMatch = function() {
                        
        $http.jsonp('http://ajax.googleapis.com/ajax/services/search/web?v=1.0&callback=JSON_CALLBACK',
                    {
                        params : { 'q' : self.topic }
                    })
        .success(function(data) {        

            self.topMatch = data.responseData.results[0].url;  
            
            SweetAlert.swal({
               title: "Clear Search?",
               text: "Everyone wants a clear textbox",
               type: "success",
               showCancelButton: true,
               cancelButtonText: 'No',
               confirmButtonText: "Yes!"
            }, 
            function(isConfirm){                        

                if (!isConfirm) return;                

                var deferred = $q.defer();
                deferred.promise.then(function() {
                    self.topic = '';
                });
                
                deferred.resolve();
                
            });      
            
        });     
        
    };
}]);


Note that we need to inject $q service to our controller. If that's a bit overkill, we can skip the use of $q service and use another approach. Another approach is to dynamically add a promise on existing $http's promise when a callback outside of AngularJS is made, an example:

var app = angular.module('theApp', ['oitozero.ngSweetAlert']);

app.controller('SampleController',['$http', 'SweetAlert', function($http, SweetAlert) {
    var self = this;
    
    self.topic = 'angularjs';

    self.topMatch = '';

    
    self.getTopMatch = function() {
                        
        var thePromise = 
            $http.jsonp('http://ajax.googleapis.com/ajax/services/search/web?v=1.0&callback=JSON_CALLBACK',
                    {
                        params : { 'q' : self.topic }
                    })
            .success(function(data) {        
    
                self.topMatch = data.responseData.results[0].url;  
                
                SweetAlert.swal({
                   title: "Clear Search?",
                   text: "Everyone wants a clear textbox",
                   type: "success",
                   showCancelButton: true,
                    cancelButtonText: 'No',
                   confirmButtonText: "Yes!"
                }, 
                function(){                                        
                    
                    thePromise.then(function() {
                        self.topic = '';
                    });                                
                    
                });      
                
            });     
        
    };
}]);


UPDATE


Another good approach is to use $timeout, unlike $scope.$apply/$scope.$digest, $timeout is testable. Though it works, it is not advisable to use $scope.$apply/$scope.$digest in a controller. $timeout works and it is testable:

var app = angular.module('theApp', ['oitozero.ngSweetAlert']);

app.controller('SampleController',['$http', '$timeout', 'SweetAlert', function($http, $timeout, SweetAlert) {
    var self = this;
    
    self.topic = 'angularjs';

    self.topMatch = '';

    
    self.getTopMatch = function() {
                        
        $http.jsonp('http://ajax.googleapis.com/ajax/services/search/web?v=1.0&callback=JSON_CALLBACK',
                    {
                        params : { 'q' : self.topic }
                    })
        .success(function(data) {        

            self.topMatch = data.responseData.results[0].url;  
            
            SweetAlert.swal({
               title: "Clear Search?",
               text: "Everyone wants a clear textbox",
               type: "success",
               showCancelButton: true,
                cancelButtonText: 'No',
               confirmButtonText: "Yes!"
            }, 
            function(isConfirm){                        

                if (!isConfirm) return;
                
                $timeout(function() {
                    self.topic = '';
                });
                
            });      
            
        });     
        
    };
}]);


Happy Coding!

No comments:

Post a Comment