I've looked at more than 10 different directive questions and none have worked for me thus far. I'm trying to have my controller recognize site.selectedUnit has changed which updates selectedChannel in my custom directive. The first time works great, but after that the ng-click does not change the view info. The selectedChannel is registered and stored on my chatList controller, but the http requests are not called again.
If i click on a unit in site.html, the selectedChannel for the unit number changes in the chatList view, so I'm guessing the http requests are not being called again.
How do I add a listener to either the directive or my chatList controller so I can call the http requests again when selectedChannel changes?
Best I can tell from the code you've provided, you need to make a few changes
site.controller
Add a definition for $scope.site otherwise site.selectedUnit will only be available in the context that it is defined in the HTML via ng-click.
$scope.site = {
selectedUnit: null
};
chatList.directive
Remove the isolate scope and inject the selectedChannel property via bindToController so that it will be accessible in the controller.
.directive('chatList', function() {
return {
restrict: 'E',
bindToController: {
selectedChannel: '='
},
templateUrl: 'chatList.html',
controller: 'ChatListController',
controllerAs: 'chatList'
};
})
chatList.controller
Watch for changes to selectedChannel.id to make your $http.get() call
vm.tenants = [];
$scope.$watch(angular.bind(vm.selectedChannel.id, function() {
// make http.get call here
}))
You have other issues as well that are too many to list so here's a working plunker
Related
I have two controllers in my app TimelineController and ReservationModalController. I want to call TimelineController from ReservationModalController and pass variable value. I'm trying to that:
this is my app.js
$stateProvider
// Timeline
.state('timeline', {
url: '/timeline',
controller: 'TimelineController',
templateUrl: 'assets/partials/timeline.html',
params: {
operation: 'false'
}
})
this is TimelineController
$scope.operation = $state.current.params.operation;
and this is ReservationModalController :
$scope.edit = function() {
$modalInstance.close();
$state.go('timeline', {
operation: 'true'
});
};
At the first time variable is initialized. But when I click the button and ReservationModalController and it's method edit is called TimelineController and it's variable $scope.operation does not change.
Please help me what is wrong?
Probably, you should $broadcast to communicate between controllers here so they are not as tightly bound to each other. $rootScope.$broadcast is sending an event through the application scope.
Any children scope of that app can catch it using a simple: $scope.$on().
It is especially useful to send events when you want to reach a scope that is not a direct parent (like two controllers of which is none a parent for the other for example).
There's a fiddle that shows how to do this: https://jsfiddle.net/VxafF/
You could also try nesting your TimeLineController inside your ReservationModalController. That way you can use $scope inheritance to communicate between controllers.
Read this:http://www.angularjshub.com/examples/basics/nestedcontrollers/
I have a navbar directive which sits above ng-view. It utilises the $rootScope to trigger events to show buttons in certain views.
I am trying to add a button to the directive template which will switch a boolean in a controller for a particular view. The view shows a period of time and each period has a particular boolean that I want to switch from the directive.
The boolean value is saved in a local storage object which is initialized when each iteration of this particular view is loaded.
First, the value needs to be communicated to the directive so the button can display as being set to true or false. When the switch is toggled, the value of that boolean needs to make its way from the directive, through the $rootScope, to the controller and then be saved in the storage object.
When the view is changed, the whole process needs to repeat. The switch needs to be able to be switched on and off multiple times, obviously.
At present, I am emitting the value from the controller to the $rootScope and then listening for that value in the directive link function.
However, what is the best way to get that $rootScope value BACK into the controller. I tried setting up a $rootScope.$watch in the controller which appeared to work on any single page but when navigating between different time periods, the $rootScope value of the boolean was not resetting properly.
I tried resetting the value in the controller initialization as follows:
$rootScope.booleanValue = false;
but this didn't work.
I have also tried the following:
$scope.$on('$routeChangeSuccess', function (next, current) {
$rootScope.booleanValue = false;
});
but I can't get the whole cycle to work properly. It still seems as though the value of the property in the $rootScope is not resetting from the view before and is then carrying over when an adjacent pay period view is loaded.
I hope this makes sense. I will save you from too much code as I think the basic idea is here.
What you are trying to do is share state from your navbar directive (an isolate scope) and your view's controller. I recommend you use a factory provider service to share that state:
angular.module('myApp').factory('navbarState', function (){
return {started: false}
});
In your navbar directive, inject the service and store the state in that service:
angular.module('myApp').directive('navigationBar', [
'$rootScope',
'navbarState',
//'NavigationStackService',
//'NavigationBarService',
function ($rootScope, navbarState) {
function link(scope, element) {
scope.startEditMode = function(){
console.log("Edit clicked");
navbarState.started=true;
//NavigationBarService.hideNavigationEdit();
//NavigationBarService.showNavigationDone();
};
scope.finishEditMode = function(){
console.log("Done clicked");
navbarState.started=false;
//NavigationBarService.hideNavigationDone();
//NavigationBarService.showNavigationEdit();
};
}
return {
templateUrl: 'templates/navigation-bar.html',
restrict: 'E',
scope: {},
link: link
};
}
]);
In your view controller, retrieve the service, put it on the controller's scope, and use it in your template.
angular.module('myApp').controller('controller2', function(navbarState) {
console.log("view controller2 started");
var vm = this;
vm.navState = navbarState;
vm.message = "hello from ct2";
});
The DEMO on JSFiddle.
Suppose I have a module with a directive as follows (this is a rough not tested)
I need to implement 3 basic things
Configuration for the element that will appear
Event listeners that the base controller can use
Public methods that the base controller can call
angular.module("componentModule",[]) .directive("myComp",function(){
return{
replace:true,
template:'<h2>This is my component</h2>',
scope:{config= "#"},
link:function(scope,element,attr){
this.deleteElement = function(id){
//writing the code to delete this component
//This is a API function that the user can call to delete
}
if (!scope.config.visible){
//this is a configuration object for the element
this.visible(false)}
}
} })
then i have my base HTML like containing the directive call like below
<div myComm="first" config="eleConfig"></myComp>
<div myComm="second" config="newEleConfig"></myComp>
I have a separate controller for my base HTML as follows,
angular.module("baseApp",['componentModule'])
.controller('baseCtrl',function(){
$scope.eleConfig = {
visible:true,
delete:function(e){
//This is called if we call the delete method
}
}
//this is how the delete method is to be called
$scope.first.deleteElement();
})
Question
How to call the deleteElement() method in the baseCtrl as shown above (want to do it the same way KENDO UI does)
The pattern that angular uses is to expose the directive API to the scope. This is how ng-model and ng-form both expose ngModelController and ngFormController APIs.
Here is how I would do it:
angular.module("componentModule",[])
.directive("myComp",function($parse){
return{
replace:true,
scope: {
config: '&'
},
template:'<h2>This is my component</h2>',
controller: function($scope) {
//Directive API functions should be added to the directive controller here or in the link function (if they need to do DOM manipulation)
},
link:function(scope,element, attr, ctrl){
//add to directive controller
if(scope.config().visible) {
//element should be visible, etc.
}
ctrl.deleteElement = function(){
//if this function is called we want to call the config.delete method:
if(scope.config && scope.config.delete) {
//calling the scope.config() method returns the config object from the parent
scope.config().delete(element);
}
}
if(attr.myComp) {
//change to scope.$parent
scope.$parent[attr.myComp] = ctrl;
}
}
}
})
Assuming markup of:
<div my-comp="first" config="configObject"></div>
<div my-comp="second" config="configObject"></div>
In your base controller
$scope.first.deleteElement();
or
$scope.second.deleteElement();
would delete the appropriate element.
UPDATE:
I've updated the directive based on your updated question. You want to pass a config object into the directive. The best way to do that is with an & binding. If you use the & binding, you need to remember that the directive will create a new scope, and you have to attach the controller to $scope.$parent.
In your first requirement, you said you want to write the delete function in the directive, but in the case of KendoUI the actual delete(change) function implementation is done in the base controller and the delete(change) event triggered when the component value changes, which in turn calls the delete function defined in the base controller by the directive.
If you want to implement something like KendoUI does then look at this
link toplunker
Switch on the browser console to see the log. KendoUI component's change event happens automatically when the input element changes but in this case i manually triggered the delete event after 3 seconds.
I have a directive(parent-directive) containing a slider(mySlider), that on stop event, call an angular $resource service with 2 params and the service return an object.
Directives structure:
<parent-directive>
<div ui-slider="slider.options" ng-model="mySlider" id="my-slider">
<span child-directive-one></span>
<span child-directive-two></span>
<span child-directive-three></span>
<div>
<span child-directive-four></child-directive-four>
</div>
</parent-directive
Whenever the user drag the slider, the service is called with different params and retieve new result, based on it I need to update the child directives.
I have in mind three ways:
using ng-model for all child elements instead directives, binding them on the scope of a controller in parent-directive;
the second one, that I don't know how to do it, is to create a controller in the parent-directive, that send and receive data from the service and share it to child-directives in order to update them.
the last one is to to create a state variable in the service and update it using a controller like to point 1.(see it above) and use a $watch to supervise the variable state and when it's changed then update the child-directives.
How should I proceed?
Please have a look here to see a brief code:
http://jsfiddle.net/v5xL0dg9/2/
Thanks!
ngModel is intended for two way binding, i.e. controls that allow the user to interfere with the value. From the description, it seems they are display-only components. So I would advise against using the ngModel.
Normally child directives require their parent. This allows them to call methods on the parent controller. What you need is the opposite: the parent controller needs to call methods on the children. It can be done: the children call a registerChild() method, and the parent iterates all registered children when it needs to call them. I find this implementation cumbersome.
Services are globals/singletons. I would vote against tying the service implementation to the UI needs.
My advice looks like your implementation of option 3, but with the parent controller holding the data:
1) Place the data you want to share with the child directives in a member variable of the parent controller:
myApp.directive('parentDirective', ['myService', function(myService){
...
controller: function($scope) {
...
this.sharedThing = ...;
}
}]);
The sharedThing can be updated when the service returns new data, or any other time it is necessary.
2) Have the children require the parent (just like your option 2), and watch this property:
myApp.directive('childDirectiveOne', function() {
return {
...
require: 'parentDirective',
link: function(scope, elem, attrs, parentDirective) {
scope.$watch(
function() {
return parentDirective.sharedThing;
},
function(newval) {
// do something with the new value; most probably
// you want to place it in the scope
}
});
}
};
});
Depending on the nature of the data, a deep watch may be required.
I have scenario where I want to send a broadcast event in one controller and have the controller for a directive receive the message. The event is sent immediately on controller startup, and the issue is that because the directive is loading the view using templateUrl, it happens asynchronously, so the event is broadcast before the directive controller is intialised. This problem doesn't happen if the view is in the main page body, but I guess there could still be an issue with controller initialisation order.
I am using the following code in my main controller:
$rootScope.$broadcast("event:myevent");
I have reproduced the issue here: http://jsfiddle.net/jugglingcats/7Wf8N.
You can see in the Javascript console that the main controller is initialised before the controller for the directive, and it never sees the event.
So my question is whether there is a way to wait until all controllers are initialised before broadcasting an event?
Many thanks
I have created a working version. I actually feel that it is a very unclean way to do it, but I could not come up with something better: http://jsfiddle.net/7Wf8N/3/
What I did is this: In the directive I added some code which will increase a counter in $rootScope upon initialization. I use a counter because as you said, you want to wait for more than one controller:
$rootScope.initialized = ( $rootScope.initialized||0 ) +1;
In the "RegularCtrl" I added a watch on this counter and if the counter reaches the correct value (everything is initialized) I send the event:
$rootScope.$watch('initialized', function() {
if ( $rootScope.initialized == 1 ) {
$rootScope.$broadcast("event:myevent");
}
});
Are you ng-view? If so, you have the $viewContentLoaded event available. This will fire after all the dom is loaded.
http://docs.angularjs.org/api/ngRoute.directive:ngView
function MyCtrl($scope, $rootScope) {
$scope.$on('$viewContentLoaded', function() {
$rootScope.$broadcast('event:myevent');
});
}
If you aren't using ng-view, you could just set a variable and use data-binding to your directive.