Conditionally loading the template in a angular directive - javascript

I am trying to load the template of directive only conditionally, for example-
.directive('truncate',["$window", function($window, $compile){
return {
restrict: 'A',
templateUrl: 'template/truncate.html',
link: function (scope, element, attrs) {
var height = element[0].offsetHeight;
var shouldBetruncated = height>200;
if(shouldBetruncated){
// want my template to load here, otherwise template is not loaded
};
}
}
}])
.run(function ($templateCache) {
$templateCache.put('template/truncate.html',
'template code'
);
})
Is there anyway to acheive this?

Try this ...
.directive('truncate',["$window", function($window, $compile){
return {
restrict: 'A',
templateUrl: 'template/truncate.html',
link: function (scope, element, attrs) {
var height = element[0].offsetHeight;
shouldBetruncated = height>200;
if(shouldBetruncated){
loadTemplate();
};
// function loadTemplate -- begins
var loadTemplate = function ($templateCache) {
$templateCache.put('template/truncate.html',
'template code'
);
};
// -- ends
}
}
}]);

/*This will solve you problem*/
link: ...
templateUrl: function() {
if(shouldBetruncated) {
return 'template/truncate.html';
} else {
return '';
}
}
For more info, you can pass the template from html or generate based on conditions

Related

How do I access a directive scope from a parent directive/controller?

