AngularJS - using a custom directive to 'emulate' Knockout 'with' binding - angularjs

The topic title is extremely deceptive, but I could not think of a different way to briefly word this.
Actual Question
(tl;dr)
How can I create a part of the HTML where any expressions are in the immediate context of a given object, like this:
<div [i want to use $scope.user]>
{{ Name }} // this is really $scope.user.Name
{{ Joined }} // this is really $scope.user.Joined
{{ Email }} // this is really $scope.user.Email
</div>
Details
(long winded explanation of why I am lost)
I know that I can do this using a Controller, but from what I am grasping, that isn't what I should do. The tutorials I read all seem to accomplish this, but I'm still left pretty confused.
I know this is doable - and in fact quite normal, in angular. But essentially my goal is to take code like this ...
<div ng-repeat="item in Items">
{{ item.Name }}
{{ item.Price }}
</div>
And use it on a slightly more personal level on non-repeated code. I believe this would be a custom directive? I've been reading a lot of resources, and they're very useful, but I'm having a difficult time associating the relationship between the DOM and the options of the directive. I'll try hard to explain...
Assume I have a model like this;
{
Name: "Ciel",
Joined: "2014/7/30",
email: "emailaddress#domain.com"
}
My understanding is that I would actually wire this in the $scope part of my controller, so .. I think it would go like this?
(function(){
var app = angular.module("program", []);
app.controller("UserController", function($scope){
$scope.user = {
Name: "Ciel",
Joined: "2014/7/30",
Email: "emailaddress#domain.com"
};
});
})();
Now, if I am grasping it right, I should be able to create a directive? to actually use this fluently in my HTML, kind of like this ...
<div ng-model="user">
{{ Name }}
{{ Joined }}
{{ Email }}
</div>
But when I tried this out, I am getting a bit confused. Because this doesn't actually seem to do what I thought. I guess what I am really lost on is how we're using ng-model here. I thought it was more like knockout's with: user, so it would be more like this;
<div data-bind="with: user">
{{ Name }}
{{ Joined }}
{{ Email }}
</div>
But that isn't seeming to be the case. When I tried running this on my own, I seem to be required to do it like this..
<div>
{{ user.Name }}
{{ user.Joined }}
{{ user.Email }}
</div>
And ng-model doesn't even seem needed.

A directive! See fiddle: http://jsfiddle.net/8drZr/1/
app.directive('with', ['$parse', function($parse) {
return {
restrict: 'A',
scope: true,
link: function(scope, elem, attrs) {
var withContext = $parse(attrs['with'])(scope.$parent);
angular.extend(scope, withContext);
}
};
}]);
The trick is setting scope: true. It will create a new child scope so as not to pollute the parent and the directive places everything under the given "with" object in this new scope using angular.extend() (for those familiar with jQuery, same as $.extend()).
Now there is a limitation, as pointed out in the comments. Two way binding between top-level properties (*) will not occur. This is a known limitation of Angular and the prototypical inheritance of scopes.
(*) By top-level properties I mean bindings like <input ng-model="value" />; non-top-level would be <input ng-model="bar.value" />
A more complicated implementation can surmount this problem, at the cost of more watches. A naive first implementation can be seen here: http://jsfiddle.net/mxG5r/1/
It is naive because for( x in withContext ) will iterate over properties that may not be desirable to watch, e.g. functions/methods.

Related

AngularJS Expression Not Displaying

I'm simply trying to get an AngularJS expression to display on screen. However, nothing shows up between the curly braces. I've inspected the app with ng-inspector and although I see an object being created with an ng-model directive, I can't display the value with the object key.
Furthermore, for testing purposes, I can't even get a simple math expression to display either.
Here's what I'm working with.
<body ng-app="angularApp">
<div ng-controller="firstCtrl">
<input ng-model="project.completed" type="checkbox">
<input ng-model="project.title" type="text" placeholder="Project Title">
<label>
{{project.title}}
1+2={{1 + 2}}
</label>
<input ng-model="project.time" type="text" placeholder="Project Time">
<label for="">{{project.time}}</label>
<button ng-click="helloWorld()">Press Me</button>
</div>
</body>
...and here's the controller:
angular.module('angularApp', [])
.controller('firstCtrl', function($scope) {
$scope.helloWorld = function() {
console.log('You just pressed the button.');
};
$scope.project = {
completed :false,
title :'test',
};
});
The only thing that shows up in the label is '1+2='.
UPDATE: After spending a ridiculous amount of time trying to debug this I have been able to get the first value of the math expression to display -- the '1'. I achieved this by adding a space around the '+' operator. Still, the full expression is not evaluating.
If you're using another templating engine, such as Twig, Liquid, or Django, the curly braces may be being stripped out. This results in the values not displaying or evaluating properly.
The solution I found is editing the interpolation characters or $interpolateProvider like so inside your controller:
angular.module('angularApp', []).config(function($interpolateProvider){
$interpolateProvider.startSymbol('{[{').endSymbol('}]}');
})
Then, just wrap your expression in the new symbols, e.g.:
{[{ 1+2 }]}
...or
{[{ project.title }]}
I had the same issue and finally found it, simply open your web page and press f12 and view the console :) Also remove any unused css. Additionally make sure that you add the "ng-controller" in the tag or some other broad scope so its well covered within the scope.

