Error: [$compile:multidir] Multiple directives [ngSwitchWhen, ngInclude] asking for transclusion - angularjs

I need a solution for preloading data with ng-include like in this blog post preloading-data-before-executing-nginclude-in-angularjs described.
It seems that this solution works prity well, but the author works with angular version 1.0.7.
Im using version 1.2.1 and have also tried version 1.7.9 and the result is the error message.
Error: [$compile:multidir] Multiple directives [ngSwitchWhen, ngInclude] asking for transclusion on: <div ng-switch-when="one" bn-preload="oneData" bn-include-log="" ng-include=" 'one.htm' ">
How can i fix it with the newer version, or is there a better solution?
In my project, i receive data by reqest and the ng-include should be waiting till the request ends and the data to show are available.
Edit:
My HTML code is like this
<div ng-init="validityList = validities.UploadList;" bn-preload="validities" ng-include="'/validity/list'"></div>
And the error in my project is
Multiple directives [ngInclude, bnPreload] asking for transclusion on
The solution Lemmy suggestet is not working for me, because bn-preload and ng-include need to be on the same element.
Edit2:
I don't know what you mean by "bn-preload html".
This is what i have done so far in angular.
const BaseApp = angular.module('BaseApp')
.factory('preloader', function ($q, $interval) {
this.load = function (target) {
const deferred = $q.defer();
const loadData = $interval(function () {
if ($q[target].length > 0) {
deferred.resolve($q[target]);
$interval.cancel(loadData);
}
}, 100);
return (deferred.promise);
}
})
.directive('bnPreload', function (preloader) {
function compile(templateElement, templateAttribute, transclude) {
function link($scope, element, attributes) {
let injectedElement = null;
let isDestroyed = false;
preloader.load(attributes.bnPreload).then(
function (preloadedData) {
if (isDestroyed) {
return;
}
$scope.setData(preloadedData);
transclude($scope, function (copy) {
element.after(injectedElement = copy);
});
}
);
$scope.$on(
'$destroy',
function () {
isDestroyed = true;
$(injectedElement).remove();
}
);
}
return (link);
}
return ({
compile: compile,
priority: 250,
terminal: true,
transclude: 'element'
});
})
.controller('validity', function ($scope, $http) {
$http({
method: 'POST',
url: '/validity/getall'
})
.then(function (response) {
$scope.validities = response.data;
});
});
Most of the code is equal to the example from the link above.

Related

materialize Dropdwon in Angular js after ajax call

I have a master page and child page in MVC
All Js files are included in master page.Now in child page I have two dropdowns which gets bind with ajax returned data.
I can see data are being populated in select options but materialize css not created.
HTML
<select data-ng-init="getAllItems()" ng-model="Item[0]" ng-options="Item['title'] for Item in Items track by Item['id']">
AJAX
$scope.getAllItems = function () {
var result = ItemsFactory();
result.then(function (result) {
if (result.success) {
$scope.Items= (result.data);
}
});
}
I have used
$('select').material_select()
in a js file that is included on master page at the end,
So my thinking is the JS where I am using $('select').material_select() gets loaded before dropdown populations, but I have included it at the end,
I Manage to get it worked
$scope.getAllItems = function () {
var result = ItemsFactory();
result.then(function (result) {
if (result.success) {
$scope.Items= (result.data);
$('select').material_select()
}
});
}
$scope.$apply($scope.getAllItems ());
but on console I am getting error
[$rootScope:inprog]
any suggestion.
Thanks
So, you have two ways:
1) It is put your select initialization in then fuction. Not the good way
$scope.getAllItems = function () {
var result = ItemsFactory();
result.then(function (result) {
if (result.success) {
$scope.Items= (result.data);
$('select').material_select();
$scope.$apply();
}
});
}
2) Make a directive that wraps .material_select(). Better way
.directive('materialSelect', [function(){
return {
restrict: 'E',
scope: {
items: '='
},
link: function(scope, elem attrs) {
elem.material_select()
}
}
}])
<material-select items="items"></material-select>
UPDATE
$scope.getAllItems = function () {
var result = ItemsFactory();
result.then(function (result) {
if (result.success) {
$scope.Items= (result.data);
$('select').material_select()
$scope.$apply()
}
});
}
$scope.getAllItems ();

