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);
})
}
}
});
Related
I want to send that to my directive but I want that data to stay updated if the data in the controller changes.
// Controller
angular
.module('app')
.controller('IndexController', IndexController)
IndexController.$inject = [];
function IndexController() {
var vm = this;
vm.name = 'John';
newName = function() {
vm.name = 'Brian';
}
newName();
}
// Directive
angular
.module('app')
.directive('userName', userName);
userName.$inject = ['$document'];
function userName($document) {
var directive = {
restrict: 'EA',
template: '<div id="user"></div>',
replace: true,
scope: {
name: '='
},
link: function(scope, elem, attrs) {
console.log(scope.data);
}
}
return directive;
}
this is how I use the directive. the problem is that it always returns the first name and not the new name after the change in the controller.
<div ng-controller="indexController">
<user-name name="indexController.name">
</div>
thank you.
Try this, you just have to inject $scope into your Indexcontroller
angular
.module('app', [])
.controller('IndexController', function($scope) {
var vm = this;
vm.name = 'John';
vm.newName = function() {
vm.name = 'Brian';
console.log(vm.name);
}
//vm.newName();
})
.directive('userName', ['$document', function() {
var directive = {
restrict: 'E',
template: '<div id="user"></div>',
replace: true,
scope: {
name: '='
},
link: function(scope, elem, attrs) {
console.log(scope.name);
}
}
return directive;
}])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="IndexController as vm">
<user-name name="vm.name"></user-name>
<button ng-click="vm.newName()">Click</button>
</div>
Without using as in controller, you cannot use controller.prop inside the scope.
Inside the controlleryou need to call the method using its $scope or this.
Check the below code.
angular
.module('app', [])
.controller('IndexController', function($scope) {
$scope.name = 'John';
$scope.newName = function() {
$scope.name = 'Brian';
}
$scope.newName();
})
.directive('userName', ['$document', function() {
var directive = {
restrict: 'E',
template: '<div id="user"></div>',
replace: true,
scope: {
name: '='
},
link: function(scope, elem, attrs) {
console.log(scope.name);
}
}
return directive;
}])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="IndexController">
<user-name name="name"></user-name>
</div>
I created a directive for google map auto-complete. everything is working fine, but the problem is when I need to access the value of input and re-set it. it doesn't work. Here is code:
<div controller='mainCtr'>
<span click='reset(destination)'>Reset</span>
<div class='floatleft' style='width:30%;margin-right:40px;'>
<smart-Googlemaps locationgoogle='destination.From'></smart-Googlemaps>
<label>From</label>
</div>
</div>
In the directive:
angular.module('ecom').directive('smartGooglemaps', function() {
return {
restrict:'E',
replace:false,
// transclude:true,
scope: {
locationgoogle: '='
},
templateUrl: 'components/directives/autocomplete/googlemap-search.html',
link: function($scope, elm, attrs){
var autocomplete = new google.maps.places.Autocomplete($(elm).find("#google_places_ac")[0], {});
google.maps.event.addListener(autocomplete, 'place_changed', function() {
var place = autocomplete.getPlace();
// $scope.location = place.geometry.location.lat() + ',' + place.geometry.location.lng();
// console.log(place);
$scope.locationgoogle = {};
$scope.locationgoogle.formatted_address = place.formatted_address;
$scope.locationgoogle.loglat = place.geometry.location;
$scope.locationgoogle.locationText = $scope.locationText;
$scope.$apply();
});
}
}
})
Here is html for directive:
<input id="google_places_ac" placeholder="Please enter a location" name="google_places_ac" type="text" class="input-block-level" ng-model='locationText'/>
The directive works fine, I create a isolated scope(locationgoogle) to pass the information I need to parent controller(mainCtr), now in the mainCtr I have a function calld reset(), after I click this,I need to clean up the input make it empty. How Can I do it?
One way to access the value of the model in your directive from a parent controller is to put that on the isolate scope too and use the two-way binding flag = like you've done with the locationgoogle property. Try this:
DEMO
html
<body ng-controller="MainCtrl">
<button ng-click="reset()">Reset</button>
<smart-googlemaps location-text="locationText"></smart-googlemaps>
</body>
js
app.controller('MainCtrl', function($scope) {
// need to define model in parent and pass to directive
$scope.locationText = {
value: ''
};
$scope.reset = function(){
$scope.locationText.value = '';
}
});
app.directive('smartGooglemaps', function() {
return {
restrict:'E',
replace:false,
// transclude:true,
scope: {
locationgoogle: '=',
locationText: '='
},
// ng-model="locationText.value"
template: '<input id="google_places_ac" placeholder="Please enter a location" name="google_places_ac" type="text" class="input-block-level" ng-model="locationText.value"/>',
link: function($scope, elm, attrs){
// implement directive googlemaps logic, set text value etc.
$scope.locationText.value = 'foo';
}
}
})
I have a collection of buttons that sort a list of items when clicked:
<div ng-controller="MainCtrl">
<sort-buttons target="filters.sort">
<sort-button></sort-button>
<sort-button></sort-button>
<sort-button></sort-button>
</sort-buttons>
</div>
I want the parent directive to save the results of the buttons to the $scope.filters.sort property on the MainCtrl controller via the target attribute, but how can I actually save to where the target attribute points to?
Here's what I have:
app.directive('sortButtons', [function(){
return {
restrict: 'E',
controller: function($scope, $element, $attrs) {
// this.foo() should save to target
this.foo = function(){
console.log('click');
};
}
}
}]).directive('sortButton', ['Config', function(Config) {
var basePath = Config.get().paths.base;
return {
restrict: 'AE',
replace: true,
require: '^sortButtons',
scope: {
label: '#',
orderBy: '&'
},
templateUrl: basePath + 'js/fantasy/templates/sort-button.htm',
link: function(scope, elem, attrs, ctrl) {
elem.on('click', function(){
ctrl.foo();
});
}
}
}]);
Try $eval on attr.target like
var data = $scope.$eval($attrs.target)
Or if your data is dynamic you can $watch the attr
var data = [];
$scope.$watch($attrs.target, function(newValue, oldValue){
data = newValue;
})
Also correct your controller injection like below, else if you will get error if you minified your source code.
controller: ['$scope','$element','$attrs', function($scope, $element, $attrs) {
var data = $scope.$eval($attrs.target)
this.foo = function(){
console.log('click');
};
}]
What I went with was removing the target attribute altogether, and instead broadcasting on the $rootScope.
Directive:
this.foo = function(){
$rootScope.$broadcast('sortButtons', {
predicate: 'foo',
reverse: false
});
};
Controller:
$rootScope.$on('sortButtons', function(event, data){
$scope.filters.sort = data;
});
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/
I have a directive defined like so:
angular.module('directives.myInput', [])
.directive('myInput', function($parse, $http, $sce){
return {
restrict: 'E',
template: '<input type="text" ng-model="searchStr" />',
controller: function($scope){
$scope.keyPressed = function(event){
$scope.showDropdown = true;
.
.
.
}
}
};
});
And then I have a button in html and directive above declared like so:
<div ng-controller="IndexCtrl">
<button ng-click="startNewLog()">Start</button>
<div ng-controller="ItemNewCtrl">
<myInput />
</div>
</div>
I want to change/initialize ng-model="searchStr" model on a button ng-click. How can I do that?
Thanks guys,
Jani
If I understand you right, first of all you need call child controller with $broadcast. Since we don't use isolate scope, we just call directive method from child controller:
[Short answer]
No isolate scope example
Demo 1 Fiddle
For isolate scope, I would map value to directive that listens on value change automatically:
Isolate scope example
Demo 2 Fiddle
[Full answer]
No isolate scope example
HTML
<div ng-controller = "IndexCtrl">
<button ng-click="startNewLog()">Start</button>
<div ng-controller="ItemNewCtrl">
<my-input></my-input>
</div>
</div>
JS
var app = angular.module('myModule', []);
app.controller('IndexCtrl', function ($scope) {
$scope.startNewLog = function(){
$scope.$broadcast('someEvent');
};
});
app.controller('ItemNewCtrl', function ($scope) {
$scope.$on('someEvent', function() {
$scope.callDirective();
});
});
app.$inject = ['$scope'];
app.directive('myInput', function(){
return {
restrict: 'E',
template: '<input type="text" ng-model="searchStr" />',
controller: function($scope){
$scope.searchStr;
$scope.keyPressed = function(event){
$scope.showDropdown = true;
}
},
link: function(scope, elm, attrs) {
scope.callDirective = function() {
scope.searchStr = 'callDirective';
};
}
};
});
Isolate scope example
HTML
<div ng-controller = "IndexCtrl">
<button ng-click="startNewLog()">Start</button>
<div ng-controller="ItemNewCtrl">
<my-input my-model='contInput'></my-input>
</div>
</div>
JS
var app = angular.module('myModule', []);
app.controller('IndexCtrl', function ($scope) {
$scope.startNewLog = function(){
$scope.$broadcast('someEvent');
};
});
app.controller('ItemNewCtrl', function ($scope) {
$scope.contInput = '';
$scope.count = 0;
$scope.$on('someEvent', function() {
$scope.contInput = 'hey mate';
});
});
app.$inject = ['$scope'];
app.directive('myInput', function(){
return {
restrict: 'E',
scope:{searchStr: '=myModel'},
template: '<input type="text" ng-model="searchStr" />',
controller: function($scope){
$scope.searchStr;
$scope.keyPressed = function(event){
$scope.showDropdown = true;
}
}
};
});