computed binding updates when function changes, is it a feature? - polymer-1.0

When I was looking for a way to have Polymer 1.0 update a computed binding, I noticed that when I declared the computing function as a property, setting the property value to a new function does update. Can I rely on that behavior or is it a coincidence?
<dom-module id="flip-fn">
<template>
<p on-tap="flip">{{fn()}}</p>
</template>
<script>
(function(){
function fn1(){return 'val1';}
function fn2(){return 'val2';}
Polymer({
is: 'flip-fn',
properties:{
fn: {type: Object, value:function(){return fn1;}}
},
flip:function(){
this.fn = fn2;
}
});
})();
</script>
</dom-module>
Noticeably, when I use that computed binding in a dom-if it does not work:
...
<p on-tap="flip">{{fn()}}</p> <!-- this works -->
<template is="dom-if" if="true">
<p>{{fn()}}</p> <!-- does not show any text -->
</template>

I think you can rely on it.
The example https://www.polymer-project.org/1.0/docs/devguide/templates.html#dynamic-sort-and-filter-changes uses dynamic filter that also uses this feature.
A function reference is also just an object. When polymer checks identity, it gets a false if it refers a different function and has to treat it as change.

Related

simple angular checkboxes

I have a T/F property called "valid" returning from an API call. I want to accurately display it as a checkbox, as well as allow the user to set/unset it. Setting or unsetting it will make a SAVE call.
{{vm.selectedQuestion}}
<md-checkbox
aria-label="Confirmed"
ng-model="vm.selectedQuestion.valid"
ng-click="vm.setReviewed()"
ng-checked="vm.selectedQuestion.valid">
Valid
</md-checkbox>
.
vm.setReviewed = function () {
vm.selectedQuestion.valid = !vm.selectedQuestion.valid;
// a bunch of other stuff
};
If the question gets loaded with value: true, then I see the checked box. If I uncheck the box, the valid property disappears from the object completely.
I know I'm doing something wrong with the ng-model and the ng-checked, but I've tried every combination I can think of.
Astonishingly, the angular docs and examples do not seem to address this simple case as far as I have found.
I would simply change it to:
{{vm.selectedQuestion}}
<md-checkbox
aria-label="Confirmed"
ng-model="vm.selectedQuestion.valid"
ng-change="vm.setReviewed()">
Valid
</md-checkbox>
with the function:
vm.setReviewed = function () {
// a bunch of other stuff depending on the value of vm.selectedQuestion.valid
};
Usually there is no need to over-complicate it (unless there is something about md-checkbox that I am not aware of). There is also no need to manually toggle the value in the ng-change (it's already changed by ng-model, and the value of vm.selectedQuestion.valid you see in the function is already after the change).
Here is the link for the docs of md-checkbox directive with a clear example:
https://material.angularjs.org/latest/api/directive/mdCheckbox
And the ng-change is as ng-change does, until angular goes v2, and then all bets are off. :)
A few notes on directives in question (with input from OP comment):
ng-click happens before the change of the ng-model and the checkbox in the element
ng-change happens after the change in the ng-model and the element
ng-checked is used only to set the checked attribute of the element if it's evaluated to truthy - should not be used alongside ng-model
Try following snippet
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.name = true;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div ng-app="myApp">
<div ng-controller="MyCtrl">
<input type="checkbox" ng-checked="name" ng-model="name">{{name}}</div>
</div>
</div>
Hope this will help you

Angular - compare many old and new values