AngularJS open modal with specific data from array

So, i'm trying to add a modal to a page with a list of clients. When i open the modal i need it to have the data from that specific client.
I was able to do this when opening another pager, sending the id trought the url, but when i need to do this with the modal, i'm not able.
This is the code i have so far:
Note: Without trying to open the specific data from client, everything is working fine.
page.html
<div class="col-md-6 cli--text">
<h3>{{client.name}}</h3>
<p ng-bind-html="client.desc | html"></p>
<a ng-click="clickToOpen({{client.id}})">More</a>
</div>
app.js
myApp.controller('CliCtrl', function ( $scope, $http, $routeParams, modals, ngDialog) {
$scope.get_client = function() {
$http.get("scripts/data/client.json")
.success( function(data) {
$scope.pagedclient = data;
})
.error(function(data) {
alert("Something wrong.");
});
};
$scope.clickToOpen = function (data) {
ngDialog.open({
template: 'scripts/data/modal.html',
closeByDocument: true,
closeByEscape: true
});
function getById(arr, id) {
for (var d = 0, len = arr.length; d < len; d += 1) {
if (arr[d].id === id) {
return arr[d];
}
}
}
$scope.get_client().then(function(){
$scope.clients = getById($scope.detRes,data);
});
$scope.nameclient = clients.name;
};
});
modal.html
<div class="modal--body">
<h2>Modal template</h2>
<h3>{{nameclient}}</h3>
</div>
I'm using this modal plugin, but since i'm new to AngularJS, i don't know exactly what to do.
In the README for the plugin that you are using, it says that you can specify the scope variable that the modal will use. So try modifying your code to something like
ngDialog.open({
template: 'scripts/data/modal.html',
closeByDocument: true,
closeByEscape: true,
scope: $scope
});
This should give your modal access to $scope.nameclient which will allow {{nameclient}} to be evaluated correctly.
You can use resolve function to pass data to your modal template and its controller:
resolve: {
myArray: function passArray() {
return $scope.myArray;
}
}
Check documentation here
The first problem i had was to send the id from the client to the controller. Instead of using ng-click="clickToOpen({{client.id}})" i used it on an id like: id="{{client.id}}. Then i just had to get the value and run the function to filter the client by his ID and send to the modal plugin.
Like #Arafeek said, i had to use his code to complement the function, and the result was this:
$scope.clickToOpen = function (event) {
ngDialog.open({
template: 'scripts/data/modal.html',
scope: $scope
});
$scope.idCli = (event.target.id);
$scope.clients = getById($scope.pagedclient,$scope.idCli);
};
function getById(arr, id) {
for (var d = 0, len = arr.length; d < len; d += 1) {
if (arr[d].id === id) {
return arr[d];
}
}
}
And inside the modal i just access the data normally: {{clients.name}} etc...

Load Angular Directive Template Async

