Let's say I have a directive on the page like so:
<outer inner-type="custom" ng-model="model" options="options text"></outer>
Does anyone know of an effective way to have it output, without re-compiling and with the ng-model reference preserved, something to the effect of:
<outer>
<!-- OTHER WRAPPER STUFF -->
<input ng-model="model" />
<!-- CONTROL TYPE SELECTED WITH NG-SWITCH OR NG-IF -->
<custom wrap-type="custom" ng-model="model" options="options text"></custom>
</outer>
I don't want to just transclude a bunch of stuff into the directive.
EDIT:
Transclusion messiness:
<outer ng-model="model.properties.property">
<custom wrap-type="custom" ng-model="model.properties.property" options="options text"></custom>
</outer>
The ng-model reference in outer is necessary for the input, and is repeated again in the inner part. In any case, I'm pretty sure this would have problems when isolate scopes become involved.
EDIT:
Here's a plunker with code that creates a direct edit option as an example: http://plnkr.co/edit/rXaMrL?p=preview.
Related
This question already has answers here:
What are the nuances of scope prototypal / prototypical inheritance in AngularJS?
(3 answers)
Closed 5 years ago.
I'm wondering what's happening behind the scenes when using ng-init.
Sometimes ng-init does some unexpected things, and I have a hard time debugging.
Let's say I have the following structure:
<!-- App -->
<div ui-view>
<!-- Component -->
<custom-component>
<!-- Transcluded component -->
<transcluded-component>
<!-- Controller -->
<div ng-controller="MyCtrl">
<!-- Element -->
<div ng-init="myVar = 'hello'">
{{myVar}}
</div>
</div>
</transcluded-component>
</custom-component>
</div>
Which element does myVar bind to?
Edit 2017-07-21: Added an example
In the following template block (especially within an ng-repeat), I may use an ng-init:
<span ng-init="path = getPath()">
<a ng-if="path" ng-href="path">
Click here
</a>
<span ng-if="!path">
Some text
</span>
</span>
In this case, I skipped a function call twice, and kept my template clean.
Sometimes ng-init does some unexpected things, and I have a hard time debugging.
The ng-init directive evaluates an Angular Expression in the context of the scope of its element. Numerous directives (ng-repeat, ng-controller, ng-if, ng-view, etc.) create their own scope.
For more information, see
AngularJS Developer Reference - scopes
AngularJS Wiki - Nuances of Scope Prototypical Inheritance
Avoid using ng-init
Avoid using ng-init. It tangles the Model and View, making code difficult to understand, debug and maintain. Instead initialize the Model in the controller.
From the Docs:
ngInit
This directive can be abused to add unnecessary amounts of logic into your templates. There are only a few appropriate uses of ngInit, such as for aliasing special properties of ngRepeat, as seen in the demo below; and for injecting data via server side scripting. Besides these few cases, you should use controllers rather than ngInit to initialize values on a scope.
— AngularJS ng-init Directive API Reference
Update
Q: I've also added an example of a code block I sometime use.
<!-- Controller -->
<div ng-controller="MyCtrl">
<!-- Element -->
<div ng-init="myVar = 'hello'">
{{myVar}}
</div>
</div>
To do the equivalent initialization in the controller:
app.controller("MyVar", function($scope) {
this.$onInit = function() {
$scope.myVar = 'hello';
};
});
By separating concerns of Model and View, the code becomes easier to understand, debug and maintain.
I have created a dropdown list like feature using angualrjs directive, The directive is working somewhat but not in correct way I expected.
Following are the issues which I am facing.
The drop-down list alignment is correct for static but not for dynamic
I am not able to select any of the option list since getValue function which I have defined in the parent directive is not been invoked from the trancluded directive
Can anyone please tell me some solution for this
My code is as given below
PLUNKER
<div ng-controller="MainCtrl">
Static
<grant-parent ng-model="grand1">
<parent label="Parent1" value="12">
<child value="56" label="Child1">Child1</child>
<child value="57" label="Child2">Child2</child>
</parent>
<parent label="Parent1" value="13">
<child value="58" label="Child3">Child3</child>
<child value="59" label="Child4">Child4</child>
</parent>
</grant-parent>
Dynamic
<grant-parent ng-model="grand2">
<parent ng-repeat="parent in data" label="{{parent.parentName}}" value="{{parent.parentId}}">
<child ng-repeat="child in parent.childrens" label="{{child.name}}" value="{{child.id}}">{{child.name}}</child>
</parent>
</grant-parent>
</div>
Transclusion and ng-repeat have caused me headaches, and I thought it would be challenging, but the solution proves quite simple:
Remove the DOM manipulation from your link function and do the transclusion in the template!
I.e. <div ng-transclude></div> in the template of the parent and remove this line: elm.find('div').replaceWith(transclude()).
Forked plunk: http://plnkr.co/edit/UGp6D29yxnu0uJwD1BM2?p=preview
Now the markup comes out a bit different, the wrapper <div> still exists. Although there seems to be no visual difference, this may not be what you want. I do not think there is a sane way to get around this, so I would suggest altering the layout a bit: Why don't you place the children inside the parent <li>, e.g. as:
<li>
<b><a href='#' ng-click='getValue(optGroupLabel,optGroupValue)'>{{optGroupLabel}}<span class='value'>{{optGroupValue}}</span></a></b>
<div ng-transclude></div><!-- the div is now inside the li -->
</li>
This works in the plunker, but the markup is still invalid (<li> within <div>).
The best solution is to wrap the children in their own <ul>, i.e.:
<li>
<b><a href='#' ng-click='getValue(optGroupLabel,optGroupValue)'>{{optGroupLabel}}<span class='value'>{{optGroupValue}}</span></a></b>
<ul ng-transclude></ul><!-- The div is replaced with ul -->
</li>
This does not work in the plunk as it is, but should work with a little CSS tweaking.
Concerning getValue You have gotten wrong how isolated scopes and transclusion work. The grandParent directive defines the getValue method in its isolated scope. The transcluded things (the parent and child directives) get the outer scope, i.e. the scope of the MainCtrl. A solution is to move getValue() to the MainCtrl.
A better solution would be to pass a callback to the descendants of the grandparent, e.g. as scope: { assignValue: '&' }. But this solution cannot be implemented for the code in its current form because the grandparent does not directly include its children, so it cannot pass arguments to them.
The final solution - copied from the comments: move getValue to the controller of grandParent, have the parent and children require the grandparent and call that function. See http://plnkr.co/edit/pS9SspLaoPlqoWMYr8I0?p=preview
New to Angular and need some assistance.
I have a block of HTML content that will be coming from a database that will contain a group of widgets. These are simple widgets that will essentially render out various elements, but for the purposes of this question we'll assume they're all basic HTML inside.
Those widgets are included in an unpredictable way, so my first thought was to use directives to render the HTML. So, we'd have something like:
<div widget data="This is the content."></div>
So I've got a directive that will place the value of data into the div. Easy enough!
Now, how would I go about nesting those widgets? So, how would I get something like:
<div widget data="Welcome! ">
<div widget data="This is some inside content."></div>
</div>
to render out:
Welcome! This is some inside content.
... because the issue I'm noticing is that if I place anything inside the directive HTML, it essentially gets ignored since it gets replaced with its own result (thus only echoing out Welcome!).
I realize I may be going the wrong direction on this in the first place, so any insight would be greatly appreciated. Thanks so much!
This is where you need to use the transclusion feature of the directive combined with ng-transclude directive.
Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion.
Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted.
A very basic version of transclusion of content based on your example might look something like this:
.directive('widget', function() {
return {
transclude: true,//Set transclusion
template: '{{text}} <section ng-transclude></section>', <!-- set where you need to present the transcluded content -->
scope: {
text: "#"
}
}
});
Demo
angular.module('app', []).directive('widget', function() {
return {
transclude: true,
template: '{{text}} <section ng-transclude></section>',
scope: {
text: "#"
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<span widget data-text="Welcome! ">
<div widget data-text="This is some inside content.">
<span widget data-text="This is some inside inside content."></span>
</div>
</span>
</div>
In brief
I'm looking for a cleaner way to work around an issue that involves isolate scopes. I'm not sure there's a better workaround that what I have, but I hope so as I'm not too happy with it.
The demo
Demo on Plunkr
Contains both a directive that shows the problem, and another one with the dirty fix.
Change the values of the inputs and see it doesn't get propagated for one of them.
The story
I wrote a directive that contains a ng-switch. The code is:
angular.module('core')
.directive('otherSearchField', function() {
return {
templateUrl: 'otherSearchField.html',
restrict: 'E',
scope: {
field: '=',
placeholder: '#',
condition: '#searchWhen'
}
};
});
Its template is:
<section ng-switch="condition">
<div ng-switch-when="true">
<input type="text" ng-model="field" placeholder="{{placeholder}}">
<button ng-click="search()">Search</button>
</div>
<div ng-switch-default>
{{field}}
</div>
</section>
I could rewrite it as a ng-if, but what matters really is that in both cases, a new scope is created by ng-switch or ng-if.
I use the directive this way:
<div ng-controller="Ctrl">
<other-search-field field="query.city" placeholder="City" search-when="{{edition.city}}"></other-search-field>
</div>
The issue
As you notice, in the directive template, we have an input bound to "field". That one is bound to the calling template via the = notation in the directive definition.
However, because we're not using the object notation, entering something in the input modifies the field in the ng-switch's scope, but does not propagate out of it.
A (dirty) solution
My current solution is to use the object notation in the directive's template, which means I need to pass the containing object to the directive, and the name of the property I want to modify.
<section ng-switch="condition">
<div ng-switch-when="true">
<input type="text" ng-model="fieldParent[field]" placeholder="{{placeholder}}">
<button ng-click="search()">Search</button>
</div>
<div ng-switch-default>
{{fieldParent[field]}}
</div>
</section>
Usage:
<my-search-field field-parent="query" field="customer" placeholder="Customer" search-when="{{edition.customer}}"></my-search-field>
This works: I'm using the object notation so the changes on the input propagate all the way up (see on the Plunkr linked above).
So ?
Hmm, I'm not too happy to pass an entire object when only one of its properties is needed though. Is there a better way ?
NB
Note I could also, in this case, use multiple ng-show as they don't create their own scope. But I'm interested in the more general issue shown here, not in this specific case.
Thanks for reading all the way. Kudos to you !
In your otherSearchField.html template, you can access the $parent $scope's field property, thereby accessing the isolate scope that your directive <other-search-field> is using.
DEMO
<section ng-switch="condition">
<div ng-switch-when="true">
<input type="text" ng-model="$parent.field" placeholder="{{placeholder}}">
<button ng-click="search()">Search</button>
<div>value in the template, in "switch": {{$parent.field}}</div>
</div>
<div ng-switch-default>
{{field}}
</div>
</section>
<div>
value in the template, out of "switch": {{field}}
</div>
I have the following main view
<div ng-include="'otions.html'"></div>
and options.html has the following
<div ng-controller="OptionsController">Options</div>
<input type="text" ng-model="keyword" value="{{keyword}}" />
<button ng-click="search()">Search</button>
But "keyword" is not binding to the scope in OptionsController.
app.controller('OptionsController',
['$scope', function($scope) {
$scope.keyword = "all";
$scope.search = function() {
console.log("hello")
};
}]);
when I click on the button, I don't see hello and the keyword all doesn't appear in the input text.
I tried moving the ng-controller part as follows
<div ng-controller="OptionsController" ng-include="'otions.html'"></div>
And things work as expected.
I read through the answers in AngularJS - losing scope when using ng-include - and I think my problem is related, but just need some more explanation to undertstand what's going on.
You should write options.html like this:
<div ng-controller="OptionsController">Options
<input type="text" ng-model="keyword" value="{{keyword}}" />
<button ng-click="search()">Search</button>
</div>
OptionsController should be put in the outer html element.
i have face same problem,
Under main tag it will be work fine but, When bind some data to nav.html it not work.
find this link
AngularJS - losing scope when using ng-include
inside include its work child scope.
better option to create custom directive its easy solution