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.
Related
So I have this inside my controller:
var myApp = angular.module('app', [], function ($interpolateProvider) {
$interpolateProvider.startSymbol('[%');
$interpolateProvider.endSymbol('%]');
});
myApp.controller('MyController', function ($scope) {
$scope.show = [];
// confirmed this returns true when intended
$scope.showElement = function (id) {
return ($scope.show.indexOf(id) > -1);
};
});
And my HTML structured as below, with my-class using display: none; and other rules which are imperative to the display of this element. Because of the number of instances where it is being used I cannot simply remove the class or alter its rules. In all other instances in the application, this works as expected.
<div class="my-class" ng-show="showElement(obj.id)">
...
</div>
The element is not shown on page load, nor is its appearance updated if the underlying variable $scope.show is changed.
The previous development team was manually (by using deep and obfuscated Javascript) adding an additional CSS class in the other instances where it was being used.
My solution was to add a visible class using the ng-class directive:
<div class="my-class" ng-class="{'visible': showElement(obj.id)}">
....
</div>
I have been tasked with developing an app in Angular 1.6, and I have not done any Angular 1.x for quite some time. Been mostly doing 2.x. In fact, never done 1.6 at all.
I have two components: a tile container, and a tile. The tiles are selectable, so I imagined having a tile container component that would keep track of which tile was selected, and a tile component which is primarily a UI component.
My tile component looks like this:
// dp-claim-filter-tile.component.js
function ClaimFilterTileController() {
var ctrl = this;
ctrl.isDisabled = () => {
return ctrl.claimCount == 0;
}
ctrl.test = function () {
console.log('ctrl = ' + JSON.stringify(ctrl));
ctrl.onTileClicked(ctrl);
}
}
angular.module('dpApp').component('dpClaimFilterTile', {
templateUrl: '/templates/dp-claim-filter-tile.tmpl.html',
controller: ClaimFilterTileController,
bindings: {
tileTitle: '#',
claimCount: '#',
isAdd: '<',
isActive: '<',
onTileClicked: '&'
}
});
And the template looks like this:
// dp-claim-filter-tile.tmpl.html
<div ng-if="$ctrl.isAdd===true" layout="column" layout-align="space-around center">
<div><h2 class="md-title">{{$ctrl.tileTitle}}</h2></div>
<div>
<md-icon md-font-icon="fa-plus" class="fa fa-2x"></md-icon>
</div>
</div>
<div ng-if="$ctrl.isAdd!==true" layout="column" layout-align="space-around center" ng-click="$ctrl.test()">
<div><h2 class="md-title tile-text">{{$ctrl.tileTitle}}</h2></div>
<div class="md-display-1 tile-text">{{$ctrl.claimCount}}</div>
</div>
The tile container component looks like this:
// dp-claim-filter-tiles.component.js
function ClaimFilterTilesController() {
var ctrl = this;
ctrl.tileClicked = function(tile) {
console.log('tile = ' + JSON.stringify(tile));
}
}
angular.module('dpApp').component('dpClaimFilterTiles', {
templateUrl: '/templates/dp-claim-filter-tiles.tmpl.html',
controller: ClaimFilterTilesController
});
And an extract of the container UI looks like this:
// dp-claim-filter-tiles.tmpl.html
<md-grid-tile>
<dp-claim-filter-tile is-active="true" tile-title="Links Sent" claim-count="7" on-tile-clicked="$ctrl.tileClicked($event)"></dp-claim-filter-tile>
</md-grid-tile>
What I am expecting, and hoping for, is for the $event parameter I am supplying to surface as a parameter to the ClaimFilterTilesController.tileClicked() function.
How do I accomplish this?
The click $event will be available in the ngClick directive by itself, you won't be able to carry it around before the actual ngClick directive gets triggered.
Therefore, you could access the click event exclusively in dp-claim-filter-tile.tmpl.html template, like this:
ng-click="$ctrl.test($event, otherParam)"
Try utilizing ng-click rather than the on-tile-clicked. This way you can get at the $event object which can be passed into other functions within your component definition.
<md-grid-tile>
<dp-claim-filter-tile-is-active="true" tile-title="Links Sent" claim-count="7" ng-click="$ctrl.tileClicked($event)"></dp-claim-filter-tile>
</md-grid-tile>
I found the answer on the AngularJS site: Intercomponent Communication. The trick lies in using the require property when defining the component. You can essentially create a reference to the enclosing component. Shall implement tomorrow.
Thanks for your answers guys.
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.
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>
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>