Is it possible to dynamically load directives in a template? - javascript

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);
}
}
})

Related

Angular isolated scope values not visible in template when using replace

I am creating a small app and I have the following directive with the template.
smallgrid.directive.js:
angular.module('myActions')
.directive('smallgrid', ['$rootScope', function($rootScope) {
return {
restrict: "E",
scope: {
actionable: "="
},
controller: function($scope) {
$scope.setLocation = function() {
console.log("yee");
};
}
};
}])
.directive('three', function() {
return {
replace: true,
templateUrl: '/app/my_actions/directives/templates/grid3x3.template.html'
};
})
.directive('four', function() {
return {
replace: true,
templateUrl: '/app/my_actions/directives/templates/grid4x4.template.html'
};
})
.directive('five', function() {
return {
replace: true,
templateUrl: '/app/my_actions/directives/templates/grid5x5.template.html'
};
});
grid3x3.template.html
<div class="k-edit-field" id="board">
<div class="row" ng-click="setLocation()">
{{actionable.probability}}
</div>
</div>
I use this directive as follows:
<smallgrid three actionable="currentAction.actionable" ng-if="somecondition"></smallgrid>
The UI renders properly. However it shows {{actionable.probability}} is empty and the Click event is not firing. However, if I remove the isolated scope and access the variable directly, values are available. I understand that when I am using isolated scopes, in the three directive, I can't access values of smallgrid. Is there a way to pass those values from smallgrid to the template?
Passing a directive as an attribute of a directive you're bound to have scope problems.
It will look better if you use scope inheritance for nested directives with ng-transclude.
So your starting point should be
<smallgrid actionable="currentAction.actionable" ng-if="somecondition">
<three></three>
</smallgrid>
This way <three> has access to the $parent
function smallgrid() {
return {
restrict: "E",
transclude: true,
scope: {
actionable: "="
},
template: `<div ng-transclude></div>`,
controller: function($scope) {
$scope.setLocation = function() {
console.log("yee");
};
}
};
}
function three() {
return {
template: `<div class="k-edit-field" id="board">
<div class="row" ng-click="$parent.setLocation()">
test = {{$parent.actionable.probability}}
</div>
</div>`
};
}
function myController($scope) {
$scope.currentAction = {actionable: {probability: "test"}};
$scope.somecondition = true;
}
angular.module('myApp', []);
angular
.module('myApp')
.controller('myController', myController)
.directive('smallgrid', smallgrid)
.directive('three', three);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="myController">
<smallgrid actionable="currentAction.actionable" ng-if="somecondition">
<three></three>
</smallgrid>
</div>
</div>

How to pass a variable from a directive (ngModel) into the html outside of the directive

