AngularJS: Trying to programatically add directives to a view - javascript

Here is my plunk: http://plnkr.co/edit/BGD0n6gmDM3kv5akIn4l?p=info
I am trying to make a view factory of sort. Ideally my controller will place a config object into scope that the view will use to render the page. It will be used to build navigation and content. I am stuck while trying to dynamically pass directives/partial view references from this object.
Here is a isolated object from the config in my controller:
$scope.partials = [
{
name: 'Well',
method: 'showWell()',
isVisible: false,
template: '<container-well></container-well>'
}
];
The focus of this question would be the template property. I built directives to function as partial views here.
Here is an example of one of my directives:
myApp.directive('containerWell', function() {
return {
restrict: 'E',
replace: false,
templateUrl: 'containers/well.html',
scope: {}
}
});
And here is the well.html template file:
<div>
<h2 class="special">Well Types</h2>
<div class="well well-cc">
<p>Closed Well</p>
<p>CSS: .well.well-cc</p>
</div>
<div class="well well-cc open">
<p>Open Well</p>
<p>CSS: .well.well-cc.open</p>
</div>
<h3 class="alt">Wells can have different highlights applied with css classes</h3>
<div class="well well-cc highlight-warning">
<p>CSS: .well.well-cc.highlight-warning</p>
</div>
</div>
Here is the code in my view that I am failing with:
<div ng-repeat="partial in partials" ng-bind-html-unsafe="{{partial.template}}"></div>
The generated markup looks like this:
<div class="ng-scope" ng-bind-html-unsafe="<container-well></container-well>" ng-repeat="partial in partials"></div>
Its just basically adding the string tag to the attribute instead of the directive.
Basically, I would like to be able to programatically add directives to my view. I am not sure if what I am trying to do is even possible. I am not confident that passing the string equivalent of the directive is the way to go. I would love some suggestions or even some stern correction if I am being ridiculous; well not too stern, maybe something constructive ;)
Here is my plunk: http://plnkr.co/edit/BGD0n6gmDM3kv5akIn4l?p=info
Thanks,
Jordan

You have to $compile the dynamic template. See the example in the docs. I forked your plunk to demonstrate the case:
http://plnkr.co/edit/WBT9FbZmvp0Xj0LAAPzk?p=preview
The points are:
ng-bind-html-unsafe is unsuitable for this usage.
Create another directive to compile the dynamic template, just as in the example:
Compilation is actually quite easy:
MyApp.directive("myDir", function($compile) {
return {
link: function(scope, elem, attrs) {
scope.$watch(
function(scope) {
return scope.$eval(attrs.myDir);
},
function(value) {
var e = $compile(value)(scope);
elem.contents().remove();
elem.append(e);
}
);
}
};
});
Use it as:
<div ng-repeat="partial in partials">
<div my-dir="partial.template"></div>
</div>

Related

ng-else directive in angularjs