I want to be able to load the directive's template from a promise. e.g.
template: templateRepo.get('myTemplate')
templateRepo.get returns a promise, that when resolved has the content of the template in a string.
Any ideas?
You could load your html inside your directive apply it to your element and compile.
.directive('myDirective', function ($compile) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
//Some arbitrary promise.
fetchHtml()
.then(function(result){
element.html(result);
$compile(element.contents())(scope);
}, function(error){
});
}
}
});
This is really interesting question with several answers of different complexity. As others have already suggested, you can put loading image inside directive and when template is loaded it'll be replaced.
Seeing as you want more generic loading indicator solution that should be suitable for other things, I propose to:
Create generic service to control indicator with.
Manually load template inside link function, show indicator on request send and hide on response.
Here's very simplified example you can start with:
<button ng-click="more()">more</button>
<div test="item" ng-repeat="item in items"></div>
.throbber {
position: absolute;
top: calc(50% - 16px);
left: calc(50% - 16px);
}
angular
.module("app", [])
.run(function ($rootScope) {
$rootScope.items = ["One", "Two"];
$rootScope.more = function () {
$rootScope.items.push(Math.random());
};
})
.factory("throbber", function () {
var visible = false;
var throbber = document.createElement("img");
throbber.src = "http://upload.wikimedia.org/wikipedia/en/2/29/Throbber-Loadinfo-292929-ffffff.gif";
throbber.classList.add("throbber");
function show () {
document.body.appendChild(throbber);
}
function hide () {
document.body.removeChild(throbber);
}
return {
show: show,
hide: hide
};
})
.directive("test", function ($templateCache, $timeout, $compile, $q, throbber) {
var template = "<div>{{text}}</div>";
var templateUrl = "templateUrl";
return {
link: function (scope, el, attr) {
var tmpl = $templateCache.get(templateUrl);
if (!tmpl) {
throbber.show();
tmpl = $timeout(function () {
return template;
}, 1000);
}
$q.when(tmpl).then(function (value) {
$templateCache.put(templateUrl, value);
el.html(value);
$compile(el.contents())(scope);
throbber.hide();
});
},
scope: {
text: "=test"
}
};
});
JSBin example.
In live code you'll have to replace $timeout with $http.get(templateUrl), I've used the former to illustrate async loading.
How template loading works in my example:
Check if there's our template in $templateCache.
If no, fetch it from URL and show indicator.
Manually put template inside element and [$compile][2] it.
Hide indicator.
If you wonder what $templateCache is, read the docs. AngularJS uses it with templateUrl by default, so I did the same.
Template loading can probably be moved to decorator, but I lack relevant experience here. This would separate concerns even further, since directives don't need to know about indicator, and get rid of boilerplate code.
I've also added ng-repeat and run stuff to demonstrate that template doesn't trigger indicator if it was already loaded.
What I would do is to add an ng-include in my directive to selectively load what I need
Check this demo from angular page. It may help:
http://docs.angularjs.org/api/ng.directive:ngInclude
````
/**
* async load template
* eg :
* <div class="ui-header">
* {{data.name}}
* <ng-transclude></ng-transclude>
* </div>
*/
Spa.Service.factory("RequireTpl", [
'$q',
'$templateCache',
'DataRequest',
'TplConfig',
function(
$q,
$templateCache,
DataRequest,
TplConfig
) {
function getTemplate(tplName) {
var name = TplConfig[tplName];
var tpl = "";
if(!name) {
return $q.reject(tpl);
} else {
tpl = $templateCache.get(name) || "";
}
if(!!tpl) {
return $q.resolve(tpl);
}
//加载还未获得的模板
return new $q(function(resolve, reject) {
DataRequest.get({
url : "/template/",
action : "components",
responseType : "text",
components : name
}).success(function(tpl) {
$templateCache.put(name, tpl);
resolve(tpl);
}).error(function() {
reject(null);
});
});
}
return getTemplate;
}]);
/**
* usage:
* <component template="table" data="info">
* <span>{{info.name}}{{name}}</span>
* </component>
*/
Spa.Directive.directive("component", [
"$compile",
"RequireTpl",
function(
$compile,
RequireTpl
) {
var directive = {
restrict : 'E',
scope : {
data : '='
},
transclude : true,
link: function ($scope, element, attrs, $controller, $transclude) {
var linkFn = $compile(element.contents());
element.empty();
var tpl = attrs.template || "";
RequireTpl(tpl)
.then(function(rs) {
var tplElem = angular.element(rs);
element.replaceWith(tplElem);
$transclude(function(clone, transcludedScope) {
if(clone.length) {
tplElem.find("ng-transclude").replaceWith(clone);
linkFn($scope);
} else {
transcludedScope.$destroy()
}
$compile(tplElem.contents())($scope);
}, null, "");
})
.catch(function() {
element.remove();
console.log("%c component tpl isn't exist : " + tpl, "color:red")
});
}
};
return directive;
}]);
````