I've got a custom directive with an html template and it basically is a menu option. When a user makes a selection it updates the ng-model variable within the directive.
But I would like for the ng-model variable within the directive passed outside of the directive into the html page.
Here's the code snippets:
Directive:
(function() {
'use strict';
angular
.module('myModule')
.controller('myController', ['$scope', function($scope) {
$scope.sortByOptions = [
{ value:'red', displayText:'Redder' },
{ value:'blue', displayText:'Bluer' },
{ value:'gold', displayText:'Golder' },
{ value:'brown', displayText:'Browner' }
];
}]
)
.directive('myDirective', myDirective);
myDirective.$inject = [];
function myDirective() {
return {
restrict: 'A',
templateUrl: 'mydirective/sorting.html',
}
}
})();
HTML Template for the directive:
<select ng-model="sortBy" ng-options="sortByOption.displayText for sortByOption in sortByOptions track by sortByOption.value"> </select> {{sortBy.value}}
HTML of the Page:
<div class="col-md-8 form-inline" my-directive>
</div>
<!-- need ng-model variable from my-directive passed into sortBy --> <!-- it's being called on same page. I turned a menu options into a directive to save from copying/pasting same code everywhere. when the menu option gets selected it populates a list which is the li you see below -->
<li ng-repeat="object in objects | filter: searchTool | orderBy: (sortAscending ? '' : '-') + sortBy">
<div class="plank">
<div class="plank-header">
</div>
</div>
</li>
As you can see I'm trying to pass ng-model="sortBy" value from the directive which is chosen by the user into other parts of the page called sortBy that is within the li.
It would be awesome if someone can give an example of what they did.
I've done something similar by essentially exposing the variable in your directive to your controller. You can do this by passing a function from your controller into your directive such that that function gets called and essentially sets a variable in your controller. Something like this.
<div mydirective call-outside-function="setSortBy".....>
mycontroller function(...) {
$scope.setSortBy = function(sb) {
$scope.localSortBy = sb;
};
}
mydirective .....
link: function(scope,element,attrs) {
scope.$watch('sortBy',function(newval) {
attrs.callOutsideFunction(newval);
});
}
Probably not the most elegant solution but it works
I did the following:
1) added a scope.$watch to allow my directive to listen for a change in that variable on the DOM. Then it will set the new value in the controller
2) Added the controller into the directive. I originally forgot to add a line in the function colorSorter to return the controller
3) I did not need to modify the directive's html template or the main html template for the page to get it working.
Here is the directive:
(function() {
angular
.module('myModule')
.controller('sortController', ['$scope', function($scope) {
$scope.sortByOptions = [
{ value:'red', text:'Redder' },
{ value:'blue', text:'Bluer' },
];
$scope.sortBy = {value: undefined}
}]
)
.directive('colorSorter', colorSorter);
colorSorter.$inject = [];
function colorSorter() {
return {
restrict: 'A',
templateUrl: 'app/color-sorter/color-sorter.html',
controller: 'sortByController',
link: function (scope) {
scope.$watch('sortBy.value', function (value) {
console.log(value);
})
}
}
}
})();

Using & bindings in nested AngularJS directives

I'm trying to bind to ng-change on an element created by a directive up through two other directives that wrap it to a method on the controller using & bindings in an isolate scope, but I can't figure out how to get arguments to pass all the way through. Here's a plunk that demonstrates the problem.
In short, I have an HTML structure like this:
<body ng-app="ExampleApp">
<div ng-controller="Controller">
<button ng-click="doSomething('Called directly')">Call Function Directly</button>
<br />
<outer on-outer-model-changed="doSomething('Called from Outer in HTML')"></outer>
</div>
</body>
The controller:
var app = angular.module('ExampleApp', []);
app.controller('Controller', ['$scope',
function($scope) {
$scope.doSomething = function(one, two, three) {
console.log(arguments);
};
}
]);
The outer directive:
app.directive('outer', function($compile) {
return {
restrict: 'E',
scope: {
outerModelChanged: '&onOuterModelChanged'
},
link: function(scope, element, attrs) {
var innerElement = angular.element('<inner></inner>');
innerElement.attr('on-inner-model-changed', 'outerModelChanged(\'Called from Outer\')');
element.after(innerElement);
$compile(innerElement)(scope);
console.log(arguments);
}
}
});
And the inner directive that the outer directive creates:
app.directive('inner', function() {
return {
scope: {
innerModelChanged: '&onInnerModelChanged'
},
restrict: 'E',
template: '<button ng-click="innerModelChanged(\'Called from Inner\')">Call from Inner</button>'
}
});
I understand that I'm getting the output ["Called from Outer in HTML"] because this is hardcoded into the <outer> tag. What I don't understand is how to pass arguments all the way up from the inner directive.
I'm not sure I 100% get what you want to accomplish but this is how you would make the ["Called from Inner"] message appear.
Change the html so the on-outer-model-changed expression does not use a hardcoded string.
<body ng-app="ExampleApp">
<div ng-controller="Controller">
<button ng-click="doSomething('Called directly')">Call Function Directly</button>
<br />
<outer on-outer-model-changed="doSomething(outerParam)"></outer>
</div>
</body>
Then change the outer directive to call outerModelChanged with a parameter. And set the outerParam to the innerParam.
app.directive('outer', function($compile) {
return {
restrict: 'E',
scope: {
outerModelChanged: '&onOuterModelChanged'
},
link: function(scope, element, attrs, controller) {
var innerElement = angular.element('<inner></inner>');
innerElement.attr('on-inner-model-changed', 'outerModelChanged({outerParam:innerParam})');
element.after(innerElement);
$compile(innerElement)(scope);
console.log(arguments);
}
}
});
Finally call the innerModelChanged from the inner directive with the innerParam set to your message.
app.directive('inner', function() {
return {
scope: {
innerModelChanged: '&onInnerModelChanged'
},
restrict: 'E',
template: '<button ng-click="innerModelChanged({innerParam:\'Called from Inner\'})">Call from Inner</button>'
}
});
Here is a plunk to the above code.