AngularJS: apply filter for ngStyle

Good day for everyone!
I have a problem with understanding AngularJS. Can I use my custom filter within ngStyle directive? Why it can't change opacity of span tag at the same time when I change value in input (but it change value in markup)? How I can realize this behaviour without direct using controller scope?
My raw code:
HTML:
<div ng-app="app">
<div ng-controller="AppCtrl">
<input type="number" ng-model="slider" max="10" min="1">
<span ng-style="{'opacity': '{{slider | filter}}'}">TEXT</span>
</div>
</div>
JS:
(function () {
angular
.module('app', [])
.controller('AppCtrl', ['$scope', function ($scope) {
$scope.slider = 6;
}])
.filter('filter', function () {
return function (input) {
return 0.1 * input;
};
});
})();
My code at JSFiddle: http://jsfiddle.net/zkdkLac3/
Answering the general question, yes, generally you can use an user created filter in generic angular expressions. You might be having issues with ng-attr due to a parsing error (probably a bug in the angular parser). You can still use filters in ng-attr with
<span ng-style="{ 'opacity': (slider | opacity) }">TEXT</span>
ng-attr though is most beneficial for binding to style objects directly
<span ng-style="sliderStyle">TEXT</span>
you can also style directly by using
<span style="opacity: {{slider|opacity}}">TEXT</span>
with the below filter:
app.filter('opacity', function () {
return function (input) {
return 0.1 * input;
};
});
Working jsfiddle
Whichever solution is better mainly depends on where you plan to re-use things. Filters are available across all scopes, but this one in particular might only make sense for a given controller. Don't forget that reuse can be accomplished with directives (which can have a controller) as well.

Is it possible to delete all scope variable of a controller? AngularJS

I am new to AngularJS and i dont know is it possible to delete all scope variables of a controller.I am using ng-controller with ng-repeat, like this.
<div ng-controller="main">
<div ng-repeat="x in list" ng-controller="test">
<input type="text" ng-model="text">
<span ng-click="remove($index)"> x </span>
<div>
</div>
JS
myapp.controller('main',function($scope){
$scope.list=[1,2,3,4]
})
myapp.controller('test',function($scope){
$scope.text="untitiled"
})
I want to remove the clicked scope.Can anyone help me or please suggest me a better way. Thanks
The question isn't very clear, but it looks like you may want to remove the item after clicking. Since you are passing into the remove function the index, you can splice it out. The DOM will autoupdate and remove that from the list:
$scope.remove = function(i) {
$scope.list.splice(i,1);
console.log($scope.list);
}
In the event you are doing something different in that you only want to hide it, you would push the index onto another array and then use something like ng-show or ng-hide.
$scope.remove2 = function(i) {
$scope.hideList.push(i);
}
$scope.shouldHide = function(i) {
return $scope.hideList.indexOf(i)!=-1;
}
<div ng-repeat="number in list2" >
{{number}}
<span ng-hide='shouldHide($index)' ng-click="remove2($index)"> x </span>
</div>
Here is a simple example of both scenarios. In real life, usually we are dealing with arrays of objects and what you might be doing is setting a property on one of the objects to hidden and controlling it that way.
Demo: http://plnkr.co/edit/G7UINKUCBJ4yZhQNtuJ2?p=info
If you actually want to remove all the keys from the scope:
function removeKeys() {
for(key in $scope) {
if (key.substr(0,1)!='$' && key!='this')
delete $scope[key];
}
}

Avoiding too many Angularjs Directives

