How $watch controller method in directive - angularjs

I have CKEDITOR, a controller and a directive. This is the method of the controller which should add ng-show and remove ng-hide from the <span>:
$scope.deleteEditorAndSave = () ->
angular.forEach CKEDITOR.instances, (editor) ->
id = editor.element.getAttribute('data_id')
text = editor.getData()
field = editor.element.getNameAtt()
html_field = $(editor.element.$)
html_field.val(text)
showing = editor.element.getAttribute('ng-show')
console.log showing
$timeout( ->
html_field.trigger('input')
$scope.save_field(text, id, field, 'no_call')
editor.destroy()
angular.forEach allClaims(), (claim) ->
console.log "CLAIM", claim
claim[showing.split('.')[1]] = false
)
And I want to call this method from the directive. When I try to do this, the <span> element doesn't re-render. Does anyone know how this can be solved?
Thanks in advance.

I think the recommended practice is to resolve DOM in the directives, once thing that can help you is a directive with isolated scope, this will allow you to use & in your directives scope like this:
<body ng-controller="MainCtrl">
<div user-name="" callme="enableEditor()"></div>
<div>
add
</div>
<script>
var myApp = angular.module('myApp', []);
myApp.controller('MainCtrl', ['$scope', function ($scope) {
$scope.enableEditor = function() {
alert("123");
};
}]);
myApp.directive("userName", function() {
return {
restrict: "A",
scope: {
value: "=userName",
callme:"&"
},
template: '<div class="click-to-edit">' +
'Edit' +
'</div>'
};
});
The attribute callme="enableEditor()" is used to pass the method to the scope directive, the directive scope uses & to indicate it is method callme:"&". Another example:
method2="someMethod()" like
scope: {
value: "=userName",
callme:"&",
method2:"&"
},template: '<div class="click-to-edit">' + 'Edit' + 'Save' + '</div>'
This is the recommended way to communicate directives with controllers.

Related

How to $compile angular template to make it work in multiple controllers with aliases?

I have a custom directive that simply $compiles a template into another.
.directive('staticInclude', function($http, $templateCache, $compile) {
return function(scope, element, attrs) {
var templatePath = attrs.staticInclude;
//
$http.get(templatePath, {
cache: $templateCache
}).success(function(response) {
var contents = element.html(response).contents();
$compile(contents)(scope);
});
};
});
I use it like:
<div static-include="components/campaign/details.html"></div>
Because I'm using aliases for the controller (using angular UI router), all model in any of the templates are like:
<p>Delivery Time: <span class="text-medium">{{CtrlAlias.campaign.newsletter.sentDate | date:CtrlAlias.currentUser.params.settings}}</span></p>
How do I make this directive work in multiple templates where CtrlAlias changes?
I tried changing $compile(contents)(scope); into $compile(contents)(scope.newCtrlAlias);
Any ideas?
When you $compile and then link, you are free to provide your own scope against which the compiled content is linked. That means that you can have the template content refer to some arbitrary ViewModel name, say vm:
<p>Delivery Time: <span>{{vm.campaign.newsletter.sentDate}}</span></p>
And link against a scope that has vm property:
var scope = { vm: {...} }
It actually might be even useful to use an isolate scope for your compiled content, to make sure that you aren't assuming an existence of scope variables that may or may not be there when the content is linked:
.directive('staticInclude', function($templateRequest, $compile) {
return {
link: function(scope, element, attrs){
var alias = attrs.alias || 'vm';
var templatePath = attrs.staticInclude;
var newScope = scope.$new(true); // isolate scope
newScope.vm = scope[alias];
// $templateRequest is essentially $http with $templateCache
$templateRequest(templatePath)
.then(function(html){
$compile(html)(newScope, function cloneAttachFn(clone){
element.empty();
element.append(clone);
});
});
}
};
});
Then usage is like so:
<div ng-controller="MainCtrl as main">
<div static-include="components/campaign/details.html" alias="main">
</div>
</div>
Really not sure I understand why you would need to use this so it's not easy to answer. However, one possible solution could be to wrap the template in a <div> to which you can append the desired controller information. It's a bit gross but it might work for you. You would have to pass in the controller name and it's alias but you could perhaps add that to your $state's data properties and access them from that but again it all seems a bit hacky.
DEMO
app.directive('staticInclude', function($http, $templateCache, $compile) {
return {
scope: {
ctrlName : '#',
alias : '#'
},
link: link
};
function link(scope, element, attrs) {
var templatePath = attrs.staticInclude;
$http
.get(templatePath, {
cache: $templateCache
})
.success(function(response) {
var ctrlStr = scope.ctrlName + ' as ' + scope.alias,
template = '<div ng-controller="' + ctrlStr + '" >' + response + '</div>',
contents = element.html(template).contents();
$compile(contents)(scope);
});
};
});

