Can you use controller as syntax with directive/ng-transclude? - javascript

I'm using controller as syntax. Inside this scope, I have a directive which transcludes content that access this controller. That controller appears to be inaccessible from within the ng-transclude.
DEMO: http://plnkr.co/edit/ZYPCym2WQV43wh4R4nwI?p=preview
Is there a restraint from using controller as from within transcluded content?

I think stuff is getting mixed up b/c of the way you're using ng-controller with a directive. Unfortunately, I can't explain why, but the way you were setting up the controller seemed funny to me :)
My thinking is, if you're putting ng-controller on an element that is a directive, you might as well make it a "directive controller" like this:
function transcludeDirective() {
return {
restrict: 'E',
transclude:true,
scope: false,
controller: DemoCtrl,
controllerAs: 'DemoCtrlVM',
template: '<div>'+
'<p>First name: <b>{{ DemoCtrlVM.first_name }}</b></p>'+
'<ng-transclude></ng-transclude>'+
'</div>'
}
}
Be sure to remove the ng-controller="DemoCtrl as DemoCtrlVM" from the HTML. If you do this, it works as expected.
This might not be what you want. Doing it your way, you could use a different controller with the directive, and doing it my way the controller is coupled to the directive...

Related

angular - append and execute script inside ng-view before append the template content within it

Since script can't be loaded inside templates, due to Angular's jQLite wasn't written to achieve it, I decided to add jQuery library before Angular since it checks for jQuery existence, and voila!, it works. But, the fact that I'm here asking a question, means that there's a 'but', so, the script doesn't execute before content loads. Of course, I made a little trick with routes.
In module's config section, I made this:
$routeProvider
.when("Business/:Context/:View?", {
templateUrl: function (url) {
return "Contexts/" + url.Context + "/" + (url.View || url.Context) + ".html";
}
});
Then let's say we set the route to "#/Business/Test" he most locate a file called Test.html on "/Contexts/Test", right eh!. Let's say Test.html content is this.
<script>
(function(ng){
console.log(ng)
ng.module('MyApp').controller('TestController', function ($scope, $route, $routeParams, $location, $http, $mdDialog) {
$scope.name = "TestController";
$scope.params = $routeParams;
$scope.name = "John";
});
})(angular)
</script>
<div ng-controller="TestController">
Hola {{ name }}
</div>
And finally the real question: why is this happening? It's like the is executed after or I don't know, because, looking the console:
Angular exists but the controller isn't added in time.
Am I doing wrong? It this behavior allowed? Can anyone lead me in this trip?
Are you trying to include the controller script with the view? Don't understand why and what the logic behind it? You have the template and the controller and the reason is to separate the view/DOM and the business logic behind it.
In angular, the script tag Load the content of a element into $templateCache, so that the template can be used by ngInclude, ngView, or directives so it doesn't interpulated the same as a <script> tag would be normally loaded.
They also state that The type of the element must be specified as text/ng-template, and a cache name for the template must be assigned through the element's id, which can then be used as a directive's templateUrl.
So you're just not using the script tag as intended by angular.
Why not including the controller in a js file and load it from the root HTML, or by using requirejs or a similar library.

Javascript (Angular) nested directive loading twice due to $compile

