pass data between components with $scope.$emit() - javascript

I'm trying to pass data between two components that have the same parent component (simple version: I have child1 component, child2 component, and both of them are children of parent component). Also all the components are as controllers.
Parent component is also a root component (it doesnt have any parent components) so I thought I had to use $rootScope?
If i want to pass a var myVar (integer) from child1 to child2 I guess I should do :
in child1 component:
$rootScope.$emit('myEvent', vm.myVar);
in parent component:
$scope.$on('changeTab', function(){
console.log('parent', vm.myVar);
});
$scope.$broadcast('changeTab', vm.myVar);
in child2 component:
$rootScope.$on('changeTab', function () {
console.log('child2', vm.myVar);
});
I am new to this and don't understand why this doesn't work, probably nothing big, if anyone could help me out would appreciate it :) ty !
By the way I get "undefined" in both console logs, but well myVar is defined properly in child1.

You need to broadcast to the child's parent:
$scope.$parent.$broadcast('hi', {
msg: 'Hello there!'
});
$scope.$broadcast sends the message down the chain, but if you broadcast to the scope's parent then the parent will send the message down, which includes both siblings.
angular.module('appModule', [])
.controller('ParentController', [function() {
this.hello = 'I am the parent';
}])
.controller('Child1Controller', ['$scope', function($scope) {
this.hello = 'I am Child 1';
var that = this;
$scope.$on('hi', function(event, data) {
console.log(data.msg);
that.hello = data.msg;
});
}])
.controller('Child2Controller', ['$scope', function($scope) {
this.hello = 'I am Child 2';
this.sayHello = function(str) {
$scope.$parent.$broadcast('hi', {
msg: str
});
}
}]);
angular.bootstrap(window.document, ['appModule'], {
strictDi: true
});
div {
border: 1px dotted #f00;
padding: 15px;
}
<div ng-controller="ParentController as parentCtrl">
<p>{{parentCtrl.hello}}</p>
<div ng-controller="Child1Controller as child1Ctrl">
<p>{{child1Ctrl.hello}}</p>
</div>
<div ng-controller="Child2Controller as child2Ctrl">
<p>{{child2Ctrl.hello}}</p>
<button type="button>" ng-click="child2Ctrl.sayHello('Child 2 is cool')">Say Hello to Child 1</button>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.7/angular.min.js"></script>

Related

Why can't I print the value from input type using AngularJS?

