Conditional loading of templates with Angular directives - javascript

I want to load different html templates depending on the screen size.
Controller:
$scope.getTemplateUrl = function() {
if ($scope.md) {
return $templateCache.get('library/timelineeditor/slidelibrary/slidelibrary.html')
} else {
return $templateCache.get('library/timelineeditor/slidelibrary/slidelibrary-mobile.html')
}
}
Directive:
function SlideLibrary() {
return {
restrict: 'E',
scope: {},
bindToController: true,
controller: 'SlideLibraryController',
controllerAs: 'vm',
template: '<ng-include src="getTemplateUrl()"/>'
};
}
It loads the right template, but something goes wrong along the way as you can see in the pic:
Are there any best practices around this or do you have any suggestions on how to do it differently?

Currently you are passing template content to src which is trying to make an ajax for fetching that template, that's why you can see 404 error in console. You should just be returning URL form getTemplateUrl method. Because src of ng-include accept templateUrl only, it will take care of loading that template into div.
$scope.getTemplateUrl = function() {
if ($scope.md) {
return 'library/timelineeditor/slidelibrary/slidelibrary.html';
} else {
return 'library/timelineeditor/slidelibrary/slidelibrary-mobile.html';
}
}

Related

Trouble using $attrs in directives controller

My directive has an attribute id-model that is used to pass an array to the directive. The directives controller should execute a function using that array as an argument. I can access the attribute in my controller and log the array but my function never runs and I'm not sure why.
If I don't use the attribute and change placesFact.getDetails(idModel) to placesFact.getDetails($scope.model) everything works fine but I want there to be more flexibility with the directive.
*Update:
To clarify on the placesFact.getDetails() method, It looks up google place info with a place id and returns the results as a promise. It has been tested and works fine.
Directive
.directive('gPlaces', function() {
return {
restrict: 'E',
scope: '#',
link: function(scope, element, attrs) {
scope.tempUrl = attrs.tempUrl;
},
controller: function($scope, $attrs, placesFact) {
var idModel = $attrs.idModel;
$attrs.$observe('idModel', function(value) {
idModel = value;
console.log(idModel); // This logs the array
placesFact.getDetails(idModel).then(function(results) {
console.log(results); // This logs nothing
$scope.places = results;
});
});
},
template: '<div ng-include="tempUrl"></div>'
}
})
Controller
angular.module('myApp').controller('PlacesCtrl', function($scope) {
$scope.model = [
'ChIJR4dOl_hYwokRApSCaQiBidk',
'ChIJv-Ghof5YwokRMtWLEV12hJI',
'ChIJjyX2GqRZwokRT-gdcGoPuSI',
'ChIJqSurReFYwokRec7JFACToas',
'ChIJn8dCo-NYwokRC_4nRUQWbNE',
'ChIJszmN0-JYwokRk-XCDbO6X_Y',
'ChIJt4TrE_1YwokRVedrKxaqYoo',
'ChIJiW0WvwJZwokRIWyzCvo3o5k',
'ChIJsS1xLQJZwokRGfXJPMwXA1A',
'ChIJI5xCX6NZwokR3jdSQwsw2DI',
'ChIJuVE5aLtZwokR-K75OxUEtzI',
'ChIJ7R4tgLtZwokRM8thlhlzE2o',
'ChIJxbWTG_pYwokRPgtFVKi-Cuc',
'ChIJKZVnwFVYwokRgDw_sxw3NCo',
'ChIJw_JUgvhYwokR91EMxVDhB8M',
'ChIJZ3oXOVZYwokRNnAXaDRKAzg',
'ChIJP9idxlZYwokRLH-I1mNfzYQ',
'ChIJndGJ5FNYwokRricJvhT0t1s',
'ChIJhZ0Sn1ZYwokRRA1MZJZHrHA',
'ChIJhTdv51NYwokR7V105uVzf8g',
'ChIJvfVwDFJYwokRtWobbwOMEVM',
'ChIJpY9Tg01YwokRCr_aQpDrqgk',
'ChIJ-fRuLFdYwokR0KKQ6Av_WhQ',
'ChIJh-DIsE1YwokRhuFrdM1ge5E'
];
});
View
<g-places temp-url="pages/places/multiTemp.html" id-model={{model}}></g-places>
As estus mentioned in the comments, my scope: '#' syntax was wrong and was the root of the problem. This is my first directive so a little trial and error and a lot of reading lead me to figure out what to do though.
Here is how the directive looks now that it is working.
.directive('gPlaces', function() {
return {
restrict: 'E',
scope: {
idModel: '=',
tempUrl: '#'
},
controller: function($scope, placesFact) {
placesFact.getDetails($scope.idModel).then(function(results) {
$scope.places = results;
});
},
template: '<div ng-include="tempUrl"></div>'
}
})