How to set the dynamic controller for directives?

Talk is cheap, show my codes first:
HTML:
<div add-icons="IconsCtrl">
</div>
directive:
angular.module('attrDirective',[]).directive('addIcons', function($compile){
return {
restrict : 'A',
controller : "IconsCtrl"
},
link : function (scope, elem , attrs, ctrl) {
var parentElem = $(elem);
var icons = $compile("<i class='icon-plus' ng-click='add()'></i>)(scope);
parentElem.find(".accordion-heading").append(icons);
},
}
});
controller:
function IconsCtrl($scope){
$scope.add = function(){
console.log("add");
};
}
now it works, when i click the plus icon, browser console output "add".
but i want to set the controller into the directive dynamically,like this:
HTML:
<div add-icons="IconsOneCtrl">
</div>
<div add-icons="IconsTwoCtrl">
</div>
Controller:
function IconsOneCtrl($scope){
$scope.add = function(){
console.log("IconsOne add");
};
}
function IconsTwoCtrl($scope){
$scope.add = function(){
console.log("IconsTwo add");
}
}
directive likes :
angular.module('attrDirective',[]).directive('addIcons', function($compile){
return {
restrict : 'A',
controller : dynamic set,depends on attrs.addIcons
},
link : function (scope, elem , attrs, ctrl) {
var parentElem = $(elem);
var icons = $compile("<i class='icon-plus' ng-click='add()'></i>)(scope);
parentElem.find(".accordion-heading").append(icons);
},
}
});
how to achieve my goal? thanks for your answer!
Now it is possible with AngularJS. In directive you just add two new property called
controller , name property and also isolate scope is exactly needed here.
Important to note in directive
scope:{}, //isolate scope
controller : "#", // # symbol
name:"controllerName", // controller names property points to controller.
Working Demo for Setting Dynamic controller for Directives
HTML Markup :
<communicator controller-name="PhoneCtrl" ></communicator>
<communicator controller-name="LandlineCtrl" ></communicator>
Angular Controller and Directive :
var app = angular.module('myApp',[]).
directive('communicator', function(){
return {
restrict : 'E',
scope:{},
controller : "#",
name:"controllerName",
template:"<input type='text' ng-model='message'/><input type='button' value='Send Message' ng-click='sendMsg()'><br/>"
}
}).
controller("PhoneCtrl",function($scope){
$scope.sendMsg = function(){
alert( $scope.message + " : sending message via Phone Ctrl");
}
}).
controller("LandlineCtrl",function($scope){
$scope.sendMsg = function(){
alert( $scope.message + " : sending message via Land Line Ctrl ");
}
})
Your case you can try this below code snippets.
Working Demo
HTML Markup :
<div add-icons controller-name="IconsOneCtrl">
</div>
<div add-icons controller-name="IconsTwoCtrl">
</div>
Angular Code :
angular.module('myApp',[]).
directive('addIcons', function(){
return {
restrict : 'A',
scope:{},
controller : "#",
name:"controllerName",
template:'<input type="button" value="(+) plus" ng-click="add()">'
}
}).
controller("IconsOneCtrl",function($scope){
$scope.add = function(){
alert("IconsOne add ");
}
}).
controller("IconsTwoCtrl",function($scope){
$scope.add = function(){
alert("IconsTwo add ");
}
});
This is how it is done:
Inside your directive element all you need is an attribute which gives you access to the name of the controller: in my case my card attribute holds a card object which has a name property. In the directive you set the isolate scope to:
scope: { card: '=' }
This isolates and interpolates the card object to the directive scope. You then set the directive template to:
template: '',
this looks to the directive's controller for a function named getTemplateUrl and allows you to set the templateUrl dynamically as well. In the directive controller the getTemplateUrl function looks like this:
controller: ['$scope', '$attrs', function ($scope, $attrs) {
$scope.getTemplateUrl = function () { return '/View/Card?cardName=' +
$scope.card.name; }; }],
I have an mvc controller which links up the proper .cshtml file and handles security when this route is hit, but this would work with a regular angular route as well. In the .cshtml/html file you set up your dynamic controller by simply putting as the root element. The controller will differ for each template. This creates a hierarchy of controllers which allows you to apply additional logic to all cards in general, and then specific logic to each individual card. I still have to figure out how I'm going to handle my services but this approach allows you to create a dynamic templateUrl and dynamic controller for a directive using an ng-repeat based on the controller name alone. It is a very clean way of accomplishing this functionality and it is all self-contained.
1- you don't need to use: var parentElem = $(elem); as elem is a jquery element. This is similar to: $($('#myid'))
2- you can not dynamically assign a controller, because directive controller is instantiated before the prelinking phase.
The directive controller has access to attrs, so you can dynamically choose which internal function (functions inside your controller) according to the value of your attrs['addIcons']
ps. note attrs['addIcons'] is camel naming.