I have a template that goes something like this:
<parent-directive>
<child-directive binding="varFromParent"></child-directive>
<button ng-click="parentDirective.save()"></button>
</parent-directive>
When executing a function in the parentDirective controller, is it possible to access and manipulate the scope variables of the childDirective for e.g. if I have them set up as so
angular.module('app').directive('parentDirective', function() {
return {
restrict: 'E',
templateUrl: '...',
controllerAs: 'parentDirective',
controller: function($rootScope, $scope) {
//...
this.save = () => {
//Need to manipulate childDirective so that its
//scope.defaultValue == 'NEW DEFAULT'
}
}
}
});
and
angular.module('app').directive('childDirective', function() {
return {
restrict: 'E',
templateUrl: '...',
scope: {
binding: '='
},
controllerAs: 'childDirective',
link: function(scope, elm, attrs) {
scope.defaultValue = 'DEFAULT';
}
}
});
How would I go about doing this? Is there any way to do this without setting up a bidirectional binding? I would like to avoid a mess of attributes on the <child-directive> element if possible.
There are many way to set up a communication between your children and your parent directive:
Bidirectional binding (like you said)
Registration of your children in your parent.
You can use the directive require property and the last parameter of the link function controllers to register a children in his parent.
Events, see $scope.on/broadcast
Angular services (as they are "singletons", it's very easy to use it to share data between your directives)
etc.
Example for 2:
angular.module('Example', [])
.directive('parent', [function () {
return {
controller: function (){
// registerChildren etc
}
// ...
};
}])
.directive('children', [function () {
return {
require: ['^^parent', 'children'],
controller: function (){
// ...
}
link: function ($scope, element, attributs, controllers) {
ParentController = controllers[0];
OwnController = controllers[1];
ParentController.registerChildren(OwnController);
// ...
}
// ...
};
}])
In this case you probably don't need to isolate child's directive scope. Define a variable you need to change on parent's scope and then child's directive scope would inherit this value so you can change it value in child's directive and it would be accessible from parent.
angular.module('app').directive('parentDirective', function() {
return {
restrict: 'E',
controllerAs: 'parentCtrl',
controller: function($scope) {
$scope.value = 'Value from parent';
this.value = $scope.value
this.save = function() {
this.value = $scope.value;
}
}
}
});
angular.module('app').directive('childDirective', function() {
return {
restrict: 'E',
controllerAs: 'childCtrl',
controller: function($scope) {
$scope.value = 'Value from child';
this.setValue = function() {
$scope.value = 'New value from child';
}
}
}
});
Here is the fiddle
http://jsfiddle.net/dmitriy_nevzorov/fy31qobe/3/

Encapsulated Link and Controller in Directive

I wan't to program a flexible angular directive with it's properties defined in an own, simple object.
Angular:
contentFactory.directive("listViewDir", function ($compile) {
return {
restrict: "E",
scope: {
datasource: '=',
config: '='
},
controller: function ($scope) {
return $scope.config.controller($scope);
},
link:
return $scope.config.link(scope, element, attrs);
}
}
});
Own Configuration Object:
contentFactory.controller("indexCtrl", function ($scope) {
$scope.config = oLiftTabs;})
var configurations = [{
controller: function ($scope) {
$scope.ButtonClicked = function () {
alert('Button wurde geklickt!');
}
return $scope;
},
link: function (scope, element, attrs){
var template = "... myTemplate ..";
element.html(template);
$compile(element.contents())(scope);
},
}]
While my solution for the controller works well, it doesn't for the link.
Is there a more proper way for my approach? Can I realize access in my encapsulated method to the services (like $compile) without declaring it in the directive declaration?
Is this what you're trying to achieve? You didn't make it clear where you expect this object to live so I've assumed you want it a parent controller. This doesn't feel like a good idea but without knowing more about your use case it's hard to say.
DEMO
html
<body ng-controller="MainCtrl">
<list-view-dir config="config"></list-view-dir>
</body>
js
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, $compile) {
$scope.config = {
controller: function ($scope) {
$scope.ButtonClicked = function () {
alert('Button wurde geklickt!');
}
return $scope;
},
link: function (scope, element, attrs){
var template = '<button ng-click="ButtonClicked()" > Alert</button>';
element.html(template);
$compile(element.contents())(scope);
},
};
});
app.directive("listViewDir", function(){
return {
restrict: "E",
scope: {
datasource: '=',
config: '='
},
controller: function ($scope) {
return $scope.config.controller($scope);
},
link: function(scope, element, attrs){
return scope.config.link(scope, element, attrs);
}
};
});
Update
From your comments it sounds like you need to use a factory. Maybe something like this? It feels pretty ugly but it could be what you're looking for.
DEMO2
var app = angular.module('plunker', []);
app.factory('directiveConfigurations', function($compile){
var configurations = {
'listViewDir': {
controller: function ($scope) {
$scope.ButtonClicked = function(){
alert('Button wurde geklickt!');
};
return $scope;
},
link: function (scope, element, attrs){
var template = '<button ng-click="ButtonClicked()" > Alert</button>';
element.html(template);
$compile(element.contents())(scope);
}
}
};
return {
get: get
};
////////////////////////
function get(key){
return configurations[key];
}
});
app.controller('MainCtrl', function($scope, directiveConfigurations) {
$scope.config = directiveConfigurations.get('listViewDir');
});
app.directive("listViewDir", function(){
return {
restrict: "E",
scope: {
datasource: '=',
config: '='
},
controller: function ($scope) {
return $scope.config.controller($scope);
},
link: function(scope, element, attrs){
return scope.config.link(scope, element, attrs);
}
};
});

AngularJS: transcluded with same controller

I have a directive that wraps another one like this :
<div direction from="origin" to="destination">
<div direction-map line-color="#e84c3d"></div>
</div>
the direction-map directive is transcluded, see my code (Fiddle available here) :
var directionController = function() {
//do stuffs
};
var directionMapController = function() {
//do other stuffs
};
var Direction = angular.module("direction", [])
.controller("directionController", directionController)
.controller("directionMapController", directionMapController)
.directive("direction", function() {
var directive = {
restrict: "AEC",
controller: "directionController",
scope: {},
transclude: true,
link: {
pre: function($scope, $element, attrs, controller, transclude) {
console.log("direction's controller is directionController : ");
console.log(controller.constructor === directionController);//true, that's ok
transclude($scope, function(clone) {
$element.append(clone);
});
}
}
};
return directive;
})
.directive("directionMap", function() {
var directive = {
require: "^direction",
controller: "directionMapController",
restrict: "AEC",
scope: true,
link: {
pre: function($scope, $element, $attrs, controller) {
console.log("directionMap's controller is directionMapController :");
console.log(controller.constructor===directionMapController);//false that's not OK!!!!
}
}
};
return directive;
});
So my question is:
Why my child directive direction-map gets as parameter the controller of its parent (I think it's because it is transcluded), is it possible to avoid this or should I just re-think my code ?
It's happening beacause you are using require: "^direction" if you remove this line the directive will get the controller of itself rather than the parent one.
Hope it help :)
Updated Fiddle