Apply Angular Controller to a Template

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.

Call controller $scope methods from nested directives

I have the following directives:
Directive 1
app.directive('tableDiv', function () {
return {
templateUrl: 'js/directives/table-div/table-div.html',
replace: true,
scope: {
table: '=',
},
controller: function ($scope, $element, $attrs) {
},
link: function postLink(scope, element, attrs) {
}
}
});
Directive 1 template:
<div data-table-div-row value="row" sizes="table.tbody.sizes" ng-repeat="row in table.tbody.values">
</div>
Directive 2:
app.directive('tableDivRow', function ($rootScope) {
return {
templateUrl: 'js/directives/table-div/table-div-row.html',
replace: true,
scope: {value: '=', sizes: '='},
controller: function ($scope, $element, $attrs) {
$scope.showInfo = function () {
$scope.visible = true;
};
$scope.hideInfo = function () {
$scope.visible = false;
};
$scope.hasTemplate = function() {
return ($scope.value.template ? true : false);
}
},
link: function postLink(scope, element, attrs) {
scope.$watch(function () {
return scope.visible;
}, function (value) {
if (value === true) {
$(element).find('div.table-row').addClass('open');
$(element).find('div.table-row.edit').removeClass('hidden');
} else {
$(element).find('div.table-row').removeClass('open');
$(element).find('div.table-row.edit').addClass('hidden');
}
}, true);
}
}
});
Directive 2 template:
<div>
<div class="row-fluid">
<div class="table-row clearfix">
<div class="{{sizes.first}} first">{{value.display.first}}</div>
<div ng-repeat="cell in value.display.cells" class="{{sizes.cells[$index]}}">{{cell}}</div>
<div class="{{sizes.last}} last regular">
<div ng-switch on="value.display.last">
<div ng-switch-when="%editbutton%">
<div class="show-info closed" ng-click="showInfo()"></div>
</div>
<div ng-switch-default>
{{value.display.last}}
</div>
</div>
</div>
</div>
</div>
<div ng-if="hasTemplate()">
<ng-include src="value.template"></ng-include>
</div>
Inside the second directive template I'm including a dynamic template based on the controller $scope model. Inside that template and in the directive template I want to call a function from the controller $scope. Is there a way to achieve that?
A child scope is created for <ng-include src="value.template"></ng-include>, which means that the parent functions should be available in this template. In other words, you shouldnt have to do anything and it'll work - see this simple example: http://plnkr.co/edit/Es2UL09ASPSTa5Fstzjf?p=preview
So, it seems it's in the docs and it wasn't clear enough for me. Inside the directive declaration I needed to add: method: '&'
scope: {
table: '=',
method: '&'
},
and inside the template where I call the directive, the method html attribute MUST HAVE () at the end:
<div data-table-div-row method="method()" value="row" sizes="table.tbody.sizes" ng-repeat="row in table.tbody.values"></div>
In this way the method can be passed down to the second directive.
As #Direvius propose, to call a method in the controller scope from a directive you must call the method passing an object with the parameter rather the parameter itself :
scope.method({message : "text"});
So, to call a controller method from the nested directive you must to wrap the parameter inside n objects :
scope.method({message : {message : "text"}});
Don't forget to declare "message" as argument in the nested directive template and the outer-directive declaration in your html :
<outer-directive outer-method-arg="method(message)"></outer-directive>
also in your outer template :
<inner-directive inner-method-arg="method(message)"></inner-directive>

Categories