Setting up catching event in directive

I have a directive for flashmessages
'use strict';
angular.module('diplomovaPraceFrontendApp')
.directive('flashMessages', () ->
directive:
restrict: 'E'
replace: true
template: '<div ng-repeat="m in messages" id="flash-messages">' +
'<div class="alert {{m.level}}">' +
'<span class="">{{m.text}}</span>' +
'</div>' +
'</div>'
controller: ($scope, $rootScope) ->
$rootScope.$on('flash:message', (_, messages, done) ->
$scope.messages = messages
done()
)
)
and when I call in my controller $rootScope.$emit('flash:message', messages, someFunction); it isn't caught by $rootScope.$on() set up in directive, although if I put this in application.run() it works correctly.
Any ideas what I'm missing? Thanks for any advice
I have edited the question:
I do of course use an shared service, here's my code http://pastie.org/private/es25rvo0zvejuw9yx3acja (sorry, gist.github.com seems to be broken for me atm)
I was following this tutorial http://chiragchamoli.com/post/61277736964/building-a-flash-message-with-angular-js
Though it seems it doesn't call the directive at all, since replace is set to true and I still see <flash-messages> in code inspector.
Plunkr version: http://plnkr.co/edit/buRaguEyTMuhUM9Z4Jsw?p=preview
I already gave a fix in #angularjs earlier, but here it is for posterity:
http://plnkr.co/edit/Fb9FYSXgU0t93w7i2B8q?p=preview
The problem is that MainCtrl is instantiated before the directive, so the $scope event gets fired before the directive sets a listener on $scope, so the directive never gets the event listened to here.
The problem is that your non directive controller function is called before the directive controller. Thus, the message is being sent before the the directive has registered for alerts.
A simple solution to this would be, instead of using events, use a shared service. Services are singletons, so any state is shared between all usages of that service. Using a service only makes sense if your all of your flashMessage directives need a shared state. If this solution does not fit your needs, help me better understand your requirements.
Working plunker
Javascript:
var app = angular.module('plunker', [])
.controller('MainCtrl', function ($scope, alertsService) {
alertsService.add({
text: 'I am an alert',
level: 'high'
});
})
.service('alertsService', function () {
this.alerts = [];
this.add = function (message) {
this.alerts.push(message);
}.bind(this);
})
.directive('flashMessages', function (alertsService) {
return {
restrict: 'E',
replace: true,
template: '<div ng-repeat="m in messages" id="flash-messages">' +
'<div class="alert {{m.level}}">' +
'<span class="">{{m.text}}</span>' +
'</div>' +
'</div>',
scope: true,
link: function ($scope) {
$scope.messages = alertsService.alerts;
}
};
});
HTML:
<body ng-controller="MainCtrl">
<flash-messages></flash-messages>
</body>

Can I require an adjacent directive?