angularjs how to change tempate url dynamically

I have a directive in my module. And I want to change the templateUrl based on a attribute.
HTML
<div test stage="dynamicstage"></div>
Module
angular.module('trial', [])
.controller('trialCtrl', function ($scope) {
$scope.dynamicstage = 'Welcome';
})
.directive('test', function () {
return {
restrict: 'A',
scope: {
'stage': '='
},
link: function (scope, element, attrs) {
scope.$watch('stage', function(condition){
if(stage === 'welcome'){
templateUrl: "hello.html";
}else{
different template url...
};
});
}
}
});
This does not work. The templateurl is not loaded into the div. I want to change the templateUrl dynamically is this possible.
I appreciate any help.
This is not very transparent in Angular. templateUrl can be a function to dynamically construct template URL, however in your case you need a scope, which is not yet available at the moment URL is constructed.
You can do something like this with the help of ngInclude:
app.directive('test', function() {
return {
restrict: 'A',
scope: {
'stage': '='
},
template: '<div ng-include="templateUrl"></div>',
link: function(scope, element, attrs) {
scope.$watch('stage', function(condition) {
if (scope.stage === 'Welcome') {
scope.templateUrl = "hello.html";
} else {
scope.templateUrl = "other.html";
};
});
}
}
});
Demo: http://plnkr.co/edit/l1IysXubJvMPTIphqPvn?p=preview
Solution1 :
scope.$watch('stage', function(condition){
var templateUrl;
if(stage === 'welcome'){
templateUrl = "hello.html";
} else{
templateUrl = "someothertemplate.html";
};
//load the template;
$http.get(templateUrl)
.then(function (response) {
// template is loaded.
// add it and compile it.
angular.element(element).html(response.data);
$compile(element.contents())(scope);
});
});
Solution2:
Use ng-include
<div test stage="dynamicstage">
<div ng-include="templateUrl"></div>
</div>
Inside directive:
scope.$watch('stage', function(condition){
var templateUrl;
if(stage === 'welcome'){
templateUrl = "hello.html";
} else{
templateUrl = "someothertemplate.html";
};
scope.$parent.templateUrl = templateUrl; // make sure that templateUrl is updated in proper scope
})

AngularJs Directive with dynamic Controller and template