Using Angular, is it possible to compare an old and new value when it is changed (programmatically, or otherwise)?
I guess what I'm after is functionality similar to ng-change that can be applied to any element and is triggered whenever the value in the expression is updated.
Of course, I could use $scope.$watch on the entire data model, find the individual changed values (and injecting a 'changeDirection' property) but this feels excessive.
Edit: Consider a data model of an array of 500 objects, each with 5 properties, 2 of which are integers that I need to know if they have increased/decreased.
You can use $watch on dot delimited paths to signify individual properties in the model you wish to watch.
<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js#1.4.8" data-semver="1.4.8" src="https://code.angularjs.org/1.4.8/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body>
<div ng-controller="ctrl">
<input ng-model="model.name.first" />Update:{{watchVals}}
</div>
<script>
var app = angular.module("app", []);
app.controller("ctrl", ["$scope",
function($scope) {
$scope.model = {
"name": {
"first": "John",
"last": "Doe"
}
};
$scope.val = "";
$scope.$watch("model.name.first", function(newVal, oldVal) {
$scope.watchVals = oldVal + " -> " + newVal;
});
}
]);
angular.bootstrap(document, [app.name]);
</script>
</body>
</html>
i recommend use $watch for watching object changes and you can pass new value and old value to function like this:
$scope.$watch('myObject', function(newValue, oldValue){
if(!angular.equals(newValue, oldValue)){
doSomeThing();
}
});
There are few key differences between ng-change (angular built in directive) and $watch.
Consider this html markup
<input type=text ng-model=obj.name ng-change="triggerNameChanged()">
Using ng-change would call triggerNameChanged() function only on the
actual changes to the input by the user.However Watch are also called in
other cases too- right when they’re being defined the first time and
on changes made to the value not by the user, e.g. programmatically.
Whenever you write {{}} expression internally a watch is set up to look for changes in your model.
Using ng-change is a bit more performant, since it uses one less watch expression. Since Angular knows it should only call the expression on change events it does not need to keep evaluating it on every digest cycle, as opposed to watch.
By default $watch compares by reference.So If you set the third parameter to true, Angular will instead "shallow" watch the object for changes.
An Example of watch
$scope.$watch('myForm.modified', handler)
in this case handler will be called if some form elements actually contains new data or if it reversed to initial state.you can use modified property of individual form elements to actually reduce amount of data sent to a server .

How to avoid "sausage" type binding when having nested model

I have nested model and I am trying to avoid vm.someObject.someChild.ChildOfChild.name type of situations. Is there a way to set the model for outer <div> so that I can instead do ChildOfChild.name or even name. In Silverlight this was called DataContext. I put "vm" on the $scope, but in html I would like to avoid having to type the full path to attribute.
For example:
<div>
{{someObject.Id}}
<div>
{{someObject.name.first}}
{{someObject.name.last}}
</div>
<div>
{{someObject.someChild.name.first}}
</div>
</div>
I would like to do something like this
<div datacontext = someObject>
{{Id}}
<div datacontext = name>
{{first}}
{{last}}
</div>
<div datacontext = someChild.name>
{{first}}
</div>
</div>
You can do this with a custom directive.
HTML:
<div ng-app="myApp" ng-controller="myCtrl as ctrl">
<div>
Access from deepObj: {{ctrl.deepObj.one.two.three.four}}
</div>
<div scope-context="ctrl.deepObj.one.two.three">
Access from third level: {{four}}
</div>
</div>
JS:
var myApp = angular.module('myApp', []);
var myCtrl = function() {
this.deepObj = {one: {two: {three: {four: "value"}}}};
};
myApp.directive('scopeContext', function () {
return {
restrict:'A',
scope: true,
link:function (scope, element, attrs) {
var scopeContext = scope.$eval(attrs.scopeContext);
angular.extend(scope, scopeContext);
}
};
});
See the documentation on $compile for information on what scope: true does.
Make sure you don't call the directive something like data-context as an attribute starting with data- has a special meaning in HTML5.
Here is the plunker: http://plnkr.co/edit/rMUQlaNsH8RTWiRrmohx?p=preview
Note that this can break two-way bindings for primitive values on the scope context. See this plunker for an example: http://plnkr.co/edit/lCuNMxVaLY4l4k5tzHAn?p=preview
You could try/abuse ng-init
Try ng-init, you'll have one more ., but it's better than the other answer I've seen proposed:
<div ng-init="x = foo.bar.baz">
{{x.id}}
{{x.name}}
</div>
BUT Be warned, doing this actually creates a value on your scope, so doing this with something like ng-model if you're reusing the same name, or in a repeater, will produce unexpected results.
Why a custom directive for this probably isn't a good idea
What #rob suggests above is clever, and I've seen it suggested before. But there are issues, which he touches on, in part at least:
Scope complexity: Adding n-scopes that need to be created (with prototypical inheritence) whenever views are compiled.
View processing complexity: Adding an additional directive (again for no real functional benefit) that needs to be checked on each node when the view is compiled.*
Readability? The next Angular developer will likely less readable because it's different.
Forms Validation: If you're doing anything with forms in Angular, this might break things like validation.
ng-model woahs: Setting things with ng-model this way will not be at all intuitive. You'll have to use $parent.whatever or $parent.$parent.whatever depending on how may contexts deep you are.
* For reference, views are $compiled more than you think: For every item in a repeater, whenever it's changed for example.
A common idea that just doesn't jive with Angular
I feel like this question comes up frequently in StackOverflow, but I'm unable to find other similar questions ATM. ... regardless, if you look at the approaches above, and the warnings given about what the side effects will be, you should be able to discern it's probably not a good idea to do what you're trying to do just for the sake of readability.