I have two element-level directives, a search box and a search results. My markup is something like this (simplified):
<catalogue-search-box query="{{query}}">
<catalogue-search-results></catalogue-search-results>
I'm trying to access the search box controller from the search results directive, but the documentation suggests that in the directive's require property I can only find controllers on the same element or on the parent element. Is there a way to find controllers on adjacent elements?
After you comments here is how I would do it: use an object to hold all your state and pass it to both directives. Demo plunker
HTML
<body ng-controller="MySearchController">
<search-box search="mySearch"></search-box>
<search-results search="mySearch"></search-results>
</body>
JS
var search = angular.module('search', []);
//simulated service
search.service('Search', ['$timeout', '$q', function($timeout, $q) {
return {
findByQuery : function(query) {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve([query + ' result1', query + ' result2']);
console.log('resolved query ' + query);
}, 2000);
return deferred.promise;
}
};
}]);
search.controller('MySearchController', ['$scope', function($scope) {
$scope.mySearch = {
query : ''
}
}]);
search.controller('SearchBoxCtrl', ['$scope', 'Search', function($scope, Search) {
$scope.execute = function(search) {
console.log(search);
if(search.query && search.query.length > 3 && !search.running) {
search.running = true;
search.promise = Search.findByQuery(search.query).then(function(val) {
search.results = val;
});
}
};
}]);
search.directive('searchBox', function(){
return {
restrict: 'E',
scope : {
search : '='
},
controller: 'SearchBoxCtrl',
template : '<div ng-hide="search.results">Query: <input type="text" ng-model="search.query" ng-disabled="search.running"></input> <button ng-click="execute(search)" ng-disabled="search.running">Search</button></div>',
replace: 'true'
};
});
search.controller('SearchResultsCtrl', function(){
});
search.directive('searchResults', function(){
return {
restrict: 'E',
scope : {
search : '='
},
controller: 'SearchResultsCtrl',
template : '<div ng-show="search.results"><div ng-repeat="result in search.results">{{result}}</div></div>',
replace: true,
link : function(scope, element, attrs, ctrl){
}
};
});
PS:
Don't use p tags in directive templates as the root node. The html parser reports 2 nodes if you have p child nodes and angular has a requirement for a single root node.
You can further use the promise in the controller to register other functions to execute when the results come in.
One way I've been experimenting with since the question is having some kind of controller directive i.e.
<catalogue-search>
<catalogue-search-box query="{{query}}">
<catalogue-search-results></catalogue-search-results>
</catalogue-search>
I can then access the "controller directive" this using the parent (^) modifier in my require statement. Each directive can then talk to each other via the controller directive.
Does this seem sensible or is it overcomplicating things?

ng-repeat wont work in directive template

I am trying to use ng-repeat in directive template like this:
JS
var module = angular.module("myModule", []);
module.directive('direct', function ()
{
return {
restrict: 'E',
controller: "directController",
// This HTML will replace the direct directive.
replace: true,
transclude: true,
scope: {title: "#"},
template:
'<div>'
// print: this is a title, which is correct
+ '{{title}}'
+ '<br>'
// print: ["1","2","3"], which is correct
+ '{{list}}'
+ '<br>'
// doesn't print anything, why ?!
+ '<div ng-repeat="l in list">{{l}}</div>'
+ '</div>',
link: function (scope, element, attrs, controller)
{
// scope has list in it
console.log(scope);
}
}
});
module.controller('directController', ["$scope", function ($scope)
{
$scope.list = ["1", "2", "3"];
}]);
angular.bootstrap($('html'),['myModule']);
HTML
<direct title="this is a title"></direct>
result HTML
<div>
this is a title
<br>
["1","2","3"]
<br>
</div>
As shown above, ng-repeat doesn't print 'list',
while printing list or logging it, it works fine, why :( ??
Update:
The problem was using the angular bootstrap function to register the module, since I didn't use the ng-app="myModule" directive.
If I use the ng-app="myModule" directive or inject to the var module = angular.module("ng"); module it should do the trick.
Be sure to wrap the angular bootstrap in the document ready function.
$(document).ready(function() {
angular.bootstrap($('html'),['myModule']);
});
Checkout here: http://jsfiddle.net/CRWAv/
The Documention says that you have to do that on manual bootstrap:
angular.element(document).ready(function() {
angular.bootstrap(document, ['myModule']);
});

Resources