Angular custom directive calling another custom directive

I am developing an angular framework where user can configure header, menu, footer and selected pages using custom directives. To complete this requirement, at one point I need the following. I have seen example on the net, but does not really explain it well.
The requirement is that the templateUrl of the first custom directive shall be replaced with a template attribute that should call another custom directive.
The following code with templateUrl works fine.
angular.module("app",[]);
angular.module("app").controller("productController", ['$scope', function ($scope) {
}]);
angular.module("app").directive("tmHtml", function () {
return {
transclude: false,
scope: {
},
controller: "productController",
templateUrl: "/templates/HideShow.html"
};
});
However, when I change the above code as follows. I am making the change so that my custom directive tmHtml calls another custom directive.
angular.module("app").directive("tmHtml", function () {
return {
transclude: false,
scope: {
},
controller: "productController",
template: ``<hideShow></hideShow>``
};
});
New Directive for hideShow is written as follows
angular.module("app").directive("hideShow", function () {
return {
tempateUrl: "/templates/HideShow.html"
};
});
It's not working. I understand I am missing something here. I could not find out. Appreciate help
Working code:
var app = angular.module('plunker', []);
app.controller('productController', function($scope) {
});
app.directive("hideShow", function() {
return {
templateUrl: "hideshow.html"
};
});
app.directive("tmHtml", function() {
return {
transclude: false,
scope: {},
controller: "productController",
template: "<hide-show></hide-show>"
};
});
the problem is with the spelling of templateUrl in your hideShow directive.
Demo : http://plnkr.co/edit/TaznOeNQ7dM9lyFgqwCL?p=preview
Try define your controller with controllerAs:
angular.module("app").directive("tmHtml", function () {
return {
transclude: false,
scope: {
},
controllerAs: "productController",
templateUrl: "/templates/HideShow.html"
};
});
angular.module("app").directive("tmHtml", function () {
return {
transclude: false,
scope: {
},
controller: "productController",
template: ``<hideShow></hideShow>``
};
});
must be replaced by
angular.module("app").directive("tmHtml", function () {
return {
transclude: false,
scope: {
},
controller: "productController",
template: "<hide-show></hide-show>"
};
});
under the attribute template, you add Html. So, you still have to use snake-case there, like in your Html files
Your first directive may have an eventually scoped attribute that you observe.
Then it may wrap the second directive. If needed, your directives may communicates as parents and children.

AngularJs: Iterating over array inside directive in order to create nested set of directives

