Angularjs binding value from service - angularjs

I wish to share a service value between one or more controllers (only one in the following example but that's not the point).
The problema is that the value hold in the service is not bound and shown in the view.
The code (derived from angularjs basic service example) is:
(function(angular) {
'use strict';
angular.
module('myServiceModule', []).
controller('MyController', ['$scope', 'notify','$log', function($scope, notify, $log) {
$scope.callNotify = function(msg) {
notify.push(msg);
};
$scope.clickCount = notify.clickCount();
$log.debug("Click count is now", $scope.clickCount);
}]).
factory('notify', ['$window','$log', function(win,$log) {
var msgs = [];
var clickCounter = 0;
return {
clickCount: function() {
clickCounter = msgs.length;
$log.debug("You are clicking, click count is now", clickCounter);
return clickCounter;
},
push: function(msg) {
msgs.push(msg);
clickCounter = msgs.length;
$log.debug("Counter is", clickCounter);
if (msgs.length === 3) {
win.alert(msgs.join('\n'));
msgs = [];
}
}
}
}]);
I wish the counter to be displayed on page:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example - example-services-usage-production</title>
<script src="//code.angularjs.org/snapshot/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="myServiceModule">
<div id="simple" ng-controller="MyController as self">
<p>Let's try this simple notify service, injected into the controller...</p>
<input ng-init="message='test'" ng-model="message" >
<button ng-click="callNotify(message);">NOTIFY</button>
<p>(you have to click {{3-self.clickCount}} times more to see an alert)</p>
</div>
<div>You have clicked {{clickCount}} times</div>
</body>
</html>
See it in action on plunker
UPDATE: corrected the trivial errors is html and service code as suggested by #SehaxX

First your HTML is wrong. Your last div is not in the div of Controller, and you dont need the self.
<body ng-app="myServiceModule">
<div id="simple" ng-controller="MyController">
<p>Let's try this simple notify service, injected into the controller...</p>
<input ng-init="message='test'" ng-model="message" >
<button ng-click="callNotify(message);">NOTIFY</button>
<p>(you have to click {{3-self.clickCount}} times more to see an alert)</p>
<div>You have clicked {{clickCount}} times</div>
</div>
</body>
Also in your service you are missing return:
clickCount: function() {
clickCounter = msgs.length;
$log.debug("You are clicking, click count is now", clickCounter);
return clickCounter;
},
And in your controller you only once call the notify.clickCount() so you need to add it to the method:
$scope.callNotify = function(msg) {
notify.push(msg);
$scope.clickCount = notify.clickCount();
$log.debug("Click count is now", $scope.clickCount);
};
Here also a working code pen with "Controller as self" if you want. But then in controller you must use this and not $scope.
Cheers,

Related

File drag and drop functionality in Angular js/MVC not working

I am trying to implement a simple file drag and drop functionality in Angular js/MVC.
I created a directive for the drag and drop.
(function (angular, undefined) {
'use strict';
angular.module('fileupload', [])
.directive("myDirective", function ($parse) {
return {
restrict: 'A',
link: fileDropzoneLink
};
function fileDropzoneLink(scope, element, attrs) {
element.bind('dragover', processDragOverOrEnter);
element.bind('dragenter', processDragOverOrEnter);
element.bind('dragend', endDragOver);
element.bind('dragleave', endDragOver);
element.bind('drop', dropHandler);
var onImageDrop = $parse(attrs.onImageDrop);
//When a file is dropped
var loadFile = function (files) {
scope.uploadedFiles = files;
scope.$apply(onImageDrop(scope));
};
function dropHandler(angularEvent) {
var event = angularEvent.originalEvent || angularEvent;
var files = event.dataTransfer.files;
event.preventDefault();
loadFile(files)
}
function processDragOverOrEnter(angularEvent) {
var event = angularEvent.originalEvent || angularEvent;
if (event) {
event.preventDefault();
}
event.dataTransfer.effectAllowed = 'copy';
element.addClass('dragging');
return false;
}
function endDragOver() {
element.removeClass('dragging');
}
}
});
}(angular));
This is the template
<div class="dropzone" data-my-Directive on-image-drop="$ctrl.fileDropped()">
Drag and drop pdf files here
</div>
This is my component code
(function (angular, undefined) {
'use strict';
angular.module('test', [])
.component('contactUs', contactUs());
function contactUs() {
ContactUs.$inject = ['$scope', '$http'];
function ContactUs($scope, $http) {
var ctrl = this;
ctrl.files = [];
ctrl.services = {
$scope: $scope,
$http: $http,
};
}
//file dropped
ContactUs.prototype.fileDropped = function () {
var ctrl = this;
var files = ctrl.services.$scope.uploadedFiles;
angular.forEach(files, function (file, key) {
ctrl.files.push(file);
});
}
return {
controller: ContactUs,
templateUrl: 'partials/home/contactus/'
};
}
}(angular));
Sometimes the drag and drop works absolutely fine without any issue. But some times I get the below issue and the drag and drop does not work and I get the black invalid cursor.
This issue is random and i do not see any errors in the console.
And I also tried other third party components like angular-file-upload
https://github.com/nervgh/angular-file-upload and I am seeing the exact same issue with that component also.
EDIT :
Answer updated for pdf preview. The code is available in the same plunker.
References : Excellent solution by #Michael at https://stackoverflow.com/a/21732039/6347317
In the example above, the response from the http POST is used in "new Blob([response]". In angular-file-upload library, the "response" would be "fileItem._file" property in "uploader.onAfterAddingFile" function. You can console log to check the data these have , so as to understand it better.
Also please note that if PDf viewer is not enabled in chrome, it has to be enabled using this: https://support.google.com/chrome/answer/6213030?hl=en
END EDIT
Since you mentioned that you tried with angular-file-upload library, i have created a plunker with it:
http://plnkr.co/edit/jeYg5fIRaC9wuEYSNOux?p=info
HTML:
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
<link rel="stylesheet" href="style.css" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script data-require="angular.js#1.5.x" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.min.js" data-semver="1.5.11"></script>
<script src="http://netdna.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-file-upload/2.5.0/angular-file-upload.min.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<div class="col-sm-4">
<h3>Select files</h3>
<div ng-show="uploader.isHTML5">
<!-- 3. nv-file-over uploader="link" over-class="className" -->
<div class="well my-drop-zone" style="width:300px" nv-file-drop nv-file-over="" uploader="uploader"
filters="syncFilter">
<label>
Click here or Drag and Drop file
<input type="file" style="visibility:hidden;" nv-file-select nv-file-over="" uploader="uploader"
filters="syncFilter" multiple/>
</label>
<div class="progress" style="margin-bottom: 0;">
<div class="progress-bar" role="progressbar" ng-style="{ 'width': uploader.queue[0].progress + '%' }"></div>
</div>
<div>{{uploader.queue[0].file.name}}</div>
<div ng-show="showAlert" class="alert alert-warning alert-dismissable">
×
<strong>Clear the existing file before uploading again!!</strong>
</div>
</div>
</div>
</div>
</body>
</html>
JS:
var app = angular.module('plunker', ['angularFileUpload']);
app.controller('MainCtrl', function($scope,FileUploader) {
var uploader = $scope.uploader = new FileUploader();
// FILTERS
// a sync filter
uploader.filters.push({
name: 'syncFilter',
fn: function(item /*{File|FileLikeObject}*/, options) {
console.log('syncFilter' + this.queue.length);
return this.queue.length < 1;
}
});
// an async filter
uploader.filters.push({
name: 'asyncFilter',
fn: function(item /*{File|FileLikeObject}*/, options, deferred) {
console.log('asyncFilter');
setTimeout(deferred.resolve, 1e3);
}
});
uploader.allowNewFiles = true;
uploader.filters.push({
name:'csvfilter',
fn: function() {
return this.allowNewFiles;
}
});
// CALLBACKS
uploader.onWhenAddingFileFailed = function(item /*{File|FileLikeObject}*/, filter, options) {
console.info('onWhenAddingFileFailed', item, filter, options);
$scope.showAlert=true;
};
uploader.onAfterAddingFile = function(fileItem) {
};
});
It is pretty straight forward and i dont get the error you mentioned. We are actually using this library in one of our Projects. I have also added a filter to restrict upload to only 1 file.
Please check this and let me know how it goes or if you have any doubts in the code.

How to update model in ui-codemirror

I have two separate controllers which shared a property. If the first controller changes the property the second controller should recognize it and should change the text in the codemirror text area. I tried to figure it out in this fiddle example but I could not find a solution.
var app = angular.module('myApp', ['ui.codemirror']);
app.service('sharedProperties', function() {
var objectValue = {
data: 'test object value'
};
return {
setText: function(value) {
objectValue.data = value;
},
getText: function() {
return objectValue;
}
}
});
app.controller('myController1', function($scope, $timeout, sharedProperties) {
$scope.setText = function(text){
sharedProperties.setText(text);
console.log(sharedProperties.getText().data);
}
});
app.controller('myController2', function($scope, sharedProperties) {
$scope.editorOptions = {
lineWrapping: true,
lineNumbers: true,
readOnly: 'nocursor',
mode: 'xml'
};
$scope.mappingFile = sharedProperties.getText();
console.log($scope.mappingFile);
});
<div ng-app="myApp">
<div ng-controller="myController1">
<input type="text" ng-model="newText"></input>
<button ng-click="setText(newText)">Set Text</button><br/>
</div>
<div ng-controller="myController2">
<ui-codemirror ui-codemirror-opts="editorOptions" ng-model="mappingFile.data" ui-refresh="true"></ui-codemirror>
</div>
</div>
At first, the way that you're doing you have 2 controllers in the same page but without any relation, I'd suggest you to make one of them as child of another.
So, to achieve what you want you need do a kind of watch on that variable from the parent controller.
Steps:
Use the $broadcast to send data to the child controller
$scope.$broadcast('newText', $scope.newText);
Use $on to receive the data from the parent controller:
$scope.$on('newText', function(event, text) {
...
});
Here's the code working based on your original code:
(function() {
'use strict';
angular
.module('myApp', ['ui.codemirror'])
.controller('myController1', myController1)
.controller('myController2', myController2);
myController1.$inject = ['$scope', '$timeout'];
function myController1($scope, $timeout) {
$scope.setText = function(text) {
console.log('Sent...', $scope.newText);
$scope.$broadcast('newText', $scope.newText);
}
}
myController2.$inject = ['$scope'];
function myController2($scope) {
$scope.editorOptions = {
lineWrapping: true,
lineNumbers: true,
readOnly: 'nocursor',
mode: 'xml'
};
$scope.$on('newText', function(event, text) {
if (!text) return;
$scope.mappingFile = text;
console.log('Received... ', $scope.mappingFile);
});
}
})();
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.17.0/codemirror.min.js"></script>
<script src="https://rawgit.com/angular-ui/ui-codemirror/master/src/ui-codemirror.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
</head>
<body>
<div ng-app="myApp">
<div ng-controller="myController1">
<input type="text" ng-model="newText">
<button ng-click="setText()">Set Text</button>
<hr>
<div ng-controller="myController2">
<textarea ui-codemirror-opts="editorOptions" ng-model="mappingFile"></textarea>
</div>
</div>
</div>
</body>
</html>
Some notes:
You don't need to pass ngModel as parameter in your ngClick, you can access it directly in your controller simply calling $scope.newText (as I did);
<input> is a self-closing tag, so of course, you don't need to close it.
I hope it helps.

Angular accordion doesn't update UI when data source has been changed

I start learning Angular and faced with some strange behaviour.
I want to add a new header to accordion dynamically but I accordion doesn't reflect it on UI till I explicitly click on some of his items. By some reason he doesn't react on items changes before it starts load itserlf aggain durnig DOM rendering.
var mainApp = angular.module('ui.bootstrap.demo', ['ui.bootstrap', 'ngResource']);
mainApp.factory('teamSharedObj',['$rootScope', function($rootScope) {
return {
teams: [],
peopleInTeam: [],
addNewTeam: function(item) {
console.log("add new team: " + item);
this.teams.push(item);
$rootScope.$broadcast('team.new');
},
addTeamMembers: function(team, teamMembers) {
for (var i = 0; i < teamMembers.length; i++) {
var temp;
// put in team as key-value pair
temp[team] = teamMembers[i]
console.log("add new team member: " + temp);
peopleInTeam.push(temp);
}
if (teamMembers.length != 0) {
$rootScope.$broadcast('teamMember.new');
}
}
}
}]);
mainApp.directive("addNewTeam", ['teamSharedObj', 'teamSharedObj', function (teamSharedObj) {
return {
restrict: 'A',
link: function( scope, element, attrs ) {
element.bind('click', function() {
console.log(scope.teamName)
teamSharedObj.addNewTeam(scope.teamName)
});
}
}
}])
mainApp.controller('teamListCtrl', ['$scope', 'teamSharedObj', function($scope, teamSharedObj) {
$scope.$on('team.new', function(event) {
console.log('new team ' + event);
$scope.items = teamSharedObj.teams;
}
);
$scope.oneAtATime = true;
$scope.items = ['new', 'another one'];//teamSharedObj.teams;
}]);
<!DOCTYPE html>
<html>
<head lang="en">
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.js"></script>
<script src= "http://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular-resource.js"></script>
<script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.11.2.js"></script>
<script src="js/test.team.js"></script>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body ng-app="ui.bootstrap.demo">
<div id="teamBlock">
<input type="text" ng-model="teamName" >
<input add-new-team type="submit" value="Add new team" >
<!--<button add-book-button>Add data</button>-->
</div>
<div>
{{teamName}}
</div>
<div ng-controller="teamListCtrl">
<accordion close-others="oneAtATime" >
<accordion-group heading="{{d}}" ng-repeat="d in items">
This content is straight in the template.
</accordion-group>
</accordion>
<div ng-repeat="item in items">{{item}}</div>
</div>
</body>
</html>
Can you suggest me please a right way to notifu component about changes in its datasource?
bind is jqLite/jQuery method and does not automatically trigger the digest loop for you. This means no dirty checking will take place and the UI will not be updated to reflect the model changes.
To trigger it manually wrap the code in a call to $apply:
element.bind('click', function() {
scope.$apply(function () {
teamSharedObj.addNewTeam(scope.teamName);
});
});
And since teamSharedObj contains a reference to the array the controller can reference it directly. Then you do not need to use $broadcast:
addNewTeam: function(item) {
this.teams.push(item);
},
And:
mainApp.controller('teamListCtrl', ['$scope', 'teamSharedObj',
function($scope, teamSharedObj) {
$scope.oneAtATime = true;
$scope.items = teamSharedObj.teams;
}
]);
Demo: http://plnkr.co/edit/ZzZN7wlT10MD0rneYUBM?p=preview

How to $watch multiples properties simultaneous and interpolate into one expression?

Suppose two input fields - name and text. How to simultaneous watch this two fields and interpolate their value into one expression?
Thanks!
Update 9/7/2014:
I did this Plunkr with a working version of the code :)
Thanks Mohammad Sepahvand!
Code:
<!doctype html>
<html ng-app="myApp">
<head>
<title>Interpolate String Template Example</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.5/angular.js"></script>
<script type="text/javascript">
angular.module('myApp', ['emailParser']).controller('MyController', ['$scope', 'EmailParser', function ($scope, EmailParser) {
// init
$scope.to = '';
$scope.emailBody = '';
$scope.$watchCollection('[to, emailBody]', function (newValues, oldValues) {
// do stuff here
// newValues and oldValues contain the new and respectively old value
// of the observed collection array
if (newValues[0] && newValues[1]) { // there's name and some text?
$scope.previewText = EmailParser.parse(newValues[1], {to: $scope.to});
}
});
}]);
angular.module('emailParser', []).config(['$interpolateProvider', function ($interpolateProvider) {
$interpolateProvider.startSymbol('__');
$interpolateProvider.endSymbol('__');
}]).factory('EmailParser', ['$interpolate', function ($interpolate) { // create service
return {
parse: function (text, propertiesToBeInterpolated) { // handle parsing
var template = $interpolate(text);
return template(propertiesToBeInterpolated);
}
};
}]);
</script>
</head>
<body>
<h3>Instructions in readme.md file - please read before!</h3>
<div id="emailEditor" ng-controller="MyController">
<label>*Name:</label>
<input ng-model="to" type="text" placeholder="Ex.: John"/>
<br><br>
<label>*Text:</label><br>
<textarea ng-model="emailBody" cols="25" rows="10" placeholder="Write something"></textarea>
<p style="color:red;">*required</p>
<div>
<pre>__previewText__</pre>
</div>
</div>
</body>
</html>
You can use the $watchGroup method that was added in angular 1.3:
$scope.$watchGroup(['prop1', 'prop2'], function(newValues, oldValues, scope) {
var prop1 =newValues[0];
var prop2 =newValues[1];
});
Or you could use $watchCollection which has been available since angular 1.1.4:
scope.$watchCollection('[prop1, prop2]', function(newValues, oldValues){
});

broadcasting model changes

I'm trying to set up a little POC to see whether or not angular would work for something I'm in the middle of.
I set up a REST server which I am able to CRUD with via angular. However, as the documentation and tutorials out there are so all over the place (read: SUPER inconsistent), I am not sure that the behavior I'm not seeing is the result of incorrect code or it's not something I can do like this.
I've gleaned from the docs that two-way binding is available, but it isn't clear how it works. NB I've read dozens of articles explaining how it works at a low level a'la https://stackoverflow.com/a/9693933/2044377 but haven't been able to answer my own question.
I have angular speaking to a REST service which modifies a sql db.
What I am wondering about and am trying to POC is if I have 2 browsers open and I change a value in the db, will it reflect in the other browser window?
As I said, I have it updating the db, but as of now it is not updating the other browser window.
app.js
angular.module('myApp', ['ngResource']);
var appMock = angular.module('appMock', ['myApp', 'ngMockE2E']);
appMock.run(function($httpBackend) {});
controllers.js
function MainCtrl($scope, $http, $resource) {
$scope.message = "";
$scope.fruits = [];
$scope.fruit = {};
$scope.view = 'partials/list.html';
var _URL_ = '/cirest/index.php/rest/fruit';
function _use_$resources_() { return false; }
function _fn_error(err) {
$scope.message = err;
}
$scope.listFruits = function() {
$scope.view = 'partials/list.html';
var fn_success = function(data) {
$scope.fruits = data;
};
$http.get(_URL_).success(fn_success).error(_fn_error);
}
function _fn_success_put_post(data) {
$scope.fruit = {};
$scope.listFruits();
}
function createFruit() {
$http.post(_URL_, $scope.fruit).success(function(data){
$scope.listFruits()
}).error(_fn_error);
}
function updateFruit() {
$http.post(_URL_, $scope.fruit).success(_fn_success_put_post).error(_fn_error);
}
function deleteFruit() {
$http.put(_URL_, $scope.fruit).success(_fn_success_put_post).error(_fn_error);
}
$scope.delete = function(id) {
if (!confirm("Are you sure you want do delete the fruit?")) return;
$http.delete("/cirest/index.php/rest/fruit?id=" + id).success(_fn_success_put_post).error(_fn_error);
}
$scope.newFruit = function() {
$scope.fruit = {};
$scope.fruitOperation = "New fruit";
$scope.buttonLabel = "Create";
$scope.view = "partials/form.html";
}
$scope.edit = function(id) {
$scope.fruitOperation = "Modify fruit";
$scope.buttonLabel = "Save";
$scope.message = "";
var fn_success = function(data) {
$scope.fruit = {};
$scope.fruit.id = id;
$scope.view = 'partials/form.html';
};
$http.get(_URL_ + '/' + id).success(fn_success).error(_fn_error);
}
$scope.save = function() {
if ($scope.fruit.id) {
updateFruit();
}
else {
createFruit();
}
}
$scope.cancel = function() {
$scope.message = "";
$scope.fruit = {};
$scope.fruits = [];
$scope.listFruits();
}
$scope.listFruits();
}
MainCtrl.$inject = ['$scope', '$http', '$resource'];
list.html
{{message}}
<hr/>
New Fruit
<ul ng-model="listFruit">
<li ng-repeat="fruit in fruits">
id [{{fruit.id}}] {{fruit.name}} is {{fruit.color}}
[X]
</li>
</ul>
index.html
<!doctype html>
<html lang="en" ng-app="myApp">
<head>
<meta charset="utf-8">
<title>FRUUUUUUUUUUUUUUUUUUUUUUUUUUUIT</title>
<link rel="stylesheet" href="css/bootstrap/css/bootstrap.css"/>
</head>
<body>
<div class="navbar">NAVBARRRRRRRRRRR</div>
<div class="container">
<div class="row">
<div ng-controller="MainCtrl">
<button ng-click="listFruits()">ListFruit()</button>
<button ng-click="cancel()">Cancel()</button>
<ng-include src="view"></ng-include>
</div>
</div>
</div>
<!-- In production use:
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.4/angular.min.js"></script>
-->
<script src="lib/angular/angular.js"></script>
<script src="lib/angular/angular-resource.js"></script>
<script src="js/app.js"></script>
<script src="js/services.js"></script>
<script src="js/controllers.js"></script>
<script src="js/filters.js"></script>
<script src="js/directives.js"></script>
</body>
</html>
form.html
<h3>{{fruitOperation}}</h3>
<hr/>
<form name="fruitForm">
<input type="hidden" name="" ng-model="fruit.id" />
<p><label>name</label><input type="text" name="name" ng-model="fruit.name" value="dfgdfgdfg" required="true" /></p>
<p><label>color</label><input type="text" name="color" ng-model="fruit.color" value="fruit.color" required="true" /></p>
<hr/>
<input type="submit" ng-click="save()" value="{{buttonLabel}}" /> <button ng-click="cancel()">Cancel</button>
</form>
Thanks for any insight or pointers.
Two-way binding refers to changes occurring in your controller's scope showing up in your views and vice-versa. Angular does not have any implicit knowledge of your server-side data. In order for your changes to show up in another open browser window, for example, you will need to have a notification layer which pushes changes to the client via long polling, web sockets, etc.

Resources