Apply Angular Controller to a Template - javascript

So, I'm not sure what it is I'm asking, but I want to achieve this:
Index.html:
<div ng-view>
</div>
<script>
angular.module('myApp', ['ngRoute'])
.config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/', { controller: "HomeController", templateUrl: '/Partials/Home/Dashboard.html' });
$routeProvider.otherwise({ redirectTo: '/' });
}]);
</script>
Home/Dashboard.html:
<h2 class="page-header">{{welcome}}</h2>
<!-- Insert my reusable component here -->
My reusable component would reside in MyComponent/Component.html and have the controller myApp.component.controller associated with it.
So effectively I want to drop in the resuable component into the page and bind it to my controller. So I got as far as having this:
.directive('MyComponent', function() {
return {
restrict: 'E',
scope: {
},
templateUrl: '/MyComponent/Component.html'
};
});
So how do I now bind my controller to it? Do I do this:
.directive('MyComponent', function() {
return {
restrict: 'E',
resolve: function () {
return /* resolve myApp.component.controller */;
},
templateUrl: '/MyComponent/Component.html'
};
});

When a directive requires a controller, it receives that controller as the fourth argument of its
link function.
.directive('MyComponent', function() {
return {
restrict: 'E',
controller: 'MyController', // attach it.
require: ['MyController','^ngModel'], // required in link function
templateUrl: '/MyComponent/Component.html',
link: function(scope, element, attrs, controllers) {
var MyController = controllers[0];
var ngModelCtlr = controllers[1];
///...
}
};
});
The ^ prefix means that this directive searches for the controller on its parents (without the ^ prefix, the directive would look for the controller on just its own element).
Best Practice: use controller when you want to expose an API to other directives. Otherwise use link.

You can assign controllers to directives:
.directive('MyComponent', function() {
return {
restrict: 'E',
controller : 'HomeController',
templateUrl: '/MyComponent/Component.html'
};
});

So I just want to clarify a few things up here.
/MyComponent/Component.html:
<h2>{{welcome}}</h2>
/mycomponent.directive.js:
.directive('MyComponent', function() {
return {
restrict: 'E',
controller : 'ComponentController',
templateUrl: '/MyComponent/Component.html'
};
});
the above bound like this in index.html:
<h2>{{welcome}}</h2> <!-- this is from ng-controller = HomeController -->
<my-component></my-component> <!-- this is scoping from ng-controller = ComponentController -->
This generates the result
<h2>Hello from MyComponent</h2>
<h2>Hello from MyComponent</h2>
It appears that the ComponentController has taken over the entire scope. I then changed the directive to this:
.directive('MyComponent', function() {
return {
restrict: 'E',
// controller : 'ComponentController',
templateUrl: '/MyComponent/Component.html'
};
});
And changed the index.html to this:
<h2>{{welcome}}</h2> <!-- this is from ng-controller = HomeController -->
<div ng-controller="ComponentController">
<my-component></my-component> <!-- this is scoping from ng-controller = ComponentController -->
</div>
This gave the correct output:
<h2>Welcome from HomeController</h2>
<h2>Welcome from ComponentController</h2>
Then I changed the directive again to this:
.directive('MyComponent', function() {
return {
restrict: 'A', // <----- note this small change, restrict to attributes
// controller : 'ComponentController',
templateUrl: '/MyComponent/Component.html'
};
});
I changed index.html to this:
<h2>{{welcome}}</h2>
<div ng-controller="ComponentController" my-component></div>
And this also produced the desired output, just in less lines of code.
So I just hope this clarifies directives to people a bit better. I put this on for completeness and the steps that I took to achieve this. Obviously I had some help from the other answers which pointed me in the right direction.

Related

How can I apply a class from one directive to another in angularjs?

How to add a class from toggleClass directive to Header directive without using a jQuery selector? Not sure how I can do this in AngularJS, is directive to directive communication needed in this case?
<Header></Header>
<toggleClass></toggleClass>
in toggleClass directive I have:
module.exports = Directive;
function Directive(){
return {
restrict: 'E',
templateUrl: '/directive.html',
link : function(scope, element){
scope.expandToggle = function() {
// add class to Header directive
}
}
}
};
and its template:
<div ng-click="expandToggle()">
<span class="collapseText">Collapse</span>
<span class="expandText">Expand</span>
</div>
1'st way: use $emit
You can $emit event from toggleClass directive to parent controller, and change boolean value to control adding/removing your class.
ToggleClass directive:
module.exports = Directive;
function Directive(){
return {
restrict: 'E',
templateUrl: '/vic-compare-quotes-expand/templates/directive.html',
link : function(scope, element){
scope.expandToggle = function() {
scope.$emit('toggle');
}
}
}
};
Parent controller:
angular
.module('yourModule')
.controller('YourController', ['$scope', YourController]);
function YourController($scope) {
$scope.$on('toggle', function() {
$scope.apllyNeededClass = true;
})
}
Your HTML:
<Header ng-class="{'classToApply': apllyNeededClass}"></Header>
<toggleClass></toggleClass>
2'nd way: use service
You can use services for sharing data between controllers (directive and controller in your case).
ToggleClass directive:
module.exports = Directive;
function Directive(){
return {
restrict: 'E',
templateUrl: '/vic-compare-quotes-expand/templates/directive.html',
link : function(scope, element){
scope.expandToggle = function() {
yourService.apllyNeededClass = true;
// don't forget to inject "yourService" in this directive
}
}
}
};
Parent controller:
angular
.module('yourModule')
.controller('YourController', ['$scope', 'yourService', YourController]);
function YourController($scope, yourService) {
$scope.apllyNeededClass = yourService.apllyNeededClass;
// bind service variable to your $scope variable
}
Your HTML:
<Header ng-class="{'classToApply': apllyNeededClass}"></Header>
<toggleClass></toggleClass>
P.S.
I'm using ng-class on <Header> tag just for example and because I don't know the template of your Header directive.