I have the following directive:
.directive('myDirective', function() {
restrict: 'A',
templateUrl: 'app/templates/someTemplate/html',
});
in my template (someTemplate.html) I have the following:
<div>
<div>Some div</div>
<input type="button" value="button" />
</div>
I want to add behavior to the button and div. I can go the route of adding directives like so:
<div>
<div div-click>Some div</div>
<input type="button" value="button" button-click />
</div>
and adding more directives and binding click events via element.bind(... but is there a best practice? Should I be adding behavior in the 'myDirective' containing those elements? via jQuery or jQlite . The clickable elements inside the template are not meant to be resuable..so should I just use jQuery to find those elements and bind event listeners to them? I can see how their can be a directives explosion by constantly using the directive route, what is the best practice?
The question for me would be on what exactly the directives should be for.
It sounds to me, as if you are trying to wrap functionality that you know from other frameworks like jQuery into directives. this leads to stuff like:
var app = angular.module("module.directives", []);
app.directive('myDirective', function() {
restrict: 'A',
templateUrl: 'app/templates/someTemplate/html',
link: function(scope, el) {
el.on("click", function() { console.log(42); });
}
});
While certainly possible, this is (at least for me) considered "bad" style.
The difference with Angular is, that it does not use the DOM as the "Model" part of the framework, like jQuery or Prototype do. Coming from these libraries this is something to wrap your head around, but actually, for starters, it boils down to this:
Work with the scope and let the changes to the scope be reflected in the DOM.
The reflection part is actually the short and easy one: Angular does this out of the box (i.e. "most of the time").
Reconsidering your example with the click - Angular provides excellent event handlers in the form of directives. ng-click is a very good example for this:
<div>
<div ng-click="method()">Some div</div>
<input type="button" value="button" ng-click="method2()" />
</div>
This directive takes an expression - it looks a bit like the old days, where you would bind javascript directly to elements, like this:
here
It's way different though - Angular will look for the names method and method2 on the current scope you are in. Which scope you are currently in depends on the circumstances (I heavily suggest the docs at this point)
For all of our intents and purposes, lets say, you configure a controller inside your directive from earlier:
var app = angular.module("module.directives", []);
app.directive('myDirective', function() {
restrict: 'A',
templateUrl: 'app/templates/someTemplate/html',
controller: ['$scope', function(scope) {
scope.active = false;
scope.method = function() { console.log(42); };
scope.method2 = function() { scope.active = !scope.active };
}]
});
You can define this in many places, even as late as during the link phase of a directive. You can also create an extra controller in a separate module. But let's just stick with this for a moment:
In the template - when you click on your div the scope's method will be called. Nothing fancy, just console output. method2 is a little bit more interesting - it changes the active variable on the scope. And you can use this to your advantage:
<div>
<div ng-click="method()">Some div</div>
<input type="button" value="button" ng-click="method2()" />
<span ng-show="active">Active</span>
</div>
When you click on your button, the span will be turned on and of - the ng-show directive handles this for you.
This has gotten a bit longer than expected - I hope though, that this sheds some light on the "best practises" (which are quite dependent on what you actually want to accomplish).

AngularJs - Access to DOM element inside ng-repeat

I have the next template:
<div ng-repeat="friend in friends | filter:filterFriendsHandler">
{{friend.name}}
</div>
and in my controller i have:
$scope.filterFriendsHandler = function(friend){
//HERE I WANT TO ACCESS TO FRIEND DOM ELEMENT; to do something like this:
//$(friendElement).text('foo');
}
Thanks
I'm going to answer the specific question here, yes I understand this isn't the "angular" way of doing things. If you want to do things the "correct" way, then don't do this, use a directive. There, disclaimers aside, here's how to do it:
Basically, what you want to do is give the DOM element an ID based on the $index or a unique value in your ng-repeat object. Here, I'll just use $index.
<div ng-repeat="friend in friends" id="friend_{{$index}}" ng-bind-html="doSomethingBadToTheDom('friend_' + $index)">
{{friend.title}}
</div>
Then, inside your controller, just query the DOM for the element with that ID:
$scope.doSomethingBadToTheDom = function(ele_id) {
var element = document.getElementById(ele_id);
element.innerHTML = "I'm abusing angular";
}
We're using ng-bind-html here because the DOM element will exist when your controller function executes, in the case of something like ng-init, it won't.
Again, this goes against everything angular stands for, so if you're trying to follow angular best practices, don't do this.
I've run into situations where the technique is useful though, especially when dealing with non-angular libraries, or those times when the "angular way" is more trouble than it's worth.
You need to use a directive for that
<div ng-app="test-app" ng-controller="MyController">
<div ng-repeat="friend in friends" nop>
{{friend.title}}
</div>
</div>
JS
app.directive('nop', function(){
return {
link: function(scope, elm){
console.log('eee', elm, arguments);
elm.css('color', 'red');
}
}
});
demo: Fiddle

Resources