how can we create ngELSE directive as same as ngIF directive?
below code for ngIfDirective. Shall we customize the code for ngELSE?
var ngIfDirective = ['$animate', function($animate) {
return {
multiElement: true,
transclude: 'element',
priority: 600,
terminal: true,
restrict: 'A',
$$tlb: true,
link: function($scope, $element, $attr, ctrl, $transclude) {
var block, childScope, previousElements;
$scope.$watch($attr.ngIf, function ngIfWatchAction(value) {
if (value) {
if (!childScope) {
$transclude(function(clone, newScope) {
childScope = newScope;
clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ');
// Note: We only need the first/last node of the cloned nodes.
// However, we need to keep the reference to the jqlite wrapper as it might be changed later
// by a directive with templateUrl when its template arrives.
block = {
clone: clone
};
$animate.enter(clone, $element.parent(), $element);
});
}
} else {
if (previousElements) {
previousElements.remove();
previousElements = null;
}
if (childScope) {
childScope.$destroy();
childScope = null;
}
if (block) {
previousElements = getBlockNodes(block.clone);
$animate.leave(previousElements).then(function() {
previousElements = null;
});
block = null;
}
}
});
}
};
}];
Normally we use like this
normal if-else
if(video == video.large){
<!-- code to render a large video block-->
}
else{
<!-- code to render the regular video block -->
}
AngularJS ng-if
<div ng-if="video == video.large">
<!-- code to render a large video block-->
</div>
<div ng-if="video != video.large">
<!-- code to render the regular video block -->
</div>
But if you are too specific that you want a directive like ng-if, ng-else-if, and ng-else then use ng-elif
Working Demo
<div ng-if="someCondition">
...
</div>
<p>
Some random junk in the middle.
</p>
<div ng-else-if="someOther && condition">
...
</div>
<div ng-else-if="moreConditions">
...
</div>
<div ng-else>
...
</div>
En else statement wouldn't make much sense on its own.
You can mimick an else statement in 2 ways with vanilla AngularJS
1. Simply use the negated check in a second ng-if
<div ng-if='myConditionIsTrue'></div>
<div ng-if='!myConditionIsTrue'></div>
2. use the ngSwitch directive
<div ng-switch="myCondition">
<div ng-switch-when="true"></div>
<div ng-switch-default></div>
</div>
Do this, its the reverse of ng-if. Simply saying ! (NOT) Value has the same effect as ng-else would. There are ng-else-if (called ng-elif) directives as well, if that's more what you're looking for.
<div ng-controller="myCtrl as ctrl">
<div ng-if="ctrl.isTrue">If</div>
<div ng-if="!ctrl.isTrue">If</div>
</div>
Though there is literally no point to creating an ng-else directive when you can simply negate the checked value in ng-if, you can modify the ng-if directive like so to achieve the exact same thing
$scope.$watch($attr.ngIf, function ngIfWatchAction(value) {
if (!value) { // add the ! here instead and name this new directive ngElse
In this it has explained how you could use the ng-else through ng-elif
Example:
<div ng-if="someTest" ng-then="theTestPassed">
Some things that assume that "someTest" is true.
</div>
<div ng-else="theTestPassed">
Some other things that assume that "someTest" is false.
</div>
http://zachsnow.com/#!/blog/2014/angularjs-ng-elif/
Also see this: http://plnkr.co/edit/XSPP3jZL8eehu9G750ME?p=preview

Is it possible to reference Angular custom directive inside itself?

Looking to implement folder hierarchy in Angular:
I'm implementing this via custom directive that references itself inside its template.
Currently it's going into infinite loop with this setup:
<!-- index.html -->
<subfolders folder="default_folder"></subfolders>
This is the <subfolders> directive:
//subfoldersDirective.js
angular.module('app').directive('subfolders', subfolders);
function subfolders() {
var directive = {
restrict: 'AE',
scope: {
folder: '=',
},
templateUrl: '/pathto/subfoldersDirective.html',
controller: DirCtrl,
controllerAs: 'vm'
};
return directive;
function DirCtrl($scope) {
var vm = this;
vm.folder = $scope.folder;
}
}
and its template:
<!-- folderDirective.html -->
<div ng-repeat="folder in vm.folder.subfolders">
{{ folder.name }}
<button ng-click="folder.is_open = true">Open folder</button>
<div ng-if="folder.is_open">
<!-- This is the problem line -->
<subfolders folder="folder"></subfolders>
</div>
</div>
In the template, <subfolders> should only get rendered after the button is clicked which triggers ng-if="folder.is_open". I guess Angular does not know this when it compiles the directive. It goes into infinite loop, even though it technically should not.
Is there a way to make it work with the directive? The logic is a bit more complex in the real app, this is why I'm looking to make it work with the directive.
I'm currently using Angular 1.2.26.
You can do this, but you need to override the compile behavior of the directive. You need to remove contents of the directive element during the compilation step, and then compile and reattach it during the post-link step. I have used the compile function below with great success:
function compile(element) {
var contents = element.contents().remove();
var contentsLinker;
return function (scope, iElement) {
if (angular.isUndefined(contentsLinker)) {
contentsLinker = $compile(contents);
}
contentsLinker(scope, function (clonedElement) {
iElement.append(clonedElement);
});
};
}
I based this function on this post, which is probably more comprehensive if you need a pre-link function.

Chaining expression binding across isolated scopes in AngularJS

How can I pass a function down through two directives with isolated scopes using expression binding?
I have two directives in a parent-child relationship:
app.directive('parentDir', function(){
return {
scope:{
parentData:'=',
changeRouteParent:'&'
},
templateUrl:'mydir/parentTemplate.html'
};
});
app.directive('childDir', function(){
return {
scope:{
dirData:'=',
changeRoute:'&'
},
templateUrl: 'mydir/childTemplate.html'
};
});
In parent template:
<div class="parentDirClass">
<a ng-click="changeRouteParent({newFoo:parentData.url})>fooLink</a>
<div child-dir ng-repeat="child in parentData.childData" dir-data="child" change-route="???"></div>
</div>
In child template:
<div class="childDirClass">
<a ng-click="changeRoute=({{newFoo:dirData.url}})">foolink</a>
</div>
Trying to use the directives together:
app.controller('exampleController', function($scope, $state){
$scope.changeRoute = function(newFoo) {
$state.go(newFoo);
};
$scope.theParentData = pData;
});
//parent data
pdata = {url:'/fooUrl',
childData:[{url:'/whatever1'},{url:'/whatever2'}, {url:'/whatever3'}]};
HTML:
<div ng-controller="exampleController">
<div parent-dir parent-data="pData" change-route-parent="changeRoute(newFoo???)"></div>
</div>
With one level of expression binding the solution seems easy as you can easily identify the variables being passed (changeRoute=({{newFoo:dirData.url}}) in the child directive) but then moving up a level I have no variable to pass. How can I continue to pass dirData.url up the chain into the example controller to call changeRoute ?
After some tinkering I found chaining can be achieved as follows --
When a bound expression is used in a template at the most-child directive:
childBoundExpression({variableFromChild:variableinIsolatedScope})
Then, starting from the parent of the most-child directive:
<div childDirective child-bound-expression="parentBoundExpression({variableToParent:variableFromChild}) ></div>
For the nth parent use the above expression and repeat until you reach the top directive/html. Eventually the name of variableToParent must be the variable that will be passed to the function on your most-parent $scope
The most important bit is that if you are passing the same variable from child to parent variableToParent and variableFromChild must be the same name.

ng-animate and custom diretives?

I am trying to add custom animation to my custom directive but failing why?
.directive('home', function ($animate) {
return {
templateUrl: 'views/pantone-inner-home.html',
restrict: 'AE',
link: function postLink($scope, element, $parent) {
var parentElement = element[0].parentElement;
var afterElement = element[0].parentElement;
$animate.enter(element, parentElement, afterElement);
$scope.PrevNext = 'open';
$scope.mainmenulink = '';
$('.top_left_logo.white img').css('position', 'fixed');
$('#focus_force').focus();
}
};
});
I then have a custom toggled ng-cluded that calls this in:
<a ng-click="closemenulink(element)" ng-href="#/services"><home class="pantone-ani"></home></a>
is just give me this everytime the ng-include includes this into the template:
TypeError: Object [object HTMLAnchorElement] has no method 'after'
why?
what does it need here:
I'm using this:
http://docs.angularjs.org/api/ngAnimate.$animate
PATNONE INNER HOME:
<div ng-click="pantone()" class="pantone_wrap_outer blue slide_bottom">
<div class="pantone_wrap_inner blue">
<div class="pantone blue">
<img src="images/services.png" alt="">
</div>
</div>
</div>
I'm trying to animate a menu with this and if i use ng-include to add these pantones ( there are 4) then after it's been opened and closed once it stays in the $templateCache so it doesn't add the "ng-enter" class after the second load which ruins my animations..
Please see the following plunker
http://plnkr.co/edit/v8aCQI59reemfiEwXICC?p=preview
I think the afterElement is null to you as there are no siblings with the element.
Please check the plunker and let me know if you need anything else.

Angular reusable class (not the CSS kind)

Is there a way to have controllers extend some base controller class? Maybe it's obvious but I'm new to Angular and just haven't found it with searches nor in the Angular docs. Maybe that's just not how you're supposed to use Angular, but I'm finding myself repeating very similar code.
I'm looking for a way to make a reusable class - as in an Object Oriented class, not the CSS kind. Maybe the more proper word is Module. I have something like the following:
<div ng-controller="FooCtrl">
<a href="" prev>Prev</a>
<div ng-model="num">{{items[num}}</div>
<a href="" next>Next</a>
</div>
<div ng-controller="BarCtrl">
<a href="" prev>Prev</a>
<div ng-model="num">{{items[num}}</div>
<a href="" next>Next</a>
</div>
Next and Prev are directives which change the "num" iterator within the applicable scope, which in turn changes the content within the middle div.
The controllers look something like:
angular.module("App").controller("FooCtrl", ["$scope", "FooProvider", function($scope, provider) {
doSomething("abc");
}]);
angular.module("App").controller("BarCtrl", ["$scope", "BarProvider", function($scope, provider) {
doSomething("xyz");
}]);
My question is if it is possible, since they're so similar, to have FooCtrl and BarCtrl extend the same base element which can specify the differences, as a parameter or something, like:
var foo = new FooCtrl("abc"),
bar = new BarCtrl("xyz");
I know that new FooCtrl isn't the Angular way but I think by now you get what I'm asking.
Is something like this more what I should be trying:
angular.module("app").controller("FooCtrl", ["$scope", "BaseCtrl", function($scope, BaseClass) {
var foo = BaseCtrl.doSomething("abc");
}]);
It looks like what you are really after is reusing the html template code, but with a different controller, as well as being able to reuse code within each component right?
You could do that like this:
angular.module('stackoverflow')
.factory('somethingService', function() {
return {
doSomething: function(input) {
console.log(input);
}
};
})
.directive('pagingFoo', function() {
return {
restrict: 'E',
template: '<div>{{items[num}}</div><a href="" next>Next Foo</div>',
controller: function(somethingService) {
somethingService.doSomething("abc");
}}
};
})
.directive('pagingBar', function() {
return {
restrict: 'E',
template: '<div>{{items[num}}</div><a href="" next>Next Bar</div>',
controller: function(somethingService) {
somethingService.doSomething("abc");
}}
};
});
then call them like this:
<html ng-app="stackoverflow">
<body>
<paging-foo></paging-foo>
<paging-bar></paging-bar>
</body>
</html>

Categories

Resources