How to better organize dynamic loaded directives in angular? - angularjs

In my project, I have a small div that has 3 options "food", "drinks", "social". These are tied to a scope variable "$scope.option" on "AppController".
I have a series of ng-switch statements:
<div ng-switch on="option">
<div ng-switch-when="food">
<fooddirective></fooddirective>
</div>
<div ng-switch-when="drinks">
<drinksdirective></drinksdirective>
</div>
<div ng-switch-when="social">
<socialdirective></socialdirective>
</div>
</div>
Note that whatever option is selected, the food, drink, or social, these only take up half the page so they are all surrounded by "AppController". My intent is to be able to "Dynamically load directives" into the page. Is there a way I can get rid of the need to have to explicitly write "<socialdirective></socialdirective>" or maybe even get rid of all of the ng-switch? Or is there some better alternative? It feels like it could get very messy if I have say 20 or 30 of these options (i.e. food, drinks, social, games).
If I know the name of the directive in advance, say "food", "drinks", "social". Is there some way I can do something like:
<div ng-switch on="option">
// code that interprets option's variable (i.e. food), appends the text "directive" after, and dynamically/lazily add it in and remove it so it behaves like an ng-switch
</div>
I am not sure if it is possible, but am looking for any better alternative to what I am doing now. Any examples of a revised way of doing is great.

You can use UI router to accomplish this:
--index.html
<body>
Top Level
<div ui-view></div>
</body>
--optionsPage.html
<select ng-options="option.name for option in data.availableOptions track by option.id" ng-model="data.selectedOption"></select>
<div ui-view></div>
In the options page, you will make the select options generate ui-sref links to each type of directive you want to display. Child states will only change the ui-view of their parent state, so you can easily route to the correct directive.
Then you define your routes as such:
.state('options.food', {
templateUrl: "partials/options.food.html"
})
.state('options.drinks', {
templateUrl: "partials/options.drinks.html"
})
Then you can define the directive in each of those HTML files.
(Much of this code is just taken from the Angular and UI-Router code examples, but hopefully you can see what you need to do.)
UI Router - https://github.com/angular-ui/ui-router

If those directives have a lot of functionality in common, to me it seems that the best alternative would be to implement a new directive, say optiondirective, that encapsulates this behavior. This way you would just insert that directive on the html with the chosen option as the attribute, the ng-switch or whatever resolution mechanism you end up using would be hidden in the template of that directive, and the common functionality would be implemented in that directive's controller. That doesn't help you get rid of the hideous part, but at least you will get to re-use a greater portion of the implementation and modularize your code.

WARNING: I am not giving you a complitely working answer.. just want to give you an idea on how to do it.
you can create another directive which wraps the original one
.directive("wrapper", function(){
return {
scope: {'option': #},
template: '<' + option + 'directive>' + '</' + option + 'directive>'
}
})
and then call it like
if this does not work you will need to compile the html in your like function like below
var tmpl = '<' + option + 'directive>' + '</' + option + 'directive>'
element.html(tmpl );
$compile(element.contents())(scope);
that should definitely do it

Related

Using directives as part of angular-translate's translate-values, how to compile?

I am in the process of internationalizing a website and have come across a sticking point with angular-translate.
Suppose I have a translationProvider for english supplying the following text:
"example.message": "I need a {{link}} or {{customDirectiveOutput}} to render directives in me"
"link.text": "this link"
I am now trying to get some HTML into those two placeholders by doing something like the following:
<div translate="example.message"
translate-values="{
link: '<a ui-sref=\'stateName\'>{{ \'link.text\' | translate }}</a>'
customDirectiveOutput: '<span my-custom-directive=\'vm.object\'></span>'
}">
</div>
Where ui-sref is from the ui-router and adds an attribute like href="/path/to/my/state", and my-custom-directive places inner html inside the span sort of like this:
<span my-custom-directive='vm.object'>
<span class='foo'>{{vm.object.foo}}</span>
<span class="bar">{{vm.object.bar}}</span>
</span>
The link tag I can "cheat" around by just hard-coding the href onto the a tag and that isn't so bad to duplicate the /path/to/state, though I'd prefer not to.
The custom directive, however, is conditional on its output so I can't just cheat the output into the translate-values
I've tried doing something in the controller for the page like
$scope.value = vm.getValue();
var element = $compile('<span my-custom-directive="value"></span>')($scope);
return element[0].outerHTML;
But this unfortunately creates an infinite digest loop. I feel like this is the right "path", having some JS pre-compile the fragment I need to insert into the translation, but I'm stuck as to how to accomplish that!
How can I insert angular-compiled HTML with directives attached into a translated string?

Variable value as directive/controller name inside template (with $compile/$interpolate)?

