i am beginner in angular and i have a dynamic list like this
{.name:superParent,id:0,parrentId:null}
{.name:a,id:1,parrentId:0}
{.name:b,id:2,parrentId:0}
{.name:c,id:3,parrentId:1}
{.name:e,id:4,parrentId:3}
{.name:d,id:5,parrentId:2}
how can i implement it as nested list using angular and Recursive template? and also i want to keep each item-id in it self in the some attribute or property
i create this , but it dose not show the tree ,i can see the data only when i use ng-repeat in li
<ul>
<li ng-repeat="col in tree">{{col.Name}}</li>
</ul>
</div>
</div>
<script>
var app = angular.module('APP', []);
app.controller('IndexCtrl', function ($scope, $http) {
$scope.tree = [];
$scope.getList = function () {
//get data from server
$http.get("/person/GetTree")
.then(function (data) {
console.log("data:", data);
$scope.tree = data.data;
$scope.loading = false;
} )
}
$scope.getList();
});
app.directive('treeView', ['$compile', function($compile) {
return {
priority : 0,
restrict : "E",
replace : true,
scope : {
head: "=head",
children: "=children"
},
template: '<div><h4 id="{{head.id}}" ng-show="head.id>0">{{head.Name}}</h4> <ul> <li ng-repeat="item in items"> <tree-view head="item" children="children"></tree-view> </li> </ul></div>',
controller : ['$scope', function ($scope) {
var array = $scope.children;
var head = $scope.head;
//I used plugin linq.js
$scope.items = array.filter(function(val){
return val.parrentId==head.id;
});
}],
compile : 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);
});
};
}
};
}]);
You can see example.
First I added main head item into your collection (it will be super parent for all other items):
{ name: "superparent", id: 0, parentId: null }
Then your collection will be looks like this:
tree = [
{name:'superparent',id:0,parrentId:Null},
{name:'a',id:1,parrentId:0},
{name:'b',id:2,parrentId:0},
{name:'c',id:3,parrentId:1},
{name:'e',id:4,parrentId:3},
{name:'d',id:5,parrentId:2},
];
All what we can do - create recursive directive (treeView) with such html template:
<div>
<h4 id="{{head.id}}" ng-show="head.id>0">{{head.name}}</h4>
<ul>
<li ng-repeat="item in items">
<tree-view head="item" children="children"></tree-view>
</li>
</ul>
</div>
I used TypeScript, but I sure you will easily undestand this code (d.compile function was taken from this answer):
module Directives {
export interface TreeItem {
name: string;
parrentId?: number
id: number;
}
TreeView.$inject = ['$compile'];
export function TreeView($compile): ng.IDirective {
var d: ng.IDirective = {};
d.priority = 0;
d.restrict = "E";
d.replace = true;
d.scope = {
head: "=head",
children: "=children"
};
d.templateUrl = '/somepath/treeView.html';
d.controller = ['$scope', function ($scope) {
var array: Array<TreeItem> = $scope.children;
var head: TreeItem = $scope.head;
$scope.items = array.filter(function(val){
return val.parrentId==head.id;
});
}];
d.compile = 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);
});
};
};
return d;
}
}
At the end, into your html page, directive will be inserted this way, where tree[0] as you guessed is super parent:
<tree-view head="tree[0]" children="tree" ng-if="tree"></tree-view>
Related
I am new to AngularJS. I have created the following controller that display a list of results and that opens a modal when a specific button is clicked:
angular.
module('panelList')
.component('panelList', {
templateUrl: '/panel-list/panel-list.template.html',
controller: ['Panel', 'PanelSelection', '$scope', '$location', '$uibModal',
function PanelListController(Panel, PanelSelection, $scope, $location, $uibModal) {
$scope.maxAbv = 2;
$scope.minAbv = 12;
$scope.maxIbu = 0;
$scope.minIbu = 100;
this.allPanelsRetrieved = (index, before, filterParams) => {
let allPanels = before;
const params = Object.assign({},
{ page: index, per_page: 80 },
filterParams);
Panel.query(params).$promise.then(data => {
if (data.length > 0) {
allPanels.push(...data);
return this.allPanelsRetrieved(index+1, allPanels, filterParams);
} else {
return allPanels;
}
});
return allPanels;
};
$scope.getPanels = () => {
const filterParams = {};
filterParams.abv_lt = $scope.minAbv;
filterParams.abv_gt = $scope.maxAbv;
filterParams.ibu_lt = $scope.minIbu;
filterParams.ibu_gt = $scope.maxIbu;
$scope.currentPagePanels = this.allPanelsRetrieved(1,[], filterParams);
};
$scope.showDetails = (panelSelected) => {
PanelSelection.setPanelSelected(panelSelected);
$uibModal.open({
component: "panelDetail",
scope: $scope,
bindToController: true,
})
};
}]
});
The controller for the modal is specified here:
angular.
module('panelDetail').
component('panelDetail', {
templateUrl: '/panel-detail/panel-detail.template.html',
controller: ['PanelSelection', '$scope','$uibModal',
function PanelDetailController(PanelSelection, $scope, $uibModal, $uibModalInstance) {
$scope.ok = () => {
$uibModalInstance.close();
};
let panelSelected = PanelSelection.getPanelSelected();
$scope.panel = panelSelected;
console.log(panelSelected);
$scope.foodPairings = panelSelected.food_pairing.join(", ");
$scope.allIngredients = this.getFormattedIngredients(panelSelected.ingredients);
$scope.method = this.getFormattedMethod(panelSelected.method);
this.getFormattedIngredients = (ingredients) => {
const listOfIngredients = [];
Object.keys(ingredients).forEach(key => {
if(Array.isArray(ingredients[key])){
for(let ingredient of ingredients[key]){
listOfIngredients.push(
`- ${ingredient.name} ${key} (${ingredient.amount.value} ${ingredient.amount.unit})`
.concat(ingredient.add != undefined ? ', added in the '+ingredient.add:'',
ingredient.attribute != undefined ? ', attribute: '+ingredient.attribute:'','.')
);
}
}else{
listOfIngredients.push(`- ${ingredients[key]} ${key}.`);
}
});
return listOfIngredients;
};
$scope.getFormattedMethod = (method) => {
const listOfMethodProcedures = [];
Object.keys(method).forEach(key => {
if(Array.isArray(method[key])){
for(let methodProcedure of method[key]){
listOfMethodProcedures.push(
`- ${key} at ${methodProcedure.temp.value} ${methodProcedure.temp.unit} `
.concat(methodProcedure.duration != undefined ? 'for '+methodProcedure.duration +' min.' : '.')
);
}
}else{
listOfMethodProcedures.push(`- ${key}.`);
}
});
return listOfMethodProcedures;
};
}
]
});
The modal is open correctly but the values inside are not taken from the scope, as they should, but they are displayed as {{value}}. In few words, the $scope passed doesn't act as scope. Moreover I get the following error:
TypeError: this.getFormattedIngredients is not a function
at new PanelDetailController
Where the error may be? How to pass successfully a scope from one controller to another for modal?
Instead of scope: $scope pass values using
resolve: { scope: $scope }
You are calling the getFormattedIngredients function before it gets declared. So this is not a $scope issue. You need to declare the function before it gets called. One way to solve such an issue is going with the angular Styleguide provided by John Papa. Angular Styleguide
Assign your function at the top of your Component/Controller/Service and use function expressions instead of function declarations.
function PanelDetailController(PanelSelection, $scope, $uibModal,$uibModalInstance) {
this.getFormattedIngredients = getFormattedIngredients;
// You can call your function from here without getting an error
// Other Code..
function getFormattedIngredients() {}
I ran into a situation in AngularJS where I want to use a template that appends to the same template when data is changed instead of replacing the existing template. Directive can be something like this:
<my-template data="myData"></my-template>
template.html can be like:
<p>ID: {{data.id}}, Name: {{data.name}}</p>
The data will contain single data, so when I change data I want above template to append instead of replacing it. So the output will be like this:
ID: 1, Name: John
ID: 2, Name: Michael
ID: 3, Name: Abraham
I also want to handle onClick when the user tap on any name above.
How can I achieve this?
Ok i got it ! try this
angular.module('myApp', [])
.controller('myController', function($scope, $interval) {
var count = 0;
var addPerson = function() {
count++
$scope.person = {
name: 'person' + count,
age: 20 + count
};
};
var timer = $interval(addPerson, 1000);
})
.directive('myDirective', function($compile) {
return {
restrict: "A",
transclude: true,
scope: "=",
link: function(scope, element, attrs) {
scope.handleClick = function(event) {
console.log(event.target.innerText)
}
scope.$watch(function() {
return scope.person;
}, function(obj) {
var elementToRender = angular.element('<div ng-click="handleClick($event)">' + obj.name + '</div>');
function renderDiv() {
return elementToRender
}
element.append(renderDiv());
$compile(elementToRender)(scope);
}, false);
}
};
});
template
<div ng-app="myApp" ng-controller="myController">
<div my-directive data="{{person}}"></div>
</div>
I neeed to pass a value from this part of the code in my directive to a controller, but not sure how to achieve that:
if (!scope.multiple) {
scope.model = value;
console.log(scope.model);
return;
}
I get the value in the console.log, I just don't know how to pass it to the controller.
This is the complete directive:
angular.module('quiz.directives')
.directive('fancySelect', function($rootScope, $timeout) {
return {
restrict: 'E',
templateUrl: 'templates/directives/fancySelect.html',
scope: {
title: '#',
model: '=',
options: '=',
multiple: '=',
enable: '=',
onChange: '&',
class: '#'
},
link: function(scope) {
scope.showOptions = false;
scope.displayValues = [];
scope.$watch('enable', function(enable) {
if (!enable && scope.showOptions) {
scope.toggleShowOptions(false);
}
});
scope.toggleShowOptions = function(show) {
if (!scope.enable) {
return;
}
if (show === undefined) {
show = !scope.showOptions;
}
if (show) {
$rootScope.$broadcast('fancySelect:hideAll');
}
$timeout(function() {
scope.showOptions = show;
});
};
scope.toggleValue = function(value) {
if (!value) {
return;
}
if (!scope.multiple) {
scope.model = value;
console.log(scope.model);
return;
}
var index = scope.model.indexOf(value);
if (index >= 0) {
scope.model.splice(index, 1);
}
else {
scope.model.push(value);
}
if (scope.onChange) {
scope.onChange();
}
};
scope.getDisplayValues = function() {
if (!scope.options || !scope.model) {
return [];
}
if (!scope.multiple && scope.model) {
return scope.options.filter(function(opt) {
return opt.id == scope.model;
});
}
return scope.options.filter(function(opt) {
return scope.model.indexOf(opt.id) >= 0;
});
};
$rootScope.$on('fancySelect:hideAll', function() {
scope.showOptions = false;
});
}
};
});
Updated
I tried to do as suggested in the answers by #Zidane and defining my object first in the controller like this:
$scope.year = {};
var saveUser = function(user) {
$scope.profilePromise = UserService.save(user);
console.log($scope.year);
This is the template:
<fancy-select
title="Klassetrinn"
model="year"
options="years"
enable="true"
on-change="onChangeYears()"
active="yearsActive"
name="playerYear"
form-name="registerForm"
>
</fancy-select>
But I got an empty object in that case.
When I define my objects like this I get the right value in the controller but in the view the title is not being displayed anymore:
$scope.search = {
years: []
};
var saveUser = function(user) {
$scope.profilePromise = UserService.save(user);
console.log($scope.search.years);
<fancy-select
title="Klassetrinn"
model="search.years"
options="years"
enable="true"
on-change="onChangeYears()"
active="yearsActive"
name="playerYear"
form-name="registerForm"
>
</fancy-select>
As you defined an isolated scope for your directive like this
scope: {
...
model: '=',
...
},
you give your directive a reference to an object on your controller scope.
Declaring the directive like <fancy-select model="myModel" ....></fancy-select> you pass your directive a reference to scope.myModel on your controller. When you modify a property on the scope.model object in your directive you automatically modify the same property on the scope.myModel object in your controller.
So you have to do
myApp.controller('myController', function($scope) {
...
$scope.myModel = {};
...
}
in your controller and in your directive just do
if (!scope.multiple) {
scope.model.value = value;
return;
}
Then you can get the value in your controller via $scope.myModel.value.
For clarification: You have to define an object on your controller and pass the directive the reference for this object so that the directive can follow the reference and doesn't mask it. If you did in your directive scope.model = 33 then you would just mask the reference passed to it from the controller, which means scope.model wouldn't point to the object on the controller anymore. When you do scope.model.value = 33 then you actually follow the object reference and modify the object on the controller scope.
you can use services or factories to share data between your angular application parts, for example
angular.module('myapp').factory('myDataSharing', myDataSharing);
function myDataSharing() {
var sharedData = {
fieldOne: ''
};
return {
setData: setData,
getData: getData,
};
function setData(dataFieldValue) {
sharedData.fieldOne = dataFieldValue;
};
function getData() {
sharedData.fieldOne
};
directive:
myDataSharing.setData(dataValue);
controller:
angular.module('myapp').controller('myController' ['myDataSharing'], function(myDataSharing) {
var myDataFromSharedService = myDataSharing.getData();
}
I'm pretty new to angular js, currently, it's going well but now I have a question.
I have a template with a topnav and a contentpart. All with its own controller.
With a button I can open a "submenu" where I can choose data from the database, within the "Liquid"-section. Thats working pretty well.
Since the Topnav is rendered at the login of the page, the topnav wont be rendered again.
If I add a Liquid in the content section, I have to reload the data behind the "Liquid"-Dropdown.
That dropdown is encapsulated in a directive:
function liquidselect(Data){
return {
restrict: 'AE',
templateUrl: 'views/controls/liquid_select.html',
scope: {
selectedValues : '='
},
link: function(scope) {
},
controller: function ($scope) {
//
Data.post('ownrecipes').then(function (results) {
$scope.searchRes = results;
});
// debugger;
//$scope.searchRes = RecipeDataService.data;
$scope.disabled = undefined;
$scope.searchEnabled = false;
$scope.searchRes = [];
$scope.flavoring = {amount: {}};
$scope.updateModelValue = function (selected) {
// console.log(selected);
//
$scope.selectedValues = selected;
};
}
}
}
The communication to the server is done by a factory:
app.factory("Data", ['$http', 'toaster',
function ($http, toaster ) { // This service connects to our REST API
// var deffered = $q.defer();
var serviceBase = 'api/v1/';
var obj = {};
obj.toast = function (data) {
toaster.pop(data.status, "", data.message, 3000, 'trustedHtml');
}
obj.get = function (q) {
return $http.get(serviceBase + q).then(function (results) {
return results.data;
});
};
obj.post = function (q, object) {
return $http.post(serviceBase + q, object).then(function (results) {
return results.data;
});
};
obj.put = function (q, object) {
return $http.put(serviceBase + q, object).then(function (results) {
return results.data;
});
};
obj.delete = function (q) {
return $http.delete(serviceBase + q).then(function (results) {
return results.data;
});
};
return obj;
}]);
How can I update/reload the data of the directive from a child scope? Is there any chance?
Here is a plunker which showcases my problem and want I want to do:
http://plnkr.co/edit/bNANkQYZfBaS4CHH3dwX
I hope it's helpful for you :-)
Layout:
Basic Layout
I am using a factory to be call in two controllers that are in same page, however I don't want that this call AJAX two times. I'm try use $q.defer(), but it doesn't work!
The code:
(function () {
'use strict';
var app = angular.module('globalApp', []).config([
'$interpolateProvider', '$httpProvider',
function ($interpolateProvider, $httpProvider) {
delete $httpProvider.defaults.headers.common['X-Requested-With'];
$interpolateProvider.startSymbol('<%');
$interpolateProvider.endSymbol('%>');
}
]);
// Index Store List
app.controller('IndexController', ['$scope', '$storeList',
function ($scope, $storeList) {
$scope.stores = [];
$storeList.getStoreList().then(function (stores) {
$scope.stores = stores;
});
}]);
// Footer Store List
app.controller('StoreListController', [
'$scope', '$storeList',
function ($scope, $storeList) {
$scope.stores = [];
$storeList.getStoreList().then(function (stores) {
$scope.stores = stores;
});
}
]);
// Factory call list os stores
app.factory('$storeList', function ($http, $q) {
var stores = [];
return {
getStoreList: function () {
//is a request needed?
if (stores.length > 0) {
var deferred = $q.defer();
deferred.resolve(stores);
return deferred.promise;
}
var uri = rootUrl + 'getStoreList';
var responsePromise = $http.get(uri);
return responsePromise.then(function (result) {
var data = result.data;
//modify collection of stores...
if (data.status) {
var stores = data.stores;
return stores;
}
});
}
};
});
}());
<body ng-app="globalApp">
<section>
<div ng-controller="IndexController">
<nav>
<a ng-repeat="store in stores" href="<%store.link%>"><%store.name%></a>
</nav>
</div>
</section>
<footer>
<div>
<ul ng-controller="StoreListController">
<li ng-repeat="store in stores">
<h3><%store.name%></h3>
<p>
<%store.street%>, <%store.number%>
<%store.neighborhood%>, <%store.cityName%>
<a><%store.phone%></a>
<a><%store.email%></a>
</p>
</li>
</ul>
</div>
</footer>
</body>
If both controllers load at the same time and call the function, stores will not contain any data so it's length will evaluate to 0.
If you need to do this onLoad of the page why don't you call the function from a service and store the results. You can utilize an observer pattern in the controls to listen for changes to the set and persist those changes to both controllers to set the value of $scope.stores once the promise resolves.
My solution:
(function () {
'use strict';
// My configs (optional)
var app = angular.module('globalApp', []).config([
'$interpolateProvider', '$httpProvider',
function ($interpolateProvider, $httpProvider) {
delete $httpProvider.defaults.headers.common['X-Requested-With'];
$interpolateProvider.startSymbol('<%');
$interpolateProvider.endSymbol('%>');
}
]);
// Index Store List
app.controller('IndexController', [
'$scope', '$storeList', 'StoreService',
function ($scope, $storeList, StoreService) {
$scope.stores = [];
$storeList.getStoreList().then(function (stores) {
$scope.stores = stores;
StoreService.setStores(stores);
});
}]);
// Footer Store List
app.controller('StoreListController', [
'$scope', 'StoreService',
function ($scope, StoreService) {
$scope.stores = [];
$scope.$watch(function () {
return StoreService.getStores();
}, function (stores) {
$scope.stores = stores;
}, true);
}
]);
// StoreService to share vars between the controllers
app.service('StoreService', function () {
var stores = [];
return {
setStores: function (_stores) {
stores = _stores;
},
getStores: function () {
return stores;
}
};
});
// Factory make ajax call to get list of stores
app.factory('$storeList', function ($http) {
return {
getStoreList: function () {
var uri = rootUrl + 'getStoreList';
var responsePromise = $http.get(uri);
return responsePromise.then(function (result) {
var data = result.data;
if (data.status) {
return data.stores;
}
return [];
});
}
};
});
}());
<body ng-app="globalApp">
<section>
<div ng-controller="IndexController">
<nav>
<a ng-repeat="store in stores" href="<%store.link%>"><%store.name%></a>
</nav>
</div>
</section>
<footer>
<div>
<ul ng-controller="StoreListController">
<li ng-repeat="store in stores">
<h3><%store.name%></h3>
<p>
<%store.street%>, <%store.number%>
<%store.neighborhood%>, <%store.cityName%>
<a><%store.phone%></a>
<a><%store.email%></a>
</p>
</li>
</ul>
</div>
</footer>
</body>
You don't need to have defer here. Remove this chunk:
//is a request needed?
if (stores.length > 0) {
var deferred = $q.defer();
deferred.resolve(stores);
return deferred.promise;
}
And your IndexController and StoreListController are both loading the storelist and hence 2 AJAX calls. Remove the chunk from either of the 2 controllers
$scope.stores = [];
$storeList.getStoreList().then(function (stores) {
$scope.stores = stores;
});
EDIT
I Did not realize earlier, but since you are using the storeList outside the scope of your StoreController, You should remove the .getStoreList() call from StoreController only