Backbone, not "this.el" wrapping

I do an extensive use of templates, and I like to use full contained templates. I mean that I want to see in the template code all the DOM elements including the root one, like this:
<script type="text/template" id="template-card">
<div class="card box" id="card-<%= id %>">
<h2><%= title %></h2>
<div><%= name %></div>
</div>
</script>
But what Backbone likes is having a template like this:
<script type="text/template" id="template-card">
<h2><%= title %></h2>
<div><%= name %></div>
</script>
And defining the root element and its attributes in the JS code. What I think is ugly and confusing.
So, any good way to avoiding my Backbone View to wrapper my template with an extra DOM element?
I have been checking this issue thread: https://github.com/documentcloud/backbone/issues/546 and I understand there is not any official way to do it.. but maybe you can recommend me a non official way.
You can take advantage of view.setElement to render a complete template and use it as the view element.
setElement view.setElement(element)
If you'd like to apply a Backbone view to a different DOM element, use setElement, which will
also create the cached $el reference and move the view's delegated
events from the old element to the new one
Two points you have to account for:
setElement calls undelegateEvents, taking care of the view events, but be careful to remove all other events you might have set yourself.
setElement doesn't inject the element into the DOM, you have to handle that yourself.
That said, your view could look like this
var FullTemplateView = Backbone.View.extend({
render: function () {
var html, $oldel = this.$el, $newel;
html = /**however you build your html : by a template, hardcoded, ... **/;
$newel = $(html);
// rebind and replace the element in the view
this.setElement($newel);
// reinject the element in the DOM
$oldel.replaceWith($newel);
return this;
}
});
And a working example to play with http://jsfiddle.net/gNBLV/7/
Now you can also define a view's tagName as a function and create a class like this:
var MyView = Backbone.View.extend({
template: '#my-template',
tagName: function() {
// inspect the template to retrieve the tag name
},
render: function() {
// render the template and append its contents to the current element
}
});
Here's a working example
Backbone.Decarative.Views provides you with an alternative way to do this, without having to rely on setElement. For more, check out my answer here.

Getting to bound data in Angular.js

Is there a way in angular to get binding back from a template?
In other words, if I have something like this:
<div ng-repeat="item in list">
<div>{{item.name}}</div>
<div>{{item.state}}</div>
</div>
would it be possible to change the item's state by clicking on it, because the repeated div would "remember" what item it was built from?
Yes, you can use the ng-click directive to trigger a method on the current scope:
// In your view's controller:
function MyCtrl($scope, MyList) {
// You probably have something like that already to
// populate your list, using a $resource or $http GET call.
// Here I use a $resource which would be defined on your module.
$scope.list = MyList.query()
$scope.setState = function(state) {
// "this" refers to the current scope
this.item.state = state
}
}
// And in your view:
<div ng-repeat="item in list">
<div>{{item.name}}</div>
<div ng-click="setState('whatever')">{{item.state}}</div>
</div>
Or you can simply set an expression such as ng-click="item.state='whatever'" directly on the div, although this is less testable - only in end-to-end tests - and less flexible, say you want to introduce validation or something).
HTH

Resources