Using service inside directive?

I am learning how to create custom directives.
My service looks like that:
myApp.service('myService',function(){
this.myFunction=function(myParam){
// do something
}
});
Here is my directive:
myApp.directive('myDirective',function(myService){
return {
restrict: 'E',
scope: {
param: '=myParam',
},
template: '<button ng-click="myService.myFunction(param)">Do action</button>',
}
});
In HTML, when I use <my-directive my-param="something"></my-directive> it properly renders as a button. However when I click it, myService.myFunction, doesn't get executed.
I suppose I am doing something wrong. Can someone give me a direction?
I guess this has something to do with the directive's scope.
The service wont be available directly inside the template. You'll have to use a function attached to the directive's scope and call the service function from within this function.
myApp.directive('myDirective',function(myService){
return {
restrict: 'E',
scope: {
param: '=myParam',
},
template: '<button ng-click="callService(param)">Do action</button>',
link: function(scope, element, attrs) {
scope.callService = function() {
myService.myFunction();
}
}
}
});
It doesn't work because in your example a directive doesn't actually know what is myService. You have to explicitly inject it e.g.:
myApp.directive('myDirective', ['myService', function(myService){ ... }]);
See also this question or this question.
You should use a controller to do all DOM-modifications.
See this plunkr: https://plnkr.co/edit/HbfD1EzS0av5BG6NgtIv?p=preview
.directive('myFirstDirective', [function() {
return {
'restrict': 'E',
'controller': 'MyFirstController',
'controllerAs': 'myFirstCtrl',
'template': '<h1>First directive</h1><input type="text" ng-model="myFirstCtrl.value">'
};
}
You can inject the service in the controller and then call that function inside your template:
Inject myService into controller:
myApp.controller("ctrl", function($scope, myService) {
$scope.doService = function(myParam) {
return myService.myFunction(myParam);
};
});
Call doService method of the controller inside your template:
myApp.directive('myDirective',function(){
return {
restrict: 'E',
scope: {
param: '=myParam',
},
template: '<button ng-click="doService(param)">Do action</button>',
}
});

Angular custom directive calling another custom directive

I am developing an angular framework where user can configure header, menu, footer and selected pages using custom directives. To complete this requirement, at one point I need the following. I have seen example on the net, but does not really explain it well.
The requirement is that the templateUrl of the first custom directive shall be replaced with a template attribute that should call another custom directive.
The following code with templateUrl works fine.
angular.module("app",[]);
angular.module("app").controller("productController", ['$scope', function ($scope) {
}]);
angular.module("app").directive("tmHtml", function () {
return {
transclude: false,
scope: {
},
controller: "productController",
templateUrl: "/templates/HideShow.html"
};
});
However, when I change the above code as follows. I am making the change so that my custom directive tmHtml calls another custom directive.
angular.module("app").directive("tmHtml", function () {
return {
transclude: false,
scope: {
},
controller: "productController",
template: ``<hideShow></hideShow>``
};
});
New Directive for hideShow is written as follows
angular.module("app").directive("hideShow", function () {
return {
tempateUrl: "/templates/HideShow.html"
};
});
It's not working. I understand I am missing something here. I could not find out. Appreciate help
Working code:
var app = angular.module('plunker', []);
app.controller('productController', function($scope) {
});
app.directive("hideShow", function() {
return {
templateUrl: "hideshow.html"
};
});
app.directive("tmHtml", function() {
return {
transclude: false,
scope: {},
controller: "productController",
template: "<hide-show></hide-show>"
};
});
the problem is with the spelling of templateUrl in your hideShow directive.
Demo : http://plnkr.co/edit/TaznOeNQ7dM9lyFgqwCL?p=preview
Try define your controller with controllerAs:
angular.module("app").directive("tmHtml", function () {
return {
transclude: false,
scope: {
},
controllerAs: "productController",
templateUrl: "/templates/HideShow.html"
};
});
angular.module("app").directive("tmHtml", function () {
return {
transclude: false,
scope: {
},
controller: "productController",
template: ``<hideShow></hideShow>``
};
});
must be replaced by
angular.module("app").directive("tmHtml", function () {
return {
transclude: false,
scope: {
},
controller: "productController",
template: "<hide-show></hide-show>"
};
});
under the attribute template, you add Html. So, you still have to use snake-case there, like in your Html files
Your first directive may have an eventually scoped attribute that you observe.
Then it may wrap the second directive. If needed, your directives may communicates as parents and children.

Is it possible to dynamically load directives in a template?

I'm making a widget system and I plan to have each widget be it's own directive. I want to be able to pass the directives that should be used from my controller to my view via an array.
This is what my code currently looks like:
app.controller('showDashboard', function ($scope){
$scope.widgets = ['widget1', 'widget2', 'widget3'];
});
In View:
<article ng-repeat="widget in widgets" class="widget">
<div {{widget}}></div>
</article>
Directive:
app.directive('widget1', function (){
return {
restrict: "A",
templateUrl: 'testWidget.html'
};
}).directive('widget2', function (){
return {
restrict: "A",
templateUrl: 'testWidget.html'
};
}).directive('widget3', function (){
return {
restrict: "A",
templateUrl: 'testWidget.html'
};
});
So rather than make the directive itself the widget, why not use ng-include to load in the templates which can themselves contain directives, controllers etc...
app.controller('showDashboard', function ($scope){
$scope.widgets = ['widget1.html', 'widget2.html', 'widget3.html'];
});
<article ng-repeat="widget in widgets" class="widget">
<div ng-include="widget"></div>
</article>
Here is a jsFiddle with an example.
And here is an updated fiddle that shows a slightly more complex widget with a controller.
It can be improved but just to get the idea
//html
<div wrapper mydir="widget"></div>
//js
.directive('wrapper', function ($compile){
return {
restrict: "A",
scope: {
mydir: "="
},
link: function ( scope, element ) {
var html = '<test '+scope.mydir+'></test>';
var compiled = $compile( html )( scope );
element.append(compiled);
}
}
})

How to require a controller in an angularjs directive

Can anyone tell me how to include a controller from one directive in another angularJS directive.
for example I have the following code
var app = angular.module('shop', []).
config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/', {
templateUrl: '/js/partials/home.html'
})
.when('/products', {
controller: 'ProductsController',
templateUrl: '/js/partials/products.html'
})
.when('/products/:productId', {
controller: 'ProductController',
templateUrl: '/js/partials/product.html'
});
}]);
app.directive('mainCtrl', function () {
return {
controller: function ($scope) {}
};
});
app.directive('addProduct', function () {
return {
restrict: 'C',
require: '^mainCtrl',
link: function (scope, lElement, attrs, mainCtrl) {
//console.log(cartController);
}
};
});
By all account I should be able to access the controller in the addProduct directive but I am not. Is there a better way of doing this?
I got lucky and answered this in a comment to the question, but I'm posting a full answer for the sake of completeness and so we can mark this question as "Answered".
It depends on what you want to accomplish by sharing a controller; you can either share the same controller (though have different instances), or you can share the same controller instance.
Share a Controller
Two directives can use the same controller by passing the same method to two directives, like so:
app.controller( 'MyCtrl', function ( $scope ) {
// do stuff...
});
app.directive( 'directiveOne', function () {
return {
controller: 'MyCtrl'
};
});
app.directive( 'directiveTwo', function () {
return {
controller: 'MyCtrl'
};
});
Each directive will get its own instance of the controller, but this allows you to share the logic between as many components as you want.
Require a Controller
If you want to share the same instance of a controller, then you use require.
require ensures the presence of another directive and then includes its controller as a parameter to the link function. So if you have two directives on one element, your directive can require the presence of the other directive and gain access to its controller methods. A common use case for this is to require ngModel.
^require, with the addition of the caret, checks elements above directive in addition to the current element to try to find the other directive. This allows you to create complex components where "sub-components" can communicate with the parent component through its controller to great effect. Examples could include tabs, where each pane can communicate with the overall tabs to handle switching; an accordion set could ensure only one is open at a time; etc.
In either event, you have to use the two directives together for this to work. require is a way of communicating between components.
Check out the Guide page of directives for more info: http://docs.angularjs.org/guide/directive
There is a good stackoverflow answer here by Mark Rajcok:
AngularJS directive controllers requiring parent directive controllers?
with a link to this very clear jsFiddle: http://jsfiddle.net/mrajcok/StXFK/
<div ng-controller="MyCtrl">
<div screen>
<div component>
<div widget>
<button ng-click="widgetIt()">Woo Hoo</button>
</div>
</div>
</div>
</div>
JavaScript
var myApp = angular.module('myApp',[])
.directive('screen', function() {
return {
scope: true,
controller: function() {
this.doSomethingScreeny = function() {
alert("screeny!");
}
}
}
})
.directive('component', function() {
return {
scope: true,
require: '^screen',
controller: function($scope) {
this.componentFunction = function() {
$scope.screenCtrl.doSomethingScreeny();
}
},
link: function(scope, element, attrs, screenCtrl) {
scope.screenCtrl = screenCtrl
}
}
})
.directive('widget', function() {
return {
scope: true,
require: "^component",
link: function(scope, element, attrs, componentCtrl) {
scope.widgetIt = function() {
componentCtrl.componentFunction();
};
}
}
})
//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});
function MyCtrl($scope) {
$scope.name = 'Superhero';
}

Categories