using ng-include for a template that has directives that use data retrieved by XHR

I am using ng-include in order to include a persistent menu, that exists in all of the views of my SPA.
The problem is that I want to display different options and content in this menu per each user type(admin, guest, user etc.), and this requires the service function authService.loadCurrentUser to be resolved first.
For the purpose of managing this content easily and comfortably, I have created a simple directive, that takes an attribute with the required access level, and at the compile phase
of the element, if the permissions of the given user are not sufficient, removes the element and it's children.
So after failing miserably at trying to make the ng-include go through the routeProvider function, I've tried to use ng-init, but nothing seems to work, the user role remain undefined at the time that I am logging it out.
I am thinking about trying a new approach, and making the entire menu a directive that includes the template that is suitable for each user type, but first I would like to try and solve this matter.
Directive:
'use strict';
/* Directives */
angular.module('myApp.directives', []).
directive('restrict', function(authService){
return{
restrict: 'A',
prioriry: 100000,
scope: {
// : '#'
},
link: function(){
// alert('ergo sum!');
},
compile: function(element, attr, linker){
var user = authService.getUser();
if(user.role != attr.access){
console.log(attr.access);
console.log(user.role);//Always returns undefined!
element.children().remove();
element.remove();
}
}
}
});
Service:
'use strict';
/* Services */
angular.module('myApp.services', []).
factory('authService', function ($http, $q) {
var authServ = {};
var that = this;
that.currentUser = {};
authServ.authUser = function () {
return $http.head('/users/me', {
withCredentials: true
});
},
authServ.getUser = function () {
return that.currentUser;
},
authServ.setCompany = function (companyId) {
that.currentUser.company = companyId;
},
authServ.loadCurrentUser = function () {
var defer = $q.defer();
$http.get('/users/me', {
withCredentials: true
}).
success(function (data, status, headers, config) {
console.log(data);
that.currentUser.company = {};
that.currentUser.company.id = that.currentUser.company.id ? that.currentUser.company.id : data.main_company;
that.currentUser.companies = [];
for (var i in data.roles) {
that.currentUser.companies[data.roles[i]['company']] = data.roles[i]['company_name'];
if (data.roles[i]['company'] == that.currentUser.company.id){
that.currentUser.role = data.roles[i]['role_type'];
that.currentUser.company.name = data.roles[i]['company_name'];
// console.log(that.currentUser.role);
}
}
// defer.resolve(data);
defer.resolve();
}).
error(function (data, status, headers, config) {
that.currentUser.role = 'guest';
that.currentUser.company = 1;
defer.reject("reject");
});
return defer.promise;
}
return authServ;
});
Menu controller:
angular.module('myApp.controllers', []).
controller('menuCtrl', function($scope, $route, $location, authService){
//TODO: Check if this assignment should be local to each $scope func in order to be compliant with 2-way data binding
$scope.user = authService.getUser();
console.log($scope.user);
// $scope.companies = $scope.user.companies;
$scope.companyOpts = function(){
// var user = authService.getUser();
if(typeof $scope.user.company == 'undefined')
return;
var companies = [];
companies[$scope.user.company.id] = $scope.user.company.name;
for(var i in $scope.user.companies){
if(i != $scope.user.company.id){
companies[i] = $scope.user.companies[i];
}
}
// console.log(companies);
// if(nonCurrentComapnies.length > 0){
console.log(companies);
return companies;
// }
}
$scope.$watch('user.company.name', function(company){
for(var i in $scope.user.companies)
if(company == $scope.user.companies[i].id)
authService.setCompany(i);
});
$scope.$watch(function(){return authService.getUser().company; }, function(company){
//Refresh the page on company change here, first time, and each time the user changes the select
// $scope.companyOpts();
// $scope.currentComapany = company;
})
;})
Main SPA HTML page:
<div ng-init="authservice.loadCurrentUser" ng-include src="'partials/menu.html'"></div>
menu element that should be visible only to the admin:
<ul class="left" restrict access="admin">
<li>You are the admin!</li>
</ul>
Thanks in advance for any assistance!
I personally would do the "reverse" way. Which mean: I will add the menu in when the user role is "admin", or "user", etc...
This way, you can do something like this in the "restrict" directive:
...
var roleListener = $scope.$watch('user.role', function (newVal, oldVal) {
if (newVal == $scope.access) {
// add the menu items
// supposed that loadcurrentuser be called only once
// we should clear the watch
roleListener();
} else {
// personally, I would remove the item here too
// so the menu would be added or removed when user.role update
}
});
...
One more thing, for just display menu base on the user role, you can use ngSwitch, something like this:
<ul class="left" ng-switch="user.role">
<li ng-switch-when="admin">You are the admin!</li>
<li ng-switch-when="user">You are the user!</li>
<li ng-switch-default><img src="some-thing-running.gif"/>Your menu is loading, please wait...</li>
</ul>
And let the magical AngularJS binding render up the menus for you!
The call to authServ.getUser should also return a promise by calling internally
authServ.loadCurrentUser
which should be modified a bit to check if the user context exists to avoid making another API call and always returning resolve with the user context:
defer.resolve(that.currentUser);
Loading the user context should also be done early on as this enables the authorization of the app. The app.run function can be used for this purpose.
hope it helps others.