I am creating a directive in which template I need to use the a scope's variable value as the name of the directive (or alternatively controller) to load.
Say I have a directive widget that has a template called widget.html which looks like:
<div class="widget widget.type" {{widget.type}} ng-controller="widget.type">
<div class="navBar">
<div ng-include="widget.type + '-t.html'"></div>
<i class="fa fa-close"></i>
<hr>
</div>
<div ng-include="widget.type + '-f.html'"></div>
</div>
Now widget.type is not getting evaluated in the first line. It works fine for ng-include. Say widget.type's value is weather. The first line should then be interpolated first to look like (doesn't matter if class attribute, widget.type-attr or ng-controller is interpolated)
<div class="widget" weather>
and then compiled to include the weather directive.
How can I get widget.type interpolated in the template?
Not an option is to use ng-include to load the directive. I need to use one common template for the widget and want to add/override/extend the base directive with additonal functionality/Variables.
If this is not the way to achieve that, is there a way to extend a directive the OOP-way?
See the plunkr
You can only place interpolation expressions in text nodes and attribute values. AngularJS evaluates your template by first turning it into DOM and then invoking directive compilation, etc. If you try to place {{...}} instead of attribute name, you'll just end up with messed-up DOM.
If you really need to replace a whole directive based on $scope variable value, you'll need to create a directive for application of other directives and do some heavy lifting with $compile (you'll have to completely re-compile the template each time the value changes). I'd recommend trying to find other designs solving your situation before attempting this.
For adjusting your template based on element attributes, see this answer.

Angularjs: is this the correct way to use controller and directive?

Normally the directive will use data in the scope, for example:
<div my-directive
data-x='x'
data-y='y'
data-z='z'
...>
</div>
However, the arguments of the directive grow very fast. So I put them in a model class.
So each of my directive has it's own model class.
<div my-directive
data-model='myDirectiveModel'>
</div>
In controllers, I just need to set
$scope.myDirectiveModel = {
x: 100.
y: 200,
add: function(){
return this.x + this.y;
}
};
Is this the correct way to use controller and directive?
It's certainly a cleaner way of passing data through to a directive than having lots attributes in the markup.
However I would suggest that one of the good things about Angular is it's declarative syntax. If you always and exclusively do what you've suggested, when you come to read back your markup in a couple of months you'll have to delve into code to see what's going on. Being more explicit in your markup could save you some time. For example when you do this:
<div my-directive show-chickens="yes" chimp-count="monkeys" />
less is hidden from you when you read the markup.
However, if you are happy with your approach and want to continue, I would just declare the model as the value of the directive attribute like so...
<div my-directive='myDirectiveModel' />
just to make it even more clean.

AngularJS rendering different template inside ng-repeat using ng-view

I would like to apologize that I couldn't provide any code snippet regarding this question, I am a newbie about AngularJS.
<div ng-repeat="item in list" ng-view></div>
Using the code above, would it be possible to render different template which would be dependent on item.type property. I was expecting a result like this:
item.type == "image" returning: <div><img src="'IMAGE_URI'"></div>
item.type == "text" returning: <div><p>TEXT</p></div>
As of now I have create a template html for the enumeration of item.type. Is this concern possible using AngularJS? I've recently learned that ng-view accompannied with ng-route.
I think one way you can do it is to use 'ng-if' to conditionally include html:
<div ng-repeat="item in list">
<div ng-if="item.type == 'image'><img src="'IMAGE_URI'"></div>
<div ng-if="item.type == 'text'><div><p>TEXT</p></div>
</div>
You can have only one ng-view,
take a look at this answer.
from the documentation for ng-view:
ngView is a directive that complements
the $route service by including the rendered
template of the current route into the main
layout (index.html) file.
Every time the current route changes,
the included view changes with it according
to the configuration of the $route service.
Requires the ngRoute module to be installed.
What you're looking for is ng-include, combined with ng-switch,
take a look at this answer on how to combine the two.
ng-include creates a new child scope, which in turn inherits from the controller.
have a look at this answer for more information about the topic.

Is there a different way to hide a scope variable from showing while AngularJS is loading?

I am using this way:
<div ng-cloak>{{ message.userName || message.text }}</div>
Is this the only / best way to ensure the user does not see the {{ }} when AngularJS is still loading ?
There are several ways to hide content before Angular has a chance to run
Put the content you want to hide in another template, and use ngInclude
<div ng-include="'myPartialTemplate.html'"></div>
If you don't actually want another request made to the server to fetch another file, there are a couple of ways, as explained in the $templateCache docs. There are tools to "compile" external HTML templates into JS to avoid having to do this manually, such as grunt-angular-templates.
Similar to ngInclude, if you put everything in custom directives, with its own template, then the template content won't be shown until Angular has had a chance to run.
<my-directive></my-directive>
With a definition of:
app.directive('myDirective', function() {
return {
restrict: 'E',
template: '<div>Content hidden until Angular loaded</div>'
}
});
ngBind as an alternative to {{}} blocks
<div>Hello <span ng-bind="name"></span></div>
ngCloak as you have mentioned (in this list for completeness).
<div ng-cloak>Content hidden until Angular compiled the template</div>
But you must have certain styles loaded before the page is rendered by the browser, as explained in the ngCloak docs.
You can use ng-bind too as the documentation explains.
A typical advantage about ng-bind is the ability to provide a default value while Angular is loading (indeed, ng-cloak can only hide the content):
<p>Hello, <span ng-bind="user.name">MyDefaultValueWhileAngularIsLoading<span/></p>
Then as soon Angular is loaded, the value will be replaced by user.name.
Besides, ng-cloak is useful when dealing with blocks (many HTML lines) and ng-bind on a particular element.

Resources