I have a directive nested within a directive. To satisfy our designers needs I require the contents to be direct children of the DOM node.
<div>
<my-directive style="color: blue;">
<p>Name: {{ ctl_a.fname }} {{ ctl_a.sname }}</p>
<p>External Test: {{ xternal }}</p>
<div>
<nested-directive incoming="ctl_a.a_counter"></nested-directive>
</div>
</my-directive>
</div>
What are my best options for getting this to load correctly? I.E "my-directive" can access ctl_a.fname, and "nested-directive" can access $scope.incoming, as the argument passed in by "ctl_a.a_counter".
Here is a plunk that demonstrates the problems I am running in to using $compile. If I use $compile, the nested directives execute twice. Once for the outer directive compile method, and once again for the manual one. Worse still the manual one is the only one that appears to render the contents correctly.
If I use ng-transclude then none of attributes passed to the inner directive work without prefixing with $$prevSibling or $parent because ng-transclude appears to create a new scope. This seems to be fundamentally wrong having to reference it like that.
Edit: Here is another plunk which forks the first one. This time i'm demonstrating ng-transclude and how I need to use $parent to access the controller for its directive.
Thanks.
I'm not sure if that's all you want to know, but you need to add terminal: true to your directive definition object. This prevents Angular from processing the HTML within the my-directive tags until you compile it yourself. Modified code from your plunker:
function myDirective($compile) {
var directive = {
compile: compile,
controller: controller,
controllerAs: 'ctl_a',
replace: true,
terminal:true, //<=======
restrict: 'E',
scope: {
data: '='
}
};
return directive;
It seems you are not using data property of my-directive. You can remove below code from my-directive.
scope:{
data: '='
}
Now you do not need to manually compile as below. So remove it also.
$compile(element.contents())(scope);
Working plunkr

templateUrl not loading when creating custom directive

I'm learning how to create directives because they seem very useful and I thought it would be a good use for a top navigation bar. I'm not sure if I'm misunderstanding how they should be used, missed something small along the way or something entirely different.
The templateUrl isn't loading and looking through other posts and the docs, I can't see what went wrong.
Here is the directive
.directive('stNavDir', function() {
return {
restrict: 'E',
transclude: true,
templateUrl: 'partials/TopNav.html',
scope: {
siteName: "=",
},
link: function(scope, element, attrbiutes) {
element.addClass('topBar');
}
}
Using it in index.html
<body>
<st-NavDir site-name={{siteName}}>{{title}}</st-NavDir>
TopNav.html
<div>
<button>Menu</button>
</br>
<div >
This will hold the navigation with a menu button, title of current location in app, and maybe other things
</div>
</div>
So it only shows the value of {{title}} and, looking in the console, there are no errors and it doesn't seem to even load TopNav.html.
If I'm using it completely wrong or there's a better way, I'm all ears as well. But this seemed like a good place to try out using a directive. I can load it fine using ng-include but I wanted to try this way and see if it would be more effective.
I'm also having trouble getting the style to take but that may be caused by this initial problem.
Change this line
<st-NavDir site-name={{siteName}}>{{title}}</st-NavDir>
to
<st-nav-dir site-name={{siteName}}>{{title}}</st-nav-dir>
Camel-case should be converted to snake-case.
st-nav-dir
in html may help.
stNavDir is the corresponding directive definition name.
Here is an interesting article:
Naming a directive

Angularjs : how to load data on a partial?

I am working with Angular and i am trying to update data of a ng-view when we click on the button but it's not working. Here my code snippet.
main page
<body ng-app="app">
<div ng-controller="MyController" >
Load datas
<div ng-view></div>
</div>
</body>
The ng-view:
<div>
{{myValue}}
</div>
<div>
<li ng-repeat ="data in datas">
{{data.name}} || {{data.value}}
</li>
</div>
The loadDatas function :
$scope.loadDatas = function(){
$http.get('data.json')
.then(function(res){
$scope.datas = res.data;
});
};
I want to load datas when the link is clicked.But it's not working. I have a plunker here if someone could help me.
Thanks
1)
Since it's an async call, the easiest way would be to wrap the var change in a $timeout callback:
$scope.loadDatas = function(){
$http.get('data.json')
.then(function(res){
$timeout(function(){
$scope.datas = res.data;
}, 0);
});
};
This basically forces a scope digest without you having to worry about the digest phase. Of course, don't forget to inject $timeout into the controller.
If you still want to do the digest manually, you can do $scope.$apply().
2)
You also need to fix your JSON as I have shown how you in the comments:
{"name":"Dan","value":"13"},
{"name":"John","value":"34"},
etc...
3)
No need to assign the same controller twice.
Specifying the controller in the route cause the controller to spawn a new scope (and plus one each time that anchor is clicked, unless you remove the href attribute or block it in another way). I fixed it by removing the controller directive:
when('/', {
templateUrl: 'partial.html'
}).
So, there was no need to specify a controller unless it's a different controller than the one the ng-view is inside of. In your case, that was not the case (you only used a controller that the view is already in) and it was causing two different controllers/scopes (even more if the href attribute is present in the anchor when you click it) to spawn and $scope.datas in one scope is not the $scope.datas in the scope that was bound to the partial.
A different variable name would work because of the scope parent/child inheritance; so if the variable names don't match, a parent scope variable would be available in the child scope without specific definition.
Here is the working version: http://plnkr.co/edit/QWPFAakvNEdJzU50LKSx?p=preview
You forgot to inject the $http in your controller:
app.controller("MyController", function($scope, $http) {
Take a look at this one: var name changed plnkr.

Using Angular, how can I show a DOM element only if its ID matches a scope variable?

I am relatively new to AngularJS.
I have a series of DIVs in a partial view. Each of the DIVs has a unique ID. I want to show / hide these DIVs based on a scope value (that matches one of the unique ID).
I can successfully write out the scope value in the view using something like {{showdivwithid}}
What would be the cleanest way to hide all the sibling divs that dont have an ID of {{showdivwithid}}
I think you are approaching the problem with a jQuery mindset.
Easiest solution is to not use the id of each div and use ngIf.
<div ng-if="showdivwithid==='firstDiv'">content here</div>
<div ng-if="showdivwithid==='secondDiv'">content here</div>
<div ng-if="showdivwithid==='thirdDiv'">content here</div>
If you don't mind the other elements to appear in the DOM, you can replace ng-if with ng-show.
Alternatively use a little directive like this:
app.directive("keepIfId", function(){
return {
restrict: 'A',
transclude: true,
scope: {},
template: '<div ng-transclude></div>',
link: function (scope, element, atts) {
if(atts.id != atts.keepIfId){
element.remove();
}
}
};
});
HTML
<div id="el1" keep-if-id="{{showdivwithid}}">content here</div>
<div id="el2" keep-if-id="{{showdivwithid}}">content here</div>
<div id="el3" keep-if-id="{{showdivwithid}}">content here</div>
First, I want to echo #david004's answer, this is almost certainly not the correct way to solve an AngularJS problem. You can think of it this way: you are trying to make decisions on what to show based on something in the view (the id of an element), rather than the model, as Angular encourages as an MVC framework.
However, if you disagree and believe you have a legitimate use case for this functionality, then there is a way to do this that will work even if you change the id that you wish to view. The limitation with #david004's approach is that unless showdivwithid is set by the time the directive's link function runs, it won't work. And if the property on the scope changes later, the DOM will not update at all correctly.
So here is a similar but different directive approach that will give you conditional hiding of an element based on its id, and will update if the keep-if-id attribute value changes:
app.directive("keepIfId", function(){
return {
restrict: 'A',
transclude: true,
scope: {
keepIfId: '#'
},
template: '<div ng-transclude ng-if="idMatches"></div>',
link: function (scope, element, atts) {
scope.idMatches = false;
scope.$watch('keepIfId', function (id) {
scope.idMatches = atts.id === id;
});
}
};
});
Here is the Plunkr to see it in action.
Update: Why your directives aren't working
As mentioned in the comments on #david004's answer, you are definitely doing things in the wrong way (for AngularJS) by trying to create your article markup in blog.js using jQuery. You should instead be querying for the XML data in BlogController and populating a property on the scope with the results (in JSON/JS format) as an array. Then you use ng-repeat in your markup to repeat the markup for each item in the array.
However, if you must just "get it working", and with full knowledge that you are doing a hacky thing, and that the people who have to maintain your code may hate you for it, then know the following: AngularJS directives do not work until the markup is compiled (using the $compile service).
Compilation happens automatically for you if you use AngularJS the expected, correct way. For example, when using ng-view, after it loads the HTML for the view, it compiles it.
But since you are going "behind Angular's back" and adding DOM without telling it, it has no idea it needs to compile your new markup.
However, you can tell it to do so in your jQuery code (again, if you must).
First, get a reference to the $compile service from the AngularJS dependency injector, $injector:
var $compile = angular.element(document.body).injector().get('$compile');
Next, get the correct scope for the place in the DOM where you are adding these nodes:
var scope = angular.element('.blog-main').scope();
Finally, call $compile for each item, passing in the item markup and the scope:
var compiledNode = $compile(itm)(scope);
This gives you back a compiled node that you should be able to insert into the DOM correctly:
$('.blog-main').append(compiledNode);
Note: I am not 100% sure you can compile before inserting into the DOM like this.
So your final $.each() in blog.js should be something like:
var $compile = angular.element(document.body).injector().get('$compile'),
scope = angular.element('.blog-main').scope();
$.each(items, function(idx, itm) {
var compiledNode = $compile(itm)(scope);
$('.blog-main').append(compiledNode);
compiledNode.readmore();
});

Categories

Resources