AngularJS Passing Variable to Directive

I'm new to angularjs and am writing my first directive. I've got half the way there but am struggling figuring out how to pass some variables to a directive.
My directive:
app.directive('chart', function () {
return{
restrict: 'E',
link: function (scope, elem, attrs) {
var chart = null;
var opts = {};
alert(scope[attrs.chartoptions]);
var data = scope[attrs.ngModel];
scope.$watch(attrs.ngModel, function (v) {
if (!chart) {
chart = $.plot(elem, v, opts);
elem.show();
} else {
chart.setData(v);
chart.setupGrid();
chart.draw();
}
});
}
};
});
My controller:
function AdListCtrl($scope, $http, $rootScope, $compile, $routeParams, AlertboxAPI) {
//grabing ad stats
$http.get("/ads/stats/").success(function (data) {
$scope.exports = data.ads;
if ($scope.exports > 0) {
$scope.show_export = true;
} else {
$scope.show_export = false;
}
//loop over the data
var chart_data = []
var chart_data_ticks = []
for (var i = 0; i < data.recent_ads.length; i++) {
chart_data.push([0, data.recent_ads[i].ads]);
chart_data_ticks.push(data.recent_ads[i].start);
}
//setup the chart
$scope.data = [{data: chart_data,lines: {show: true, fill: true}}];
$scope.chart_options = {xaxis: {ticks: [chart_data_ticks]}};
});
}
My Html:
<div class='row-fluid' ng-controller="AdListCtrl">
<div class='span12' style='height:400px;'>
<chart ng-model='data' style='width:400px;height:300px;display:none;' chartoptions="chart_options"></chart>
{[{ chart_options }]}
</div>
</div>
I can access the $scope.data in the directive, but I can't seem to access the $scope.chart_options data.. It's definelty being set as If I echo it, it displays on the page..
Any ideas what I'm doing wrong?
UPDATE:
For some reason, with this directive, if I move the alert(scope[attrs.chartoptions]); to inside the $watch, it first alerts as "undefined", then again as the proper value, otherwise it's always undefined. Could it be related to the jquery flot library I'm using to draw the chart?
Cheers,
Ben
One problem I see is here:
scope.$watch(attrs.ngModel, function (v) {
The docs on this method are unfortunately not that clear, but the first argument to $watch, the watchExpression, needs to be an angular expression string or a function. So in your case, I believe that you need to change it to:
scope.$watch("attrs.ngModel", function (v) {
If that doesn't work, just post a jsfiddle or jsbin.com with your example.

Resources