Use Directive scope in Controller - angularjs

I built a directive that has checkboxes next to labels, inside a jQuery UI Accordion:
<ul class="checkbox-grid employee-info-tabs">
<li ng-repeat="column in columnsData">
<div class="styleAvailableColumns">
<input type="checkbox" ng-model="column.Selected" />
<label class="list-columns">{{ column.ColumnDisplayName }}</label>
</div>
</li>
</ul>
In my Controller, I want to be able to save the selected choices the user makes inside the directive, but I'm not sure how.
Here's my directive:
angular.module('component.column', [])
.directive('uiAccordion', function ($timeout, Columns, $location) {
return {
scope: {
columnsData: '=uiAccordion'
},
templateUrl: '/scripts/app/directives/test.html',
link: function (scope, element) {
var generateAccordion = function () {
$timeout(function () {
$(element).accordion({
header: "> div > h3",
collapsible: true,
active: 'none'
});
});
}
var loc = $location.absUrl();
var reportId = loc.substring(loc.lastIndexOf('/') + 1);
Columns.getAll(reportId).then(function (data) {
scope.columnsData = data;
generateAccordion();
}
Here's how I use the directive in my view <div ui-accordion="accordionData"></div>
I tried using scope: { '=' } but got Expression 'undefined' used with directive 'uiAccordion' is non-assignable!.
I've done some other googling, but I'm not 100% on the 'correct' direction on how to get this accomplished. If I can provide any other information, please let me know.

Set your directive scope to:
scope: {
columnsData: '='
},
Since you want the controller to maintain that data, your controller should have a reference to $scope.columnsData.
Then, on the view which is using the controller, you can feed that into the directive like so:
<div ui-accordion columns-data="columnsData"> </div>
Here's an example of your controller:
angular
.module('...')
.controller('myCtrl', ['$scope', function($scope) {
$scope.columnsData = "abcd123"
}]);

Try using your directive as:
<div ui-accordion="controllersColumnsData"></div>
where controllersColumnsData is a collection you can iterate in your controller whose items will have ColumnDisplayName and Selected properties set from your directive.

Related

isolate controller to only controller specific scopes

So I have a directive and inside the directive view (html) I put a controller however its affecting the rest of the viewModel (vm). What's the best way to isolate a controller to only control specific viewModel?
That's the structure of the view model and directive, I thought ng-controller="ctrl as vm" would only find vm within the class of "controller" but instead its finding every vm on the page.
Directive:
var directive = {
templateUrl: '/Content/app/core/scaffolding/views/popup.html',
restrict: 'A',
link: function (scope, element, attributes) {
console.log('something')
}
};
view:
<div class="directive">
<div class="moreVm">
</div>
<div class="controller" ng-controller="ctrl as vm">
<button ng-click="vm.find()"></button>
</div>
</div>
I tried making "ctrl as jvm" but still the same haha, its just a guess.
<div class="controller" ng-controller="ctrl as jvm">
<button ng-click="jvm.find()"></button>
</div>
Try this.
var directive = {
restrict: "A",
scope: true,
bindToController: {},
controller: "ctrl as vm",
templateUrl: "/Content/app/core/scaffolding/views/popup.html"
};
I've come up with an example using directives which may be of some help - Plunker
As you can see clicking the button in directive2 does not set the value of $scope.aValue in directive1.
JS
var app = angular.module('plunker', [])
.directive("directive1", function accountDir() {
return {
restrict: "EA",
templateUrl: "directive1.html",
scope: {},
controller: function ($scope) {
$scope.$watch("aValue", function(newValue) {
console.log(newValue);
})
}
};
}
)
.directive("directive2", function accountDir() {
return {
restrict: "EA",
templateUrl: "directive2.html",
scope: {},
controller: function ($scope) {
$scope.setAValue = function () {
$scope.aValue = 42;
console.log($scope.aValue);
}
}
};
}
);
Markup
<body>
<directive1></directive1>
</body>
directive1.html
<directive2></directive2>
directive2.html
Directive2
<br>
<button ng-click="setAValue()">Set a value</button>
If I not guess wrong,you want do that when ctrl as different names, the directive console.log different value? or in vm but the value within directive is different with out of the directive?
if you want first ,you just make two controller and then set different value;
controller('ctrl1',function(){ this.name});
controller('ctrl2',function(){ this.name});
else want two
directive('myDir',function(){ return {restrict:'AE',scope:{},controller:function(){this.name='haha'}}})
and now the value is isolate with outer

AngularJs: Why does scope inside my directive not update when async data arrives?

I have a directive with isolate scope which takes a scope variable by reference
angular.module('myApp')
.directive('myDirective', function() {
return {
scope: {
items: '='
},
templateUrl: 'template.html',
replace: true,
controller: 'myDirectiveCtrl',
controllerAs: 'ctrl'
};
})
.controller('myDirectiveCtrl', function($scope) {
this.items = $scope.items;
});
This is passed in like so:
<div my-directive items='items'></div>
In the external controller data is asynchronously loaded and the scope items passed into the directive updated:
angular.module('myApp', [])
.controller('myCtrl', function($scope) {
$scope.setItems = function() {
$scope.items = [
'Here',
'There',
'Everywhere'
];
};
});
When the data is loaded, the scope outside my directive updates, but inside it doesn't
My html:
<div my-directive items='items'></div> <!-- this doesn't update -->
Outside directive
<ul ng-repeat='i in items'> <!-- this does update -->
<li>{{i}}</lu>
</ul>
<button ng-click="setItems()">Set items</button>
How can I get my scope inside my directive to update? Do I
Plunker here
When Angular first runs your directive's controller function, your $scope.items === undefined, so when you do this.items = $scope.items, your this.items === undefined too.
That's it. After that there is nothing that changes this.items.
This is unlike $scope.items. $scope.items is two-way bound to the outer scope, so whenever Angular detects a change externally, it sets the isolated scope variable.
The easiest way (and most suitable, in my opinion) is to use the $scope property directly in the directive:
<div>
Inside directive
<ul ng-repeat="i in items">
<li>{{ i }}</li>
</ul>
</div>
If you want to use your controller as ViewModel instead of scope (I don't know why you would), you could do:
$scope.$watchCollection("items", function(newVal, oldVal) {
ctrl.items = newVal;
});
EDIT:
In Angular 1.3 you can also do bindToController: true in the directive's definition, so that the controller property "items" will get the two-way binding that $scope.items gets. Then, you don't even need to do this.items = $scope.items;:
Your forked plunker to illustrate.
If it is isolated scope you cannot change what is inside the directive after you create a separate variable inside the directive controller.
Here is the updated plunker which removes the controller for the directive.
'use strict';
angular.module('myApp')
.directive('myDirective', function() {
return {
scope: {
items: '='
},
templateUrl: 'template.html',
replace: true
};
});
Try putting your items in an object. See this example at Plunker
index.html
<div my-directive items='allItems'></div>
Outside directive
<ul ng-repeat='i in allItems.items'>
<li>{{i}}</lu>
</ul>
<button ng-click="setItems()">Set items</button>
</div>
directive.js:
'use strict';
angular.module('myApp')
.directive('myDirective', function() {
return {
scope: {
items: '='
},
templateUrl: 'template.html',
replace: true,
controller: 'myDirectiveCtrl',
controllerAs: 'ctrl'
};
})
.controller('myDirectiveCtrl', function($scope) {
this.items = $scope.items;
});
template.html:
<div>
Inside directive
<ul ng-repeat="i in ctrl.items.items">
<li>{{ i }}</li>
</ul
</div>
script.js:
angular.module('myApp', [])
.controller('myCtrl', function($scope) {
$scope.allItems={};
$scope.setItems = function() {
$scope.allItems.items = [
'Here',
'There',
'Everywhere'
];
};
});
There is a better explanation here:
Angular - ngModel not updating when called inside ngInclude

Angular UI Bootstrap modal inside ngRepeat

I am making an app, where I have a lot of input fields. Those input fields are generated from JSON object array field with AngularJS ngRepeat directive and have a button next to them which open an Angular UI Bootstrap modal to edit this value in a bigger textarea. I cannot figure out how to reference this model property to Angular UI Bootstrap so that I can save the changes made in modal. Since this functionality is needed in multiple views, I turned it into a service.
I have made a plunker to illustrate my problem.
http://plnkr.co/edit/ZrydC5UExqEPvVg7PXSq?p=preview
Currently in plunker example modal contains textarea, but I will actually need to use Text-Angular directive, because those fields contain some HTML markup and I would be easier for users to edit values with this nice addon.
TextAngular
EDIT: Please, if you are taking time to answer, you might aswell take a little more time to edit my plunker example to show exactly how your solution would look like, because seems that everyone who tries to help me, think they know the solution, but in reality it doesn't work :( Thanks!
I personally like to decorate my $scope with the services (i.e. $scope.modalService = ModalService;), so I understand the source of the logic. In the ng-repeat you then pass the value item into the method call:
<div class="input-group">
<input class="form-control" ng-model="value.value">
<span class="input-group-addon" ng-click="modalService.openTextEditModal(value)">
<span class="glyphicon glyphicon-align-justify"></span>
</span>
</div>
The modal service and modal template would then reference the object, in this case a clone of the object to help with state management, not the text:
app.factory('ModalService', function($modal) {
return {
openTextEditModal: function(item) {
var modalInstance = $modal.open({
templateUrl: 'modal.html',
backdrop: 'static',
controller: function($scope, $modalInstance, $sce, item) {
var clone = {};
angular.copy(item, clone);
$scope.clone = clone;
$scope.close = function() {
$modalInstance.dismiss('cancel');
};
$scope.save = function() {
angular.extend(item, clone);
$modalInstance.close();
};
},
size: 'lg',
resolve: {
item: function() {
return item;
}
}
});
}
};
});
With the corresponding modal template changes:
<div class="modal-header">
<h3 class="modal-title">Edit text</h3>
</div>
<div class="modal-body">
<textarea class="form-control" ng-model="clone.value"></textarea>
</div>
<div class="modal-body">
<button class="btn btn-warning" ng-click="close()">Close</button>
<button class="btn btn-success pull-right" ng-click="save()">Save</button>
</div>
It might be easier to make a controller for your modal and pass in the objects that you need from your scope. Those will be passed by reference so changes to them will update the scope of your parent scope. Something like this in your MainCtrl :
var modalInstance = ModalService.open({
templateUrl: 'modal.html',
controller: 'YourModalController',
size: 'lg',
resolve: {
text: function () {
return $scope.editText;
}
}
});
modalInstance.result.then(function () {
});
And then in your modal controller:
app.controller('YourModalController', ['$scope', '$modalInstance', 'text', function YourModalController($scope, $modalInstance, text) {
$scope.text = text;
$scope.close = function() {
$modalInstance.dismiss('cancel');
};
$scope.save = function() {
$modalInstance.close($scope.text);
};
}]);
And if you want it to be reusable so you do not have to duplicate the modal instance code in the parent controller you could make that a directive.
You can return the promise and then handle the success callback in the controller.
In the openTextEditModal function, return modalInstance.result;.
Then, in the controller you can do this:
$scope.editText = function(text){
ModalService.openTextEditModal(text).then(function(newText){
console.log(newText);
$scope.text = newText; // do something with the new text
});
};

Directive not rendering after $http request

I began working with angularjs and have a problem.
app.js (just the relevant route)
$routeProvider.when('/search', {templateUrl: 'partials/search-results.html', controller: 'SearchController', title: 'Product Search'});
search-results.html
<div product-list></div>
index.html
<div class="page_content_offset p_bottom_0">
<div class="container mh600">
<div ng-view></div>
</div>
</div>
product-list.html
<div ng-repeat="item in items">
<p>{{item.address_components[0].long_name}}</p>
</div>
controller.js just relevant code:
$scope.search = function() {
$scope.loading = true;
$scope.items = {};
ProductSearchService.searchItems($scope.term).then(function(data) {
$scope.items = data;
$scope.loading = false;
});
};
directives.js (just relevant code)
directive('productList', function() {
return {
restrict: 'EA',
templateUrl: 'partials/list/product-list.html'
};
My Problem now is:
The ProductSearchService loads the data. But the directive not rendering as expected.
If i move the directive code from search-results.html to my index.html like this:
<div class="page_content_offset p_bottom_0">
<div class="container mh600">
<div product-list></div>
<div class="clearfix"></div>
</div>
</div>
evrything is rendered nicely. So i think i included my directive wrongly or forgot something important.
I've created a plunkr similar to my setup:
http://plnkr.co/edit/60dvyFnz74yrsK12J2J4?p=preview
Everything works fine in that.
In my local application i changed the "product-list.html" model property access to this
<div ng-repeat="item in $parent.items">
<p>{{item.address_components[0].long_name}}</p>
</div>
Update controllers.js
angular.module('myApp.controllers', [])
.controller('SearchController', ['$scope','$http','ProductSearchService', function($scope, $http, ProductSearchService) {
$scope.items = {};
$scope.search = function() {
$scope.loading = true;
ProductSearchService.searchItems($scope.term).then(function(data) {
$scope.items = data;
$scope.loading = false;
});
};
}]);
Update 2:
I now have a plnkr where the problem can be shown:
http://plnkr.co/edit/QgU1cf3JeJYKu6Fkl9zv?p=preview
You did not set any scope attribute to your directive.
This means that your directive use the defining/containing scope as the directive own scope.
Thus , the scope that is used in product-list.html is the same as the one used by search-result.html (and so by the SearchController).
The only reason , you could have to use the $parent.items would be if you specified scope:{}, in your directive.You can even test it in your plunker (I did).
Can you double check the directive scope attribute ?
Thx
directive('productList', function() {
return {
restrict: 'EA',
replace: true,
templateUrl: '<section data-ng-include=" 'partials/list/product-list.html' "></section>'
};
});
try this one as the directive :)

Angularjs Two way binding issues

I'm trying to create a dynamic directive that will receive his binding options from attributes.
This is my controller view:
<div ng-controller="OtCtrl">
<div ng-include src="'/client/views/openTradeView.html'"></div>
</div>
my View:
<div>
{{name}}
<div swings binding="Risk" title="Risk"></div>
<div swings binding="Amount" title="Amount"></div>
</div>
This is my directive View:
<div>
{{title}}
-
{{amount}}
+
</div>
This is my directive:
app.directive("swings", function () {
return {
replace: true,
scope: {
title : '#title',
amount: '=binding',
extra: '=bindingExtra'
},
resctrict: "A",
controller: function($scope){
$scope.minus = function (event, binding) {
$scope.amount -= 1;
console.log(binding)
}
$scope.plus = function (event) {
$scope.amount += 1;
}
},
templateUrl: '/client/views/swingsDirectiveView.html'
}
});
And finally my Controller:
app.controller("OtCtrl", ['$scope', function ($scope) {
$scope.Amount = 400;
$scope.Risk = 100;
setInterval(function(){
console.log($scope.Risk)
},2000);
}]);
I know that when I use ng-view, angularjs creates new scope. My issue that when I click plus or minus, the amount in the directive updates but the Risk model in the controller not, but on first load directive takes the risk init value and set the amount.
How can I fix this issue.
Thanks
I would suggest creating a model property in your controller like so:
app.controller("OtCtrl", ['$scope', function($scope) {
$scope.model = {
Amount: 400,
Risk: 100
}
}]);
Then, bind it to your directive via:
<div swings binding="model.Risk" title="model.Risk"></div>
What is likely happening is that scope inheritance is creating a copy of Amount and Risk (since they're primitives); whereas creating an object like this causes the reference to get copied, meaning the scope hierarchy shares the same data.

Resources