Here is a very simple application that lets the user change his name. However, there is some bug in this code. Whenever the user writes something into input, the change is not reflected in the welcome header.
app.js
'use strict';
angular
.module('angularScopesBug', [])
.controller("WelcomeController", function ($scope) {
$scope.name = 'John Doe';
$scope.getName = function() {
return $scope.name;
};
})
.controller("EditingController", function($scope) {
$scope.editMode = false;
$scope.changeName = function() {
$scope.editMode = true;
};
$scope.closeEditor = function() {
$scope.editMode = false;
};
})
.directive("nameEditor", function () {
return {
template: 'Write your name: <input type="text" ng-model="name">'
};
});
index.html
<div ng-app="angularScopesBug" ng-controller="WelcomeController">
<h1>Hello, {{ getName() }}</h1>
<div ng-controller="EditingController">
<button ng-click="changeName()" ng-show="!editMode">Change your name</button>
<div ng-show="editMode">
<name-editor ng-show="editMode"></name-editor>
<button ng-click="closeEditor()" ng-show="editMode">Close name editor</button>
</div>
</div>
</div>
The header should change according to the input.
Always use an object in ng-model.
Primitives have no inheritance and the nested controller is using a child scope. When that child scope is created it gets the primitive name as value.
When it is an object the object is inherited so that updating property values will be reflected in all references to that object
angular
.module('angularScopesBug', [])
.controller("WelcomeController", function($scope) {
$scope.model = {
name: 'John Doe'
};
})
.controller("EditingController", function($scope) {
// simplified for clarity
})
.directive("nameEditor", function() {
return {
template: 'Write your name: <input type="text" ng-model="model.name">'
};
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.min.js" ></script>
<div ng-app="angularScopesBug" ng-controller="WelcomeController">
<h1>Hello, {{ model.name }}</h1>
<div ng-controller="EditingController">
<button ng-click="editMode = true" ng-show="!editMode">Change your name</button>
<div ng-show="editMode">
<name-editor ng-show="editMode"></name-editor>
<button ng-click="editMode = false" ng-show="editMode">Close name editor</button>
</div>
</div>
</div>
Your two controllers have different $scope values. Since the editor is inside the EditingController $scope, it is changing a .name value in the lower scope, not in the higher WelcomeController $scope.
Try giving yourself a parent method to modify the value;
$scope.changeName = function(str) {
$scope.name = str;
};
And then calling that method with the new name from the child.

Angularjs: Dynamically load directives in view with data

I am trying to make a dynamic framework with angularjs. I load sections with a webapi, that have data about the directive that it uses and the data that should be used in that directive. The data that I send can look like this:
[
{
id: "section1",
directive: "<my-directive></my-directive>",
content: {
title: "This is a title",
text: "This is a text"
}
},
{
id: "section2",
directive: "<my-other></my-other>",
content: {
title: "This is another title",
list: ["This is a text", "This is another text"]
}
}
]
When I load this data, I convert the directives in to element with $compile.
angular.forEach($sections, (value, key):void => {
value.directive = $compile(value.directive)($scope);
}
So I can actually load this data in the view, like this:
<div ng-repeat="section in sections">
{{section.directive}}
</div>
First of all, this doesn't show up in my view, so how do I fix this?
Then the second issue I have. When I actually get this directive loaded into the view, how will I be able to access the data that should be used in this directive? I do have an id added to the sections.This is what I tried:
angular.forEach($sections, (value, key):void => {
value.directive = $compile(value.directive)($scope);
var parent = angular.element('#sectionsparent'); //The parent element has this id
parent.append(value.directive);
}
This way the section elements show up, but I cannot access the data that should be loaded inside the directive.
Thank you for your help in advance, let me know if you need more information.
EDIT:
When the directive is eventually loaded, I want to be able to access the data that belongs to that section. So if we take first section in the sample data, I want to be able to do the following in the template of the directive:
<!-- This file is myDirectiveTemplate.hmtl -->
<div id="{{id}}>
<h1>{{title}}</h1>
<p>{{text}}</p>
</div>
I don't care if I have to access these properties through a viewmodel object, so it would be {{vm.id}} instead of {{id}}. But I prefer to not have any function calls inside my template to actually get data.
Alright. There may be another way to accomplish this, or perhaps using includes instead of directives, but here's one way at least.
Taking your example code, you can follow your second route with $compile and append but you also need to pass an html-attribute for the isolate scope's content and bind it with a new $scope with the section added. (You also need to wrap in a $timeout so querying the DOM happens after it's initially rendered).
var app = angular.module('app', []);
app.controller('AppCtrl', function($scope, $compile, $timeout) {
$scope.sections = [
{
id: "section1",
directive: "my-directive",
content: {
title: "This is a title",
text: "This is a text"
}
},
{
id: "section2",
directive: "my-other",
content: {
title: "This is another title",
list: ["This is a text", "This is another text"]
}
}
];
// Need to timeout so rendering occurs and we can query the DOM.
$timeout(() => {
angular.forEach($scope.sections, (section) => {
let newScope = $scope.$new();
newScope.content = section.content;
let dir = section.directive;
let compiled = $compile(`<${dir} content="content"></${dir}>`)(newScope);
let parent = angular.element(document.querySelector('#' + section.id));
parent.append(compiled);
});
});
});
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: {content: '='},
template: `<div>
<h1>{{content.title}}</h1>
<p>{{content.text}}</p>
</div>`,
};
});
app.directive('myOther', function() {
return {
restrict: 'E',
scope: {content: '='},
template: `<div>
<h1>{{content.title}}</h1>
<ul>
<li ng-repeat="item in content.list">{{item}}</li>
</ul>
</div>`,
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="AppCtrl">
<div ng-repeat="section in sections" id="{{section.id}}"></div>
</div>

How to communicate between 2 component - sibling components(Angular 1.5.8)

Hey I have the following component tree: I have a root component called rules and two sons components called: rulesPanel & rulesEditor.
Now I can create a communication between son and mother component:
rulesEditor can call to rules component and jump an event on him.
rulesPanel can call to rules component and jump an event on him.
I want to have a communication between the 2 brothers:
rulesEditor and rulesPanel.
I don't want to use $scope or $broadcast, I want to do it through the bindings of the component himself.
I have tried to think of way of doing so, but all I got is that I can call to upper level but not to a parallel level.
Edit:
My Question is different from the possible duplication,
I don't want to pass a data, I want to execute a function in one component and then execute another function in the sibling component as a result of a click function in the brother component.
Here is my code and what I have achieved so far:
var app = angular.module("app",[]);
angular.module('app').component('rules', {
template: `
<rules-panel dial-mom="$ctrl.receivePhoneCall(message)">
</rules-panel>
<rules-editor>
</rules-editor>`,
bindings: {
},
controller: rulesController,
});
function rulesController(){
var self = this;
self.receivePhoneCall = function(message){
console.log("Hello Son");
console.log("I got your message:",message)
}
console.log("rulesController")
}
angular.module('app').component('rulesPanel', {
template: `<h1>rulesPanel</h1>
<button ng-click="$ctrl.callMom()">CallMom</button>
<button ng-click="$ctrl.CallBrother()">CallBrother</button>`,
bindings: {
dialMom: '&'
},
controller: rulesPanelController,
});
function rulesPanelController(){
var self = this;
console.log("rulesPanelController");
self.callMom = function(){
console.log("Call mom");
self.dialMom({message:"Love you mom"});
}
self.CallBrother = function(){
console.log("Call brother");
}
}
angular.module('app').component('rulesEditor', {
template: '<h1>rulesEditor</h1>',
bindings: {
},
controller: rulesEditorController,
});
function rulesEditorController(){
console.log("rulesEditorController")
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
<div ng-app="app">
<rules></rules>
</div>
You can use a semi Angular 2 component approach. Meaning you can use Input/output approach to achieve this.
I will give you an example and you can take it from there.
Let's say you have a header and a main component.
In your header component where you want to notify the main you can raise an event like this:
.component('headerComponent', {
template: `
<h3>Header component</h3>
<a ng-class="{'btn-primary': $ctrl.view === 'list'}" ng-click="$ctrl.setView('list')">List</a>
<a ng-class="{'btn-primary': $ctrl.view === 'table'}" ng-click="$ctrl.setView('table')">Table</a>
`,
controller: function() {
this.setView = function(view) {
this.view = view
this.onViewChange({$event: {view: view}})
}
},
bindings: {
view: '<',
onViewChange: '&'
}
})
With binding view: '<' we specify that header component will be able to read outer something and bind it as view property of the own controller.
Header controller can be used like this:
<header-component view="root.view" on-view-change="root.view = $event.view"></header-component>
On the other hand main in simpler, it only needs to define input it accepts:
.component('mainComponent', {
template: `
<h4>Main component</h4>
Main view: {{ $ctrl.view }}
`,
bindings: {
view: '<'
}
})
And finally it all wired together:
<header-component view="root.view" on-view-change="root.view = $event.view"></header-component>
<main-component view="root.view"></main-component>
Here is a plunker.

ng-show - using a service as a scope parameter

I'm writing an angular 1.5.0-rc0 application using bootstrap for a nav bar component.
I want to show the user an added items to his navigation bar if his user group id is 1.
first I created a service:
app.factory('UserService', function() {
return {
userGroupId : null
};
});
I created the nav bar as a directive, so i included it in the main html file
<nav-bar></nav-bar>
and the nav-bar directive code:
(function () {
angular.module('myalcoholist').directive('navBar', function () {
return {
restrict: 'E',
templateUrl: 'views/nav.html',
controller: ['$scope','$auth', 'UserService',function ($scope,$auth,UserService) {
$scope.user=UserService;
$scope.isAuthenticated = function()
{
return $auth.isAuthenticated();
};
}]
}
});
})();
as you can see I set $scope.user as the returned object from UserService.
in my login controller, after a successful login I set the userGroupId.
angular.module('myalcoholist').controller('LoginController',['$scope','$auth','$location', 'toastr','UserService',function ($scope,$auth,$location,toastr,UserService) {
$scope.authenticate = function (provider) {
$auth.authenticate(provider).then(function (data) {
var accessToken = data.data.token;
apiKey=accessToken;
UserService.userGroupId=data.data.user_group_id;
...
now.. my nav-bar template file is as the following code:
<li ng-show="user.userGroupId == 1">
Admin Drinks
</li>
even after the authentication, when I uset userGroupId to 1 the element is still not shown.
any ideas?
update
I debugged and noticed that UserService.userGroupId is still null. so
I changed the UserService to have the following code:
app.factory('UserService', function() {
var user = {userGroupId:null};
return {
setUserGroupId: function (userGroupId) {
user.userGroupId=setUserGroupId;
},
getUserGroupId: function () {
return user.userGroupId;
}
};
});
in my LoginController I now try to execute setUserGroupId:
angular.module('myalcoholist').controller('LoginController',['$scope','$auth','$location', 'toastr','UserService',function ($scope,$auth,$location,toastr,UserService) {
$scope.authenticate = function (provider) {
$auth.authenticate(provider).then(function (data) {
var accessToken = data.data.token;
apiKey=accessToken;
UserService.setUserGroupId(data.data.user_group_id);
...
when I debug i see that userService is an object with two functions as I defined, but when the javascript chrome debugger tries to execute this line:
UserService.setUserGroupId(data.data.user_group_id);
I get the following error:
ReferenceError: setUserGroupId is not defined
at Object.setUserGroupId (app.js:21)
at login-controller.js:12
at angular.js:15287
at m.$eval (angular.js:16554)
at m.$digest (angular.js:16372)
at m.$apply (angular.js:16662)
at g (angular.js:11033)
at t (angular.js:11231)
at XMLHttpRequest.v.onload (angular.js:11172)
I have created a fiddle showcasing your requirement (as close as possible), and it seems to work fine.
http://jsfiddle.net/HB7LU/21493/
My guess is that you aren't actually setting the value when you think you are, and will likely require some debugging. Here is the code for brevity.
HTML
<div ng-controller="MyCtrl">
<div ng-click="clicked()">
Click ME, {{user.value}}!
</div>
<test-dir></test-dir>
</div>
JS
angular.module('myApp',[])
.service('TestService', function(){
return {
value: 2
};
})
.directive('testDir', function(){
return {
restrict: 'E',
template: '<div ng-show="user.value === 1">Here is some text</div><div>Some more always showing</div>',
controller: function ($scope, TestService) {
$scope.user = TestService;
}
};
})
.controller('MyCtrl', function($scope, TestService){
$scope.user = TestService;
$scope.clicked = function(){
TestService.value = 1;
};
});

Recursion in Angular directives

There are a couple of popular recursive angular directive Q&A's out there, which all come down to one of the following solutions:
manually incrementally 'compile' HTML based on runtime scope state
example 1 [stackoverflow]
example 2 [angular jsfiddles page]
don't use a directive at all, but a <script> template which refers to itself
example 1 [google groups]
The first one has the problem that you can't remove previously compiled code unless you comprehensibly manage the manual compile process. The second approach has the problem of... not being a directive and missing out on its powerful capabilities, but more urgently, it can't be parameterised the same way a directive can be; it's simply bound to a new controller instance.
I've been playing with manually doing an angular.bootstrap or #compile() in the link function, but that leaves me with the problem of manually keeping track of elements to remove and add.
Is there a good way to have a parameterized recursive pattern that manages adding/removing elements to reflect runtime state? That is to say, a tree with a add/delete node button and some input field whose value is passed down a node's child nodes. Perhaps a combination of the second approach with chained scopes (but I have no idea how to do this)?
Inspired by the solutions described in the thread mentioned by #dnc253, I abstracted the recursion functionality into a service.
module.factory('RecursionHelper', ['$compile', function($compile){
return {
/**
* Manually compiles the element, fixing the recursion loop.
* #param element
* #param [link] A post-link function, or an object with function(s) registered via pre and post properties.
* #returns An object containing the linking functions.
*/
compile: function(element, link){
// Normalize the link parameter
if(angular.isFunction(link)){
link = { post: link };
}
// Break the recursion loop by removing the contents
var contents = element.contents().remove();
var compiledContents;
return {
pre: (link && link.pre) ? link.pre : null,
/**
* Compiles and re-adds the contents
*/
post: function(scope, element){
// Compile the contents
if(!compiledContents){
compiledContents = $compile(contents);
}
// Re-add the compiled contents to the element
compiledContents(scope, function(clone){
element.append(clone);
});
// Call the post-linking function, if any
if(link && link.post){
link.post.apply(null, arguments);
}
}
};
}
};
}]);
Which is used as follows:
module.directive("tree", ["RecursionHelper", function(RecursionHelper) {
return {
restrict: "E",
scope: {family: '='},
template:
'<p>{{ family.name }}</p>'+
'<ul>' +
'<li ng-repeat="child in family.children">' +
'<tree family="child"></tree>' +
'</li>' +
'</ul>',
compile: function(element) {
// Use the compile function from the RecursionHelper,
// And return the linking function(s) which it returns
return RecursionHelper.compile(element);
}
};
}]);
See this Plunker for a demo.
I like this solution best because:
You don't need an special directive which makes your html less clean.
The recursion logic is abstracted away into the RecursionHelper service, so you keep your directives clean.
Update:
As of Angular 1.5.x, no more tricks are required, but works only with template, not with templateUrl
Manually adding elements and compiling them is definitely a perfect approach. If you use ng-repeat then you will not have to manually remove elements.
Demo: http://jsfiddle.net/KNM4q/113/
.directive('tree', function ($compile) {
return {
restrict: 'E',
terminal: true,
scope: { val: '=', parentData:'=' },
link: function (scope, element, attrs) {
var template = '<span>{{val.text}}</span>';
template += '<button ng-click="deleteMe()" ng-show="val.text">delete</button>';
if (angular.isArray(scope.val.items)) {
template += '<ul class="indent"><li ng-repeat="item in val.items"><tree val="item" parent-data="val.items"></tree></li></ul>';
}
scope.deleteMe = function(index) {
if(scope.parentData) {
var itemIndex = scope.parentData.indexOf(scope.val);
scope.parentData.splice(itemIndex,1);
}
scope.val = {};
};
var newElement = angular.element(template);
$compile(newElement)(scope);
element.replaceWith(newElement);
}
}
});
I don't know for sure if this solution is found in one of the examples you linked or the same basic concept, but I had a need of a recursive directive, and I found a great, easy solution.
module.directive("recursive", function($compile) {
return {
restrict: "EACM",
priority: 100000,
compile: function(tElement, tAttr) {
var contents = tElement.contents().remove();
var compiledContents;
return function(scope, iElement, iAttr) {
if(!compiledContents) {
compiledContents = $compile(contents);
}
iElement.append(
compiledContents(scope,
function(clone) {
return clone; }));
};
}
};
});
module.directive("tree", function() {
return {
scope: {tree: '='},
template: '<p>{{ tree.text }}</p><ul><li ng-repeat="child in tree.children"><recursive><span tree="child"></span></recursive></li></ul>',
compile: function() {
return function() {
}
}
};
});​
You should create the recursive directive and then wrap it around the element that makes the recursive call.
As of Angular 1.5.x, no more tricks are required, the following has been made possible. No more need for dirty work arounds!
This discovery was a by product of my hunt for a better/cleaner solution for a recursive directive. You can find it here https://jsfiddle.net/cattails27/5j5au76c/. It supports as far is 1.3.x.
angular.element(document).ready(function() {
angular.module('mainApp', [])
.controller('mainCtrl', mainCtrl)
.directive('recurv', recurveDirective);
angular.bootstrap(document, ['mainApp']);
function recurveDirective() {
return {
template: '<ul><li ng-repeat="t in tree">{{t.sub}}<recurv tree="t.children"></recurv></li></ul>',
scope: {
tree: '='
},
}
}
});
function mainCtrl() {
this.tree = [{
title: '1',
sub: 'coffee',
children: [{
title: '2.1',
sub: 'mocha'
}, {
title: '2.2',
sub: 'latte',
children: [{
title: '2.2.1',
sub: 'iced latte'
}]
}, {
title: '2.3',
sub: 'expresso'
}, ]
}, {
title: '2',
sub: 'milk'
}, {
title: '3',
sub: 'tea',
children: [{
title: '3.1',
sub: 'green tea',
children: [{
title: '3.1.1',
sub: 'green coffee',
children: [{
title: '3.1.1.1',
sub: 'green milk',
children: [{
title: '3.1.1.1.1',
sub: 'black tea'
}]
}]
}]
}]
}];
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
<div>
<div ng-controller="mainCtrl as vm">
<recurv tree="vm.tree"></recurv>
</div>
</div>
After using several workarounds for a while, I've repeatedly come back to this issue.
I'm not satisfied by the service solution since it works for directives that can inject the service but does not work for anonymous template fragments.
Similarly, solutions which depend upon specific template structure by doing DOM manipulation in the directive are too specific and brittle.
I have what I believe is a generic solution that encapsulates the recursion as a directive of its own that interferes minimally with any other directives and can be used anonymously.
Below is a demonstration that you can also play around with at plnkr: http://plnkr.co/edit/MSiwnDFD81HAOXWvQWIM
var hCollapseDirective = function () {
return {
link: function (scope, elem, attrs, ctrl) {
scope.collapsed = false;
scope.$watch('collapse', function (collapsed) {
elem.toggleClass('collapse', !!collapsed);
});
},
scope: {},
templateUrl: 'collapse.html',
transclude: true
}
}
var hRecursiveDirective = function ($compile) {
return {
link: function (scope, elem, attrs, ctrl) {
ctrl.transclude(scope, function (content) {
elem.after(content);
});
},
controller: function ($element, $transclude) {
var parent = $element.parent().controller('hRecursive');
this.transclude = angular.isObject(parent)
? parent.transclude
: $transclude;
},
priority: 500, // ngInclude < hRecursive < ngIf < ngRepeat < ngSwitch
require: 'hRecursive',
terminal: true,
transclude: 'element',
$$tlb: true // Hack: allow multiple transclusion (ngRepeat and ngIf)
}
}
angular.module('h', [])
.directive('hCollapse', hCollapseDirective)
.directive('hRecursive', hRecursiveDirective)
/* Demo CSS */
* { box-sizing: border-box }
html { line-height: 1.4em }
.task h4, .task h5 { margin: 0 }
.task { background-color: white }
.task.collapse {
max-height: 1.4em;
overflow: hidden;
}
.task.collapse h4::after {
content: '...';
}
.task-list {
padding: 0;
list-style: none;
}
/* Collapse directive */
.h-collapse-expander {
background: inherit;
position: absolute;
left: .5px;
padding: 0 .2em;
}
.h-collapse-expander::before {
content: '•';
}
.h-collapse-item {
border-left: 1px dotted black;
padding-left: .5em;
}
.h-collapse-wrapper {
background: inherit;
padding-left: .5em;
position: relative;
}
<!DOCTYPE html>
<html>
<head>
<link href="collapse.css" rel="stylesheet" />
<link href="style.css" rel="stylesheet" />
<script data-require="angular.js#1.3.15" data-semver="1.3.15" src="https://code.angularjs.org/1.3.15/angular.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js" data-semver="2.1.1" data-require="jquery#*"></script>
<script src="script.js"></script>
<script>
function AppController($scope) {
$scope.toggleCollapsed = function ($event) {
$event.preventDefault();
$event.stopPropagation();
this.collapsed = !this.collapsed;
}
$scope.task = {
name: 'All tasks',
assignees: ['Citizens'],
children: [
{
name: 'Gardening',
assignees: ['Gardeners', 'Horticulture Students'],
children: [
{
name: 'Pull weeds',
assignees: ['Weeding Sub-committee']
}
],
},
{
name: 'Cleaning',
assignees: ['Cleaners', 'Guests']
}
]
}
}
angular.module('app', ['h'])
.controller('AppController', AppController)
</script>
</head>
<body ng-app="app" ng-controller="AppController">
<h1>Task Application</h1>
<p>This is an AngularJS application that demonstrates a generalized
recursive templating directive. Use it to quickly produce recursive
structures in templates.</p>
<p>The recursive directive was developed in order to avoid the need for
recursive structures to be given their own templates and be explicitly
self-referential, as would be required with ngInclude. Owing to its high
priority, it should also be possible to use it for recursive directives
(directives that have templates which include the directive) that would
otherwise send the compiler into infinite recursion.</p>
<p>The directive can be used alongside ng-if
and ng-repeat to create recursive structures without the need for
additional container elements.</p>
<p>Since the directive does not request a scope (either isolated or not)
it should not impair reasoning about scope visibility, which continues to
behave as the template suggests.</p>
<p>Try playing around with the demonstration, below, where the input at
the top provides a way to modify a scope attribute. Observe how the value
is visible at all levels.</p>
<p>The collapse directive is included to further demonstrate that the
recursion can co-exist with other transclusions (not just ngIf, et al)
and that sibling directives are included on the recursive due to the
recursion using whole 'element' transclusion.</p>
<label for="volunteer">Citizen name:</label>
<input id="volunteer" ng-model="you" placeholder="your name">
<h2>Tasks</h2>
<ul class="task-list">
<li class="task" h-collapse h-recursive>
<h4>{{task.name}}</h4>
<h5>Volunteers</h5>
<ul>
<li ng-repeat="who in task.assignees">{{who}}</li>
<li>{{you}} (you)</li>
</ul>
<ul class="task-list">
<li h-recursive ng-repeat="task in task.children"></li>
</ul>
<li>
</ul>
<script type="text/ng-template" id="collapse.html">
<div class="h-collapse-wrapper">
<a class="h-collapse-expander" href="#" ng-click="collapse = !collapse"></a>
<div class="h-collapse-item" ng-transclude></div>
</div>
</script>
</body>
</html>
Now that Angular 2.0 is out in preview I think it's ok to add an Angular 2.0 alternative into the mix. At least it will benefit people later:
The key concept is to build a recursive template with a self reference:
<ul>
<li *for="#dir of directories">
<span><input type="checkbox" [checked]="dir.checked" (click)="dir.check()" /></span>
<span (click)="dir.toggle()">{{ dir.name }}</span>
<div *if="dir.expanded">
<ul *for="#file of dir.files">
{{file}}
</ul>
<tree-view [directories]="dir.directories"></tree-view>
</div>
</li>
</ul>
You then bind a tree object to the template and watch the recursion take care of the rest.
Here is a full example: http://www.syntaxsuccess.com/viewarticle/recursive-treeview-in-angular-2.0
There is a really really simple workaround for this that does not require directives at all.
Well, in that sense, maybe it is not even a solution of the original problem if you assume you need directives, but it IS a solution if you want a recursive GUI structure with parametrized sub-structures of the GUI. Which is probably what you want.
The solution is based on just using ng-controller, ng-init and ng-include. Just do it as follows, assume that your controller is called "MyController", your template is located in myTemplate.html and that you have an initialization function on your controller called init that takes argument A, B, and C, making it possible to parametrize your controller. Then the solution is as follows:
myTemplate.htlm:
<div>
<div>Hello</div>
<div ng-if="some-condition" ng-controller="Controller" ng-init="init(A, B, C)">
<div ng-include="'myTemplate.html'"></div>
</div>
</div>
I found by plain conincidence that this kind of structure can be made recursive as you like in plain vanilla angular. Just follow this design pattern and you can use recursive UI-structures without any advanced compilation tinkering etc.
Inside your controller:
$scope.init = function(A, B, C) {
// Do something with A, B, C
$scope.D = A + B; // D can be passed on to other controllers in myTemplate.html
}
The only downside I can see is the clunky syntax you have to put up with.
You can use angular-recursion-injector for that: https://github.com/knyga/angular-recursion-injector
Allows you to do unlimited depth nesting with conditioning. Does recompilation only if needed and compiles only right elements. No magic in code.
<div class="node">
<span>{{name}}</span>
<node--recursion recursion-if="subNode" ng-model="subNode"></node--recursion>
</div>
One of the things that allows it to work faster and simpler then the other solutions is "--recursion" suffix.
I ended up creating a set of basic directives for recursion.
IMO It is far more basic than the solution found here, and just as flexible if not more, so we are not bound to using UL/LI structures etc... But obviously those make sense to use, however the directives are unaware of this fact...
A Super simple example would be:
<ul dx-start-with="rootNode">
<li ng-repeat="node in $dxPrior.nodes">
{{ node.name }}
<ul dx-connect="node"/>
</li>
</ul>
The implementation of 'dx-start-with' an 'dx-connect' is found at: https://github.com/dotJEM/angular-tree
This means you don't have to create 8 directives if you need 8 different layouts.
To create a tree-view on top of that where you can add or delete nodes would then be rather simple. As in: http://codepen.io/anon/pen/BjXGbY?editors=1010
angular
.module('demo', ['dotjem.angular.tree'])
.controller('AppController', function($window) {
this.rootNode = {
name: 'root node',
children: [{
name: 'child'
}]
};
this.addNode = function(parent) {
var name = $window.prompt("Node name: ", "node name here");
parent.children = parent.children || [];
parent.children.push({
name: name
});
}
this.removeNode = function(parent, child) {
var index = parent.children.indexOf(child);
if (index > -1) {
parent.children.splice(index, 1);
}
}
});
<div ng-app="demo" ng-controller="AppController as app">
HELLO TREE
<ul dx-start-with="app.rootNode">
<li><button ng-click="app.addNode($dxPrior)">Add</button></li>
<li ng-repeat="node in $dxPrior.children">
{{ node.name }}
<button ng-click="app.removeNode($dxPrior, node)">Remove</button>
<ul dx-connect="node" />
</li>
</ul>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>
<script src="https://rawgit.com/dotJEM/angular-tree-bower/master/dotjem-angular-tree.min.js"></script>
</div>
From this point on, the controller and template could be wrapped in it's own directive if one would wish for it.

Categories