KnockoutJS animated transition - javascript

Can anyone explain why the animated transition example in the knockoutjs website here uses the template binding? The example uses this:
<div data-bind='template: { foreach: planetsToShow,
beforeRemove: hidePlanetElement,
afterAdd: showPlanetElement }'>
<div data-bind='attr: { "class": "planet " + type }, text: name'> </div>
</div>
But the following works just as well:
<div data-bind='foreach: {data: planetsToShow,
beforeRemove: hidePlanetElement,
afterAdd: showPlanetElement}'>
<div data-bind='attr: { "class": "planet " + type }, text: name'> </div>
</div>
Here is their original fiddle. Here is my updated fiddle. The use of the template binding seems extraneous. Thoughts?

There is no special reason why the sample uses the template binding.
On the top of the page it is even mentioned:
When using the template/foreach binding, you can provide afterAdd and beforeRemove callbacks.
So it does not matter whether you use the foreach binding or the template binding in "foreach mode", because internally the foreach binding just delegates back to the template binding.
So they are essentially doing the same thing only with different syntax, however you have more options if you are directly using the template binding:
The main difference is that when using the foreach binding you cannot use named templates you have to use the inline template, but the in template binding you can specify any template even change it dynamically.

Related

How to dynamically nest directives in AngularJS

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>

Angular directive with a ng-switch containing an input: can it be done cleanly?

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>

Knockout: afterRender not being called in a foreach template binding

I'm trying to use Knockout's afterRender binding, but the function that I reference is never called.
I have nested view-models:
vmConcepts and vmConcept, where vmConcepts.Concepts = array of vmConcept objects.
vmConcept (the inner model) has a function self.Rendered = function (elmnt) {...
I bind vmConcepts (the outer model) to the following markup (you can see that this calls a nested template):
<ul>
<!-- ko template: { name: 'concept-template', foreach: { data: Concepts, afterRender: Rendered } } --><!-- /ko -->
</ul>
My understanding is that this afterRender binding should be called for each vmConcept object (in vmConcepts.Concepts) passed to the concept-template template, but that doesn't happen. I've even added the same Rendered function to vmConcepts and that doesn't get hit either.
I've tried this as both a data-bind binding and as a virtual binding.
What am I missing?
Your bindings are messed up. The foreach inside template isn't the same as the foreach binding. The after rendered on the template will fire after every child.
Try this
<ul>
<!-- ko template: {
name: 'concept-template',
foreach: Concepts,
afterRender: Rendered
} --><!-- /ko -->
</ul>
I have a working example here:
http://jsfiddle.net/4t94G/1/

Knockout templates with same name and different templateUrl

I have the following problem.
I have two templates with the same names that constitute a editable and a readonly variant of the same template.
Two different places in my html (that is created dynamically, but it is this condition that gives me the problem) I use the template binding, and I want to bind to each of these.
The first one will then look like this:
<div data-bind="template: {name: 'myTemplate', data: $data, templateUrl: '/Templates/readonly/' }"></div>
And the second one looks like this
<div data-bind="template: {name: 'myTemplate', data: $data, templateUrl: '/Templates/editable/' }"></div>
The problem is that when I get to the second template binding, knockout will reuse the first template since it has the same name , and that will make both templates readonly.
So are there any way to make knockout download the second template if it resides in another location that the other one, or are there no way around having unique names on all of the templates.
Given the current logic, it will be a lot of work to change that, so I'm hoping that it can be done through the binding instead of renaming.
EDIT
I'm using the External Template Engine found here.
It seems that as long as the name is unique it will fetch it again (even if that will result in the same url.
So changing them to
<div data-bind="template: {name: 'readonly/myTemplate', data: $data, templateUrl: '/Templates/' }"></div>
<div data-bind="template: {name: 'editable/myTemplate', data: $data, templateUrl: '/Templates/' }"></div>
Made it work as I hoped, with no changes on the server side :)
Thanks to #pax162 for putting me on the correct track!

Exclude html element at ko.applyBindings

Here is a simplified version of the problem:
<div id="model-one-container" data-bind="css: {foo: someModelOneProperty}">
<div id="model-two-container" data-bind="text: someModelTwoProperty"></div>
<div data-bind="text: anotherModelOneProperty"></div>
</div>
.
ko.applyBindings(viewModelOne, document.getElementById("model-one-container"));
ko.applyBindings(viewModelTwo, document.getElementById("model-two-container"));
If I do that, knockout will complain that there isn't a "someModelTwoProperty" in viewModelOne, so I need to exclude the #model-two-container div from the first applyBindings.
Is there any way to do this without altering the view-models?
Here's the answer.
Since knockout 2.0 there is a controlsDescendantBindings flag which you can use to create a custom binding that then stops KO from binding on an element or any of its children.

Categories

Resources