I want to create a directive that has dynamic view with dynamic controller. the controller and the template view is coming from the server.
The Directive
var DirectivesModule = angular.module('BPM.Directives', []);
(function () {
'use strict';
angular
.module('BPM.Directives')
.directive('bpmCompletedTask', bpmCompletedTask);
bpmCompletedTask.$inject = ['$window'];
function bpmCompletedTask ($window) {
// Usage:
// <bpmCompletedTask></bpmCompletedTask>
// Creates:
//
var directive = {
link: link,
restrict: 'E',
scope: {
type: '=',
taskdata: '=',
controllername:'#'
},
template: '<div ng-include="getContentUrl()"></div>',
controller: '#',
name: 'controllername'
};
return directive;
function link(scope, element, attrs) {
scope.getContentUrl = function () {
return '/app/views/TasksViews/' + scope.type + '.html';
}
scope.getControllerName = function ()
{
console.warn("Controller Name is " + scope.type);
return scope.type;
}
}
}
})();
Here how I'm trying to use the directive
<div ng-controller="WorkflowHistoryController as vm">
<h2>Workflow History</h2>
<h3>{{Id}}</h3>
<div ng-repeat="workflowStep in CompletedWorkflowSteps">
<bpm-completed-task controllername="workflowStep.WorkflowTaskType.DataMessageViewViewName" taskdata="workflowStep.WorkflowTaskOutcome.TaskOutcome" type="workflowStep.WorkflowTaskType.DataMessageViewViewName">
</bpm-completed-task>
</div>
</div>
The problem now is when the directive gets the controller name it get it as literal string not as a parameter.
Is it doable ?
if it's not doable, What is the best solution to create dynamic views with its controllers and display them dynamically inside ng-repeat?
Thanks,
Update 20 Jan I just updated my code in case if some one interested in it. All the Credit goes to #Meligy.
The First Directive:
(function () {
'use strict';
angular
.module('BPM.Directives')
.directive('bpmCompletedTask', bpmCompletedTask);
bpmCompletedTask.$inject = ['$compile', '$parse'];
function bpmCompletedTask ($compile, $parse) {
var directive = {
link: function (scope, elem, attrs) {
console.warn('in the first directive - before if');
if (!elem.attr('bpm-completed-task-inner'))
{
console.warn('in the first directive');
var name = $parse(elem.attr('controllername'))(scope);
console.warn('Controller Name : ' + name);
elem = elem.removeAttr('bpm-completed-task');
elem.attr('controllernameinner', name);
elem.attr('bpm-completed-task-inner', '');
$compile(elem)(scope);
}
},
restrict: 'A',
};
return directive;
}
})();
The Second Directive
angular
.module('BPM.Directives')
.directive('bpmCompletedTaskInner',['$compile', '$parse',
function ($window, $compile, $parse) {
console.warn('in the second directive');
return {
link: function (scope, elem, attrs) {
console.warn('in the second directive');
scope.getContentUrl = function () {
return '/app/views/TasksViews/' + scope.type + '.html';
}
},
restrict: 'A',
scope: {
type: '=',
taskdata: '=',
controllernameinner: '#'
},
template: '<div ng-include="getContentUrl()"></div>',
controller: '#',
name: 'controllernameinner'
};
}]);
The Html
<div ng-repeat="workflowStep in CompletedWorkflowSteps">
<div bpm-completed-task controllername="workflowStep.WorkflowTaskType.DataMessageViewViewName" taskdata="workflowStep.WorkflowTaskOutcome.TaskOutcome"
type="workflowStep.WorkflowTaskType.DataMessageViewViewName">
</div>
</div>
Update:
I got it working, but it's really ugly. Check:
http://jsfiddle.net/p6Hb4/13/
Your example has a lot of moving pieces, so this one is simple, but does what you want.
Basically you need a wrapper directive that takes the JS object and converts into a string property, then you can use هى your directive for everything else (template, scope, etc).
.
Update 2:
Code Inline:
var app = angular.module('myApp', []).
directive('communicatorInner', ["$parse", "$compile",
function($parse, $compile) {
return {
restrict: 'A',
template: "<input type='text' ng-model='message'/><input type='button' value='Send Message' ng-click='sendMsg()'><br/>",
scope: {
message: '='
},
controller: '#'
};
}
]).
directive('communicator', ['$compile', '$parse',
function($compile, $parse) {
return {
restrict: 'E',
link: function(scope, elem) {
if (!elem.attr('communicator-inner')) {
var name = $parse(elem.attr('controller-name'))(scope);
elem = elem.removeAttr('controller-name')
elem.attr('communicator-inner', name);
$compile(elem)(scope);
}
}
};
}
]).
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 ");
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.9/angular.min.js"></script>
<div ng-app="myApp">
<div ng-init="test = {p: 'PhoneCtrl', l: 'LandlineCtrl' }">
<communicator controller-name="test.p" message="'test1'"></communicator>
<communicator controller-name="test.l"></communicator>
</div>
</div>
.
Original (irrelevant now but can help other related issues)
Yes, it should work.
A test with Angular 1.3:
http://jsfiddle.net/p6Hb4/9/
Things to check:
Is the controller defined and added to the module? It will not work
If the controller is just a global function it won't work. It has to be added via the <myModule>.controller("<controllerName>", <functiion>) API
Does ng-controller work? Just adding it to the template
Similarly, does using ng-controller directly outside of the directive work?

Categories