Inside angularJS directive I'm trying to iterate over array and based on values I would like to create nested list of directives.
Current version of directive
Directive type
.directive("type", function($compile, $log){
return{
restrict: "E",
replace: true,
transclude: true,
scope: {
type: '='
},
template: "<div></div>",
link: function(scope, element, attrs){
if (angular.isArray(scope.type)){
angular.forEach(scope.type, function(value, index){
$log.error(value);
element.append("<type type='scope.type['"+index+"]'></type>");
});
} else if (angular.isObject(scope.type)){
element.append("OBJECT")
} else {
element.append("<div>{{scope.type}}</div>")
}
$compile(element.contents())(scope)
}
};
})
I also tried to use above directive with next version of link function:
if (angular.isArray(scope.type)) {
element.append("<div ng-repeat='element in scope.type'><type type='element'></type></div>");
} else if (angular.isObject(scope.type)) {
element.append("OBJECT")
} else {
element.append("<div>{{scope.type}}</div>")
}
$compile(element.contents())(scope)
}
None of provided codes solve my issue.
Below you will find example explaining on specific case:
Let's say that I had next object in the scope.type = [null,"int"]. Now I would like to use <type type='type'><type> and as a result of first evaluation I want to have sth like:
<type type='type[0]'></type><type type='type[1]'></type>
Further evaluation of those values should lead to some simpler form but right now it is not important.
How I can achieve sth like this?
Edit
I tried even to exctract part of the code responsible for iteration to the seperate directive but it still does not work. Code:
Update link function in type directive:
link: function(scope, element, attrs) {
if (angular.isArray(scope.type)) {
element.append("<typeb type='scope.type'></typeb>")
} else if (angular.isObject(scope.type)) {
element.append("OBJECT")
} else {
element.append("<div>{{scope.type}}</div>")
}
$compile(element.contents())(scope)
}
New directive:
.directive("typeb", function($compile, $log){
return{
restrict: "E",
replace: true,
transclude: true,
scope: {
type: '='
},
template: "<div ng-repeat='t in type'>{{t}}</div>",
};
})
Problem still occurs but generated html contains only next pieces as a result of typeb directive:
<!-- ngRepeat: t in type -->
The problem you are getting is <!-- ngRepeat: t in type --> this is because your type didn't contains any value, when it is inside typeb directive
Your directive shouldn't be use scope.variable on view.
Scope variable will be directly accessible by their name like
{{type}} or <typeb type='type'></typeb>
Change your link code to below.
Directive link
link: function(scope, element, attrs) {
if (angular.isArray(scope.type)) {
element.append("<typeb type='type'></typeb>")
} else if (angular.isObject(scope.type)) {
element.append("OBJECT")
} else {
element.append("<div>{{type}}</div>")
}
$compile(element.contents())(scope)
}
Thanks.
Use an ng-repeat in the template
<your-directive attr="item.someattr" ng-repeat="item in items"></your-directive>
.directive("type", function($compile, $log){
return{
restrict: "E",
replace: true,
transclude: true,
scope: {
type: '='
},
template: "NG REPEAT HERE",
...
})

Angular custom directive: a strange error occurs when set controller attribute to `function(scope){}`

When I wrote a custom directive, a strange error blocks me.
angular.module('app.directives', [])
.directive('cyMenu', ['RecursionHelper', function(RecursionHelper) {
function postLink(){};
return {
restrict: 'E',
templateUrl: 'views/component/cy-menu.html',
replace: true,
transclude: false,
require: '?^cyMenu',
controller: function ($scope) { // when set this argument($scope) to scope, error occurs.
this.getList = function() {
return $scope.list;
}
},
scope: {
list: '=',
isSubmenu: '#'
},
compile: function(tElement) {
return RecursionHelper.compile(tElement, postLink);
}
}
}
As I pointed out(see comment), when I set controller attribute to controller: function (scope) {}, the error occurs:
Error: [$injector:unpr] Unknown provider: scopeProvider <- scope
http://errors.angularjs.org/1.3.6/$injector/unpr?p0=scopeProvider%20%3C-%20scope
...
I don't know why. Any help will be appreciated.
update
Here is angular's official demo, it looks similar to my directivehttps://code.angularjs.org/1.3.6/docs/guide/directive:
angular.module('docsTabsExample', [])
.directive('myTabs', function() {
return {
restrict: 'E',
transclude: true,
scope: {},
controller: function($scope) {
...
},
templateUrl: 'my-tabs.html'
};
})
A very good practice is to separate every component in a separate file, so I would start by putting your controller in a file like that :
//File recursionHelperController.js
(function() {
'use strict';
angular
.module('app.controllers')
.controller('RecursionHelperController', RecursionHelperController);
RecursionHelperController.$inject = ['$scope'];
function RecursionHelperController($scope) {
//do your stuff
}
})();
Note that I gave you the most correct format I know for a controller or any angular element in general, but you can do something simpler like:
angular.module('app.controllers')
.controller('RecursionHelperController', ['$scope', function($scope) {
//do your stuff
}])
You can then call this controller in your main file :
controller: 'RecursionHelperController'
Hope it helps
Update :
Sometimes the automatic injection has some troubles that's why I recommend doing it this way with an explicit injection. The angular doc only shows the simplest way for clarity and tutorial purposes
Update 2
If you don't want to separate the controller, try using the injection safe notation
controller : ['$scope', function($scope) {
//do your stuff
}])

AngularJS directive to directive communication throwing an error about controller not found

I have 2 directives, one for searching and one for pagination. The pagination directive needs to access the search directive to find out what property we're currently searching by. When I load the page though, it throws an error saying Error: [$compile:ctreq] Controller 'search', required by directive 'pagination', can't be found!. However I have a controller setup in my search directive.
Here is my search directive:
angular.module('webappApp')
.directive('search', function ($route) {
return {
templateUrl: 'views/search.html',
restrict: 'E',
scope: {
searchOptions: '=',
action: '=',
currentProperty: '=',
currentValue: '='
},
controller: function($scope) {
$scope.searchBy = $scope.searchOptions[0].text;
$scope.searchByProperty = $scope.searchOptions[0].property;
$scope.setSearchBy = function(event, property, text) {
event.preventDefault();
$scope.searchBy = text;
$scope.searchByProperty = property;
};
$scope.search = function() {
$scope.searching = true;
$scope.currentProperty = $scope.searchByProperty;
$scope.currentValue = angular.element('#searchCriteria').val();
$scope.action($scope.searchByProperty, $scope.currentValue, function() {
$scope.searching = false;
});
};
$scope.reload = function() {
$route.reload();
};
}
};
});
Here is my pagination directive:
angular.module('webappApp')
.directive('pagination', function () {
return {
templateUrl: 'views/pagination.html',
restrict: 'E',
require: '^search',
scope: {
basePath: '#',
page: '=',
sort: '='
},
link: function(scope, element, attrs, searchCtrl) {
console.debug(searchCtrl);
scope.searchByProperty = searchCtrl.searchByProperty;
}
};
});
In order for one directive to use another's controller by use of require, it needs to either share the same element as the controller containing directive, or it has to be a child of it.
You can't use require in the way you have, where the elements are siblings.
Angular docs about directives, including require
If it doesn't make sense to rearrange the DOM in the way I've described, you should inject a service into both directives which contains the data/methods you wish to share between the two.
Note: you could also experiment with the $$nextSibling / $$prevSibling properties of the directives' scopes, but this would present only a very fragile solution
You cannot use require in directive like that, however , since the only thing you need to pass between directives is a string , just bind them to the same property in parent controller (it can be parent directive controller):
...
<div ng-app='app' ng-controller='MyCtrl as ctrl'>
<my-dir-one s1='ctrl.message'></my-dir-one>
<my-dir-two s2='ctrl.message'></my-dir-two>
and first directives:
app.directive('myDirOne', function ($route) {
return {
templateUrl: 'views/my-dir-one.html',
restrict: 'E',
scope: {
s1: '=',
second directive
app.directive('myDirTwo', function ($route) {
return {
templateUrl: 'views/my-dir-one.html',
restrict: 'E',
scope: {
s2: '=',

Categories