Angular.js Accessing input field value inside controller $scope - angularjs

I've got a directive which defines a input field of type="file", which I can print and is not empty, namely:
<form class="form-horizontal" role="form">
<input type="file" file-model="myFile"/>
{{myFile}} <-- this prints fine
<button type="submit" class="btn btn-success" ng-click="saveData()">Post</button>
</form>
which I can see if called in the view
app.js
.directive('fileModel', ['$parse', function ($parse) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var model = $parse(attrs.fileModel);
var modelSetter = model.assign;
element.bind('change', function(){
scope.$apply(function(){
modelSetter(scope, element[0].files[0]);
});
});
}
};
}]);
What I am trying to do now is access the field inside my controller:
.controller('Ctrl', function($scope, fileUpload) {
...
$scope.myFile; <-- initialise it
$scope.saveData = function() {
var file = $scope.myFile;
console.log(file); <-- prints out as undefined
}
.service('fileUpload', ['$http', function ($http) {
this.uploadFileToUrl = function(file, uploadUrl){
var fd = new FormData();
fd.append('file', file);
$http.post(uploadUrl, fd, {
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
})
.success(function(){
})
.error(function(){
});
}
}]);
But file is undefined.
Any ideas why that would happen and how to access the value of the field?

If you want to bring in attribute values to your directive, I recommend doing it like so.
.directive('myDirective', ['$parse', function ($parse) {
return {
restrict: 'A',
scope: {
fileModel: '=fileModel'
}
link: function(scope, element, attrs) {
var model = scope.fileModel;
var modelSetter = model.assign;
element.bind('change', function(){
scope.$apply(function(){
modelSetter(scope, element[0].files[0]);
});
});
}
};
}]);
Note I changed your directive name since you already had an attribute with that name.
<input type="file" my-directive file-model="myFile"/>
I'm not sure what you are trying to do after you have the attribute value, but if you console.log(scope.fileModel) you can see what built in options are available. This is an example of isolated scope within directives.
Update with controller access
To access within your controller, you could emit the value from your directive:
scope.$emit('myFile', scope.fileModel);
and then listen for the event in your controller:
$scope.$on('myFile', function (event, myFile) {
$scope.myFile = myFile;
};
Update with working fiddle
http://jsfiddle.net/jonesmac82/376SS/26/

Related

Angular 1.x - retrieve directive value from controller and assign it back to parent scope

I'm unable to get a variable value from directive to use that back in a controller. I do not have to bind the value to a view. All I need is to set 'cleanInputValue' from directive to $scope.keywords in Controller.
Here is my directive and controller -
Html with md-autocomplete for keywords field - search box.
<form id="searchbox" ng-controller="navsearchController as sc" title="Search this site" layout="row">
<md-autocomplete
md-no-cache="true"
md-selected-item="sc.currentKeyword"
md-search-text="keywords"
md-items="item in querySearch(keywords)"
md-item-text="item.display"
md-min-length="3"
md-input-name="search"
md-input-id="search-nav"
md-clear-button="false"
placeholder="Search"
md-dropdown-position="bottom"
flex>
<md-item-template>
<span md-highlight-text="keywords" md-highlight-flags="gi">{{item.display}}</span>
</md-item-template>
</md-autocomplete>
<div class="search-button" flex="none">
<button type="submit" ng-click="sc.search()" title="Search" tabindex="0">GO</button>
</div>
</form>
Directive:
.directive('test', function () {
return {
require: 'ngModel',
restrict: 'A',
scope: {
text: '#text'
},
link:function(scope, element, attrs, modelCtrl){
modelCtrl.$parsers.push(function(inputValue) {
if (inputValue === undefined){
return '';
}
var cleanInputValue = inputValue.replace(/[^\w\s\-\"]/gi, '');
if (cleanInputValue != inputValue) {
modelCtrl.$setViewValue(cleanInputValue);
modelCtrl.$render();
}
return cleanInputValue;
});
//console.log(scope.text);
}
};
})
Controller:
.controller('navsearchController', function($timeout, $element, $compile, $scope, $rootScope, $http, $location, DataService, $routeParams, $filter, $route){
var _this = this;
$timeout(function () {
var myAutoCompleteInput =
angular.element($element[0].querySelector('#search-nav'));
myAutoCompleteInput.attr("test", "test");
//myAutoCompleteInput.attr("text", "blah");
console.log($scope.keywords);
$compile(myAutoCompleteInput)($scope);
});
_this.search = function(){
xyzStorage.set('currentKeyword', $scope.keywords);
$scope.keywords = $filter('removeSpecialChars')($scope.keywords);
$location.path('/xyz/search/' + $scope.keywords);
$location.url($location.path());
$location.search({
range: xyzStorage.get('itemPerPage'),
})
$route.reload();
};
})
What you really want to do is bind the value from your controller to your directive. Don't think of it as "returning" a value from your directive.
Let's take a look.
.directive('test', function () {
return {
require: 'ngModel',
restrict: 'A',
scope: {
text: '#text',
cleanInputValue: '=testTextClean' // Adding a new TWO WAY binding here!
},
link:function(scope, element, attrs, modelCtrl){
modelCtrl.$parsers.push(function(inputValue) {
if (inputValue === undefined){
return; // exit the function and don't assign, ok
}
// Now we use scope
scope.cleanInputValue = inputValue.replace(/[^\w\s\-\"]/gi, '');
if (scope.cleanInputValue != inputValue) {
modelCtrl.$setViewValue(scope.cleanInputValue);
modelCtrl.$render();
}
// no longer need a return
});
}
};
})
In your controller you are accessing the input element within the md-autocomplete component
.controller('navsearchController', function($timeout, $element, $compile, $scope, $rootScope, $http, $location, DataService, $routeParams, $filter, $route){
var _this = this;
$timeout(function () {
var myAutoCompleteInput =
angular.element($element[0].querySelector('#search-nav'));
myAutoCompleteInput.attr("test", "test");
myAutoCompleteInput.attr("test-text-clean", "sc.keywords");
console.log($scope.keywords);
$compile(myAutoCompleteInput)($scope);
});
_this.search = function(){
xyzStorage.set('currentKeyword', $scope.keywords);
$scope.keywords = $filter('removeSpecialChars')($scope.keywords);
$location.path('/xyz/search/' + $scope.keywords);
$location.url($location.path());
$location.search({
range: xyzStorage.get('itemPerPage'),
})
$route.reload();
};
})
Now in your controller the value in $scope.keywords will always have the updated value set from your directive.

file in the controller says undefined

I am having trouble while uploading a file. I have created one service:
angular
.module('app')
.service('fileUpload', ['$http', function ($http) {
this.uploadFileToUrl = function(file, uploadUrl){
var fd = new FormData();
fd.append('file', file);
$http.post(uploadUrl, fd, {
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
})
.success(function(){
})
.error(function(){
});
}}]);
then i have a directive:
.directive('fileModel', ['$parse', function ($parse) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var model = $parse(attrs.fileModel);
var modelSetter = model.assign;
element.bind('change', function(){
scope.$apply(function(){
modelSetter(scope, element[0].files[0]);
});
});
}
};}]);
And in the controller:
$scope.uploadFile = function(){
var file = $scope.myFile;
console.log('file is ' );
console.dir(file);
var uploadUrl = "/fileUpload";
fileUpload.uploadFileToUrl(file, uploadUrl);
};
HTML:
Browse <input type="file" file-model="myFile"/>
<button ng-click="uploadFile()">upload me</button>
As you can see i am using console.log in the controller and what i am having is
file is undefined
Thanks in advance!
change
.directive('fileModel', ['$parse', function ($parse) {
return {
restrict: 'A',
scope: {
percent: "=fileModel"
},
link: function(scope, element, attrs) {
var model = $parse(attrs.fileModel);
var modelSetter = model.assign;
scope.$watch('percent', function(value) {
console.log(value)
});
element.bind('change', function(){
scope.$apply(function(){
modelSetter(scope, element[0].files[0]);
});
});
}
};}]);
check the console value inside the percentage watch once you upload file

Directive / controller scope undefined when injecting directive in controller

I have this html
<input type="file" file-input="files" multiple />
<button ng-click="upload()" type="button">Upload</button>
<li ng-repeat="file in files">{{file.name}}</li>
This directive:
.directive('fileInput', ['$parse', function($parse){
return {
restrict: 'A',
link: function(scope, elm, attrs){
//console.log("directives scope: ")
elm.bind('change', function(){
$parse(attrs.fileInput)
.assign(scope,elm[0].files)
scope.$apply()
//console.log(scope);
})
}
}
}]);
and in my controller I have this function:
$scope.upload=function(){
console.log($scope.files)
var fd = new FormData() // put these 3 lines in a service
angular.forEach($scope.files, function(file){
fd.append('file', file)
})
$http.post('/api/directory/upload-file-test', fd,
{
transformRequest: angular.identity, // returns first argument it is passed
headers:{'Content-Type': undefined} //multipart/form-data
})
.success(function(d){
console.log(d)
console.log("works?")
})
}
It works fine if I just put the HTML directly in the html file as you'd normally do, however...
I need to inject it, and when I do that, the directive scope and controller scope is not the same.. so files which I've added to scope.files in the directive is just "undefined" inside the controller function, so my file upload breaks...
More exactly...
If I do this:
<tr ng-repeat="prop in tab.properties">
<td>{{prop.name}}</td>
<td compile ng-bind-html="prop.data_type.html | unsafe"></td>
<td>{{prop.description}}</td>
</tr>
Where the content inside the ng-bind-html quotes (prop.data_type.html) simply just equals to this:
<input type="file" file-input="files" multiple />
<button ng-click="upload()" type="button">Upload</button>
<li ng-repeat="file in files">{{file.name}}</li>
It doesn't work. The scopes are different.
My compile-directive looks like this:
.directive('compile',function($compile, $timeout){
return{
restrict:'A',
link: function(scope,elem,attrs){
$timeout(function(){
$compile(elem.contents())(scope);
});
}
};
})
and the last relevant bit of code would be the unsafe-filter which is this:
.filter('unsafe', function($sce) {
return function(val) {
return $sce.trustAsHtml(val);
};
})
Does anyone have an idea why my "upload" function inside my controller and my directive scope cannot stay synced and reference the same scope IF and only IF I inject my html with my compile-directive and unsafe-filter via ng-bind-html? Is there anyway around this or must I refrain from using directives to make this work?
I've tried first Angular 1.3.0 rc4 and now after I upgraded to latest version v. 1.3.5 it's still the same.
I was able to solve this, and here's the simple solution:
(old code):
<input type="file" file-input="files" multiple />
<button ng-click="upload()" type="button">Upload</button>
<li ng-repeat="file in files">{{file.name}}</li>
Replaced with this:
<input type="file" file-input="test.files" multiple />
<button ng-click="upload()" type="button">Upload</button>
<li ng-repeat="file in test.files">{{file.name}}</li>
And this old code:
.directive('fileInput', ['$parse', function($parse){
return {
restrict: 'A',
link: function(scope, elm, attrs){
//console.log("directives scope: ")
elm.bind('change', function(){
$parse(attrs.fileInput)
.assign(scope,elm[0].files)
scope.$apply()
//console.log(scope);
})
}
}
}]);
Should be replaced with this:
.directive('fileInput', ['$parse', function($parse){
return {
restrict: 'A',
link: function(scope, elm, attrs){
if(typeof(scope.test) == undefined){
scope.test = { "files": []}
}
if(typeof(scope.test.files) !== undefined){
scope.test["files"] =[]
}
elm.bind('change', function(){
$parse(attrs.fileInput)
.assign(scope,elm[0].files)
scope.$apply()
})
}
}
}]);
And the same with the controller function (old code first):
$scope.upload=function(){
console.log($scope.files)
var fd = new FormData() // put these 3 lines in a service
angular.forEach($scope.files, function(file){
fd.append('file', file)
})
$http.post('/api/directory/upload-file-test', fd,
{
transformRequest: angular.identity, // returns first argument it is passed
headers:{'Content-Type': undefined} //multipart/form-data
})
.success(function(d){
console.log(d)
console.log("works?")
})
}
Solution:
$scope.test = {files:undefined}
$scope.upload=function(){
console.log($scope.test.files)
var fd = new FormData() // put these 3 lines in a service
angular.forEach($scope.test.files, function(file){
fd.append('file', file)
})
$http.post('/api/directory/upload-file-test', fd,
{
transformRequest: angular.identity, // returns first argument it is passed
headers:{'Content-Type': undefined} //multipart/form-data
})
.success(function(d){
console.log(d)
console.log("works?")
})
}
If you need more explanation, the wise man Josh David Miller have it here. This was the comment that made me realize how to solve this problem: https://stackoverflow.com/a/15645354/3973406
Basically it has nothing to do with isolate scope, but because we were breaking a rule and Angular being a bitch about it!

AngularJS directive not being updated with parent scope variable changes

I have a directive and a controller in my AngularJS app as shown below, where I need the directive to be updated with the controller scope variable changes.
Problem I am facing is that any change to the controller scope variable do not update the directive. I've tried using {scope: false}, tried making an isolated scope and one-way binding with the controller scope variable as shown below but none worked, so can someone please check my code and let me know what I am missing / doing wrong here? Thanks
First trial using isolated scope in directive
.directive('loginPanelDir', function() {
return {
restrict: 'A',
scope: {
loginStatus: "&userLoginStatus"
},
link: function(scope, element, attrs) {
console.log(scope.loginStatus()); //will always print 0 despite of changes to the scope var in controller
}
};
});
.controller('LoginController', function ($scope, $location) {
$scope.LoginStatus = "0";
$scope.clickMe = function(){
$scope.LoginStatus = "1";
};
});
<div id="login" login-panel-dir user-login-status="LoginStatus">
<button id="btnLogin" type="submit" ng-click="clickMe()">Login</button>
Second trial using {scope:false} in directive
.directive('loginPanelDir', function() {
return {
restrict: 'A',
scope: false,
link: function(scope, element, attrs) {
console.log(scope.LoginStatus()); //will always print 0 despite of changes to the scope var in controller
scope.$watch(function(){ scope.LoginStatus }, function(){
console.log('Login status : '+scope.LoginStatus); //will always return 0...
});
}
};
});
.controller('LoginController', function ($scope, $location) {
$scope.LoginStatus = "0";
$scope.clickMe = function(){
$scope.LoginStatus = "1";
};
});
<div id="login" login-panel-dir>
<button id="btnLogin" type="submit" ng-click="clickMe()">Login</button>
You don't have to use $timeouts or $intervals to watch changes for certain scope values. Inside your directive you can watch for the changes of your login status via watching the user-login-status attribute.
DEMO
Something like this:
JAVASCRIPT
.controller('LoginController', function($scope) {
$scope.LoginStatus = "0";
$scope.clickMe = function(){
$scope.LoginStatus = "1";
};
})
.directive('loginPanelDir', function() {
return function(scope, elem, attr) {
scope.$watch(attr.userLoginStatus, function(value) {
console.log(value);
});
}
});
HTML
<div id="login" login-panel-dir user-login-status="LoginStatus">
<button id="btnLogin" type="submit" ng-click="clickMe()">Login</button>
</div>
Working plunk.
Use $timeout not setTimeout:
setTimeout(function(){
$scope.LoginStatus = "1";
}, 3000);
should be:
$timeout(function(){
$scope.LoginStatus = "1";
}, 3000);
setTimeout is native Javascript, so it will not run a digest and angular won't be notified of the changes hence no updates to the bindings, $timeout runs a digest when it completes, forcing an update to the binding.
Well its working here, just realized your $watch is also wrong:
it should be:
scope.$watch("LoginStatus", function(){
console.log('Login status : '+scope.LoginStatus); //will always return 0...
});

Sibling directives sharing scope

I'm running into an issue where, through a directive, I am trying to set a property on the scope of a controller. The issue is that, for some reason, the scope on the directive seems to be isolating itself, but only in this instance. It works fine in other places of the application. So when I attempt to use $scope.files in my controller, it's coming back as undefined.
Controller:
app.controller('newProjectModalController', function($scope, $modalInstance, $http, $location, account, $http){
$scope.account = account.data;
$scope.project = {
name: '',
client: '',
users: [],
image: '/assets/images/add-project-photo.jpg'
};
$scope.cancel = function(){
$modalInstance.dismiss('cancel');
};
$scope.updateImage = function(item){
var filereader = new FileReader();
filereader.readAsDataURL($scope.files.item(0));
filereader.onload = function(event){
$scope.project.image = event.target.result;
}
}
$scope.submit = function(){
var formData = new FormData();
formData.append('file', $scope.files.item(0));
$http.post($scope.api_url + '/Project', $scope.project)
.success(function(data, status, headers, config){
$modalInstance.close();
$location.path('/project/' + data.id);
});
};
});
Directive:
app.directive('fileUpload', function($parse){
return {
restrict: 'A',
transclude: true,
template: '<input type="file" name="file" multiple style="height:100%;width:100%;display:inline-block;opacity:0.0;position:absolute;top:0;left:0" />',
link: function(scope, element, attrs){
var onFileChange = $parse(attrs.fileUpload);
var file = element.children('input');
file.on('change', function(){
scope.files = file[0].files;
onFileChange(scope);
})
}
}
});
Template:
<div class="row fieldset not" id="photo">
<div class="col-sm-8 col-sm-offset-2">
<h2 class="tight">Add project photo</h2>
<div class="add-project-photo" file-upload="updateImage()" style="background-image: url({{project.image}})"></div>
<span class="note">300px <i>by</i> 120px</span>
</div>
</div>
The template is only partial... the file itself is rather large
Edit: I should mention that the modal is being built using UI-Bootstrap
Since you are not transcluding anything, you can remove transclude: true from your fileUpload directive. You can also set scope: false to tell the directive to use parent (controller) scope.
app.directive('fileUpload', function($parse){
return {
restrict: 'A',
scope: false,
template: '<input type="file" name="file" multiple style="height:100%;width:100%;display:inline-block;opacity:0.0;position:absolute;top:0;left:0" />',
link: function(scope, element, attrs){
var onFileChange = $parse(attrs.fileUpload);
var file = element.children('input');
file.on('change', function(){
scope.files = file[0].files;
onFileChange(scope);
})
}
}
});

Resources