Outside Angular context? - angularjs

I have following HTML and JS code:
HTML:
<!doctype html>
<html ng-app="test">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<div ng-controller="Ctrl2">
<span>{{result}}</span>
<br />
<button ng-click="a()">A</button>
<button my-button>B</button>
</div>
JS:
function Ctrl2($scope) {
$scope.result = 'Click Button to change this string';
$scope.a = function (e) {
$scope.result = 'A';
}
$scope.b = function (e) {
$scope.result = 'B';
}
}
var mod = angular.module('test', []);
mod.directive('myButton', function () {
return function (scope, element, attrs) {
//change scope.result from here works
//But not in bind functions
//scope.result = 'B';
element.bind('click', scope.b);
}
});
I bind click event to my-button and want to change $scope.result when user clicked button B (similar to ng-click:a() on button A). But the view won't update to the new $scope.result. Someone advised me to call $scope.$apply() at the bottom of your event handler.but $scope.$apply is called when variable will be outside angular context. How can I evaluate that on button "B" click event is outside angular context?

element.bind() is a low-level (jqLite) call that is not tracked by Angular. So you need to help Angular know that a change happened by calling $scope.$apply(); in the element.bind() handler.
Perhaps do:
element.bind('click', function () {
scope.$apply(function () {
scope.b();
});
});

Related

Custom angular directive : how to watch for scope changes

I am writing a custom directive with a single field in its scope. This field is a dictionary with arrays in values.
I want the directive to react to any change made on this field : new entry, additional value in list, etc...
I'm just trying to figure out why :
my directive does not react when I change values in the dictionary.
directive is not even initialized with the initial dictionary.
Here is a simplified version of my script, where I only perform some logging in the sub-directive.Nothing happens when the button is clicked on the dictionary is modified :
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Test</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script>
angular.module("myApp", [])
.controller("myCtrl", function($scope) {
$scope.dico = {};
$scope.dico["Paris"] = [];
$scope.dico["Paris"].push("Tour Eiffel");
$scope.dico["Paris"].push("Champs Elysees");
$scope.dico["London"] = [];
$scope.dico["London"].push("British Museum");
$scope.addPOI = function() {
$scope.dico["Wellington"] = [];
$scope.dico["Wellington"].push("Botanic Garden");
console.log($scope.dico);
};
})
.directive('subdirective', function() {
return {
restrict: 'E',
template: '<div><span ng-repeat="key in myDico">{{key}}</span></div>',
link: function(scope, element, iAttrs) {
console.log("test");
scope.$watch("myDico", function(newVal, oldVal) {
console.log("watch!");
console.log(newVal);
//update values...
}, true);
},
scope: {
myDico: '='
}
};
});
</script>
</head>
<body ng-app="myApp">
<div ng-controller="myCtrl">
<button ng-click="addPOI()">
Add POI
</button>
<div>
<subdirective myDico="dico"></subdirective>
</div>
</div>
</body>
</html>
I have tried to use $watch, $watchCollection, deep watch, but it does not seem to do the job.
You are missing scope binding definition in your Directive Definition Object.
scope: {
myDico: '='
}

how to check data model values changed angular?

I have an online Web form using angular.js. When the user edits the information and clicks back it loses the unsaved data. How can I check whether the data model values have changed and warn the user that they have unsaved information that needs to be saved before navigating away?
I have forked this example: plunker
angular.module('formExample', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.master = {};
$scope.update = function(user) {
$scope.master = angular.copy(user);
};
$scope.reset = function() {
$scope.user = angular.copy($scope.master);
};
$scope.reset();
}]);
I have used this directive to warn unsaved data when user leaves from the page inject this directive and change the messages
<html ng-app="myApp">
<head>
<script src="http://code.angularjs.org/1.1.2/angular.min.js"></script>
<script type="text/javascript">
function Ctrl($scope) {
var initial = {text: 'initial value'};
$scope.mySample = angular.copy(initial);
}
angular.module("myApp", []).directive('confirmOnExit', function() {
return {
link: function($scope, elem, attrs) {
window.onbeforeunload = function(){
if ($scope.myForm.$dirty) {
return "The form is dirty, do you want to stay on the page?";
}
}
$scope.$on('$locationChangeStart', function(event, next, current) {
if ($scope.myForm.$dirty) {
if(!confirm("The form is dirty, do you want to stay on the page?")) {
event.preventDefault();
}
}
});
}
};
});
</script>
</head>
<body>
<form name="myForm" ng-controller="Ctrl" confirm-on-exit>
Text: <input name="input" ng-model="mySample.text">
<p>mySample = {{mySample.text}}</p>
</form>
</body>
</html>

Custom Animations with ng-animate $animate

I need some help on better understanding custom animations in AngularJS 1.3.
The objective
Click on an element
Animate separate element on the DOM
I have created the following plunkr with no success
http://plnkr.co/edit/zg3BglCY9VfgPJc2pfNg?p=preview
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular-animate.min.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="app">
<ul>
<li animate-trigger> Click on me to animate </li>
</ul>
<div class="divtoanimate animated">
Animate Action Baby
</div>
</body>
</html>
JS
'use strict';
var app = angular.module('app', ['ngAnimate'])
app.directive('animateTrigger', ['$animate', function ($animate) {
return function (scope, elem, attrs) {
elem.on('click', function (elem) {
var el = angular.element(document.getElementsByClassName("divtoanimate"));
console.log("clicked");
var promise = $animate.addClass(el, "bounceIn");
promise.then(function () {
$animate.removeClass(el, "bounceIn");
});
});
}
}]);
Use $scope.apply for the initial animation and inside your promise to both add and remove the classes. Check out the code below and the attached plunkr, which demonstrates the animation repeating each time the animage-trigger directive is clicked.
working-plunkr
var app = angular.module('app', ['ngAnimate'])
app.directive('animateTrigger', ['$animate', function ($animate) {
return function (scope, elem, attrs) {
elem.on('click', function (elem) {
scope.$apply(function() {
var el = angular.element(document.getElementsByClassName("divtoanimate"));
var promise = $animate.addClass(el, "bounceIn");
promise.then(function () {
scope.$apply(function() {
$animate.removeClass(el, "bounceIn");
});
});
});
});
}
}]);
Since you're using the jquery event handler, you need to call scope.$apply(function() {...}) to perform your $animate calls.
Here's plunkr updated with scope.$apply:
http://plnkr.co/edit/qOhLWze8pGkO9dGRp1Sg?p=preview
More on scope.$apply:
https://github.com/angular/angular.js/wiki/When-to-use-$scope.$apply()

angularjs adding my own function to the existing ng-click

I have ng-click="foo()" which alert "foo"
In my own directive, if ng-click is found, I want to add another function to alert "bar"
I tried this
DEMO: http://plnkr.co/edit/1zYl0mSxeLoMU3yjoGBV?p=preview
and it did not work
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<script src="http://code.angularjs.org/1.2.7/angular.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('MyCtrl', function($scope) {
$scope.foo = function() { alert('foo'); }
$scope.bar = function() { alert('bar'); }
});
app.directive("myAttr", function() {
return {
link: function(scope, el, attrs) {
el.attr('ng-click', attrs.ngClick+';bar()');
}
}
})
</script>
</head>
<body ng-controller="MyCtrl">
<a my-attr ng-click="foo()" href="">click here!</a>
</body>
</html>
I was also not able to another ng-* directive to this to make it work, i.e. el.attr('ng-focus', 'bar()');. It seems that I cannot change or add ng-* directive once it is rendered.
How can I achieve this, and what was I doing wrong?
app.directive("myAttr", function() {
return {
priority: 1,
compile: function(el, attrs) {
attrs.ngClick += ';bar()';
}
}
})
First of all you want a compile function, for when link is called, the ng-click directive is already set up.
The second important thing is to change the priority. You want to ensure that your directive is called before ng-click. ng-click has the default priority 0, so 1 is enough.
The last and important thing, which is not obvious, is that you don't want to change the element, but attrs itself. It is created only once per element. So when ng-click accesses it it would still contain the same value, if you changed the attribute on the element directly.
I think you can do what you want with ngTransclude.
app.directive("myAttr", function() {
return {
transclude:true,
template: '<span ng-click="bar()" ng-transclude></span>',
link: function(scope, el, attrs) {
}
}
});
Does that work?
EDIT
Okay what about this one?
app.directive("myAttr", function($compile) {
return {
link: function(scope, el, attrs) {
el.attr('ng-click', 'bar()');
el.removeAttr('my-attr');
$compile(el)(scope);
}
}
});
While this could be done with compile as outlined above, that approach doesn't guarantee the order in which the ng-click items would be added to a DOM node (as you have already discovered), and is inherently slow (as has been pointed out by Words Like Jared.
Personally, I would just do something like this:
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<link rel="stylesheet" href="style.css">
<script src="http://code.angularjs.org/1.2.7/angular.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('MyCtrl', function($scope) {
$scope.foo = function() { alert('foo'); }
$scope.bar = function() { alert('bar'); }
});
app.directive('myAttr', function() {
return {
scope: true,
link: function(scope, el, attrs) {
if(attrs.hasOwnProperty('ngClick')){
scope.foo = function(){
scope.$parent.foo();
scope.$parent.bar();
}
}
}
};
});
</script>
</head>
<body ng-controller="MyCtrl">
<a my-attr ng-click="foo()" href="">click here!</a>
</body>
</html>
Whats going on:
scope: true: By default directives do not create new scopes, simply sharing their parent scope. By setting scope: true, every instance of this directive will create a child scope, that will prototypically inherit from the parent scope.
Then you can simply override the method desired (foo()) and voila
Live demo:
http://plnkr.co/edit/8A8y96wAhqGEowFaRQUH?p=preview
I freely admit I may entirely misunderstand what you are trying to do. However, given the example you provided, I think you might be better served by separating concerns a little more.
It seems from your example that you are trying to trigger foo and bar together whenever your directive is present. If both foo and bar are concerns of the controller, then why not wrap them both up in another function and assign that function to the ng-click of your element. If foo is a concern of the controller, but bar is a concern of the directive, why not trigger the bar functionality directly from the directive code?
If the functionality wrapped up in 'foo' and 'bar' is suppose to be defined by the controller creator...
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<link rel="stylesheet" href="style.css">
<script src="http://code.angularjs.org/1.2.7/angular.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('MyCtrl', function($scope) {
$scope.foo = function() { alert('foo'); }
$scope.bar = function() { alert('bar'); }
$scope.pak = function() {
$scope.foo();
$scope.bar();
}
});
</script>
</head>
<body ng-controller="MyCtrl">
<a ng-click="pak()" href="">click here!</a>
</body>
</html>
Or, if the functionality wrapped up in 'foo' is suppose to be defined by the controller creator, but the functionality wrapped up in 'bar' is suppose to be defined by the directive creator...
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<link rel="stylesheet" href="style.css">
<script src="http://code.angularjs.org/1.2.7/angular.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('MyCtrl', function($scope) {
$scope.foo = function() { alert('foo'); }
});
app.directive('myAttr', function(){
return {
link: function(scope, element, attrs){
element.click(function(){
alert('bar');
});
}
}
});
</script>
</head>
<body ng-controller="MyCtrl">
<a ng-click="foo()" href="">click here!</a>
</body>
</html>

AngularJS and contentEditable two way binding doesn't work as expected

Why in the following example the initial rendered value is {{ person.name }} rather than David? How would you fix this?
Live example here
HTML:
<body ng-controller="MyCtrl">
<div contenteditable="true" ng-model="person.name">{{ person.name }}</div>
<pre ng-bind="person.name"></pre>
</body>
JS:
app.controller('MyCtrl', function($scope) {
$scope.person = {name: 'David'};
});
app.directive('contenteditable', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
// view -> model
element.bind('blur', function() {
scope.$apply(function() {
ctrl.$setViewValue(element.html());
});
});
// model -> view
ctrl.$render = function() {
element.html(ctrl.$viewValue);
};
// load init value from DOM
ctrl.$setViewValue(element.html());
}
};
});
The problem is that you are updating the view value when the interpolation is not finished yet.
So removing
// load init value from DOM
ctrl.$setViewValue(element.html());
or replacing it with
ctrl.$render();
will resolve the issue.
Short answer
You're initializing the model from the DOM using this line:
ctrl.$setViewValue(element.html());
You obviously don't need to initialize it from the DOM, since you're setting the value in the controller. Just remove this initialization line.
Long answer (and probably to the different question)
This is actually a known issue: https://github.com/angular/angular.js/issues/528
See an official docs example here
Html:
<!doctype html>
<html ng-app="customControl">
<head>
<script src="http://code.angularjs.org/1.2.0-rc.2/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<form name="myForm">
<div contenteditable
name="myWidget" ng-model="userContent"
strip-br="true"
required>Change me!</div>
<span ng-show="myForm.myWidget.$error.required">Required!</span>
<hr>
<textarea ng-model="userContent"></textarea>
</form>
</body>
</html>
JavaScript:
angular.module('customControl', []).
directive('contenteditable', function() {
return {
restrict: 'A', // only activate on element attribute
require: '?ngModel', // get a hold of NgModelController
link: function(scope, element, attrs, ngModel) {
if(!ngModel) return; // do nothing if no ng-model
// Specify how UI should be updated
ngModel.$render = function() {
element.html(ngModel.$viewValue || '');
};
// Listen for change events to enable binding
element.on('blur keyup change', function() {
scope.$apply(read);
});
read(); // initialize
// Write data to the model
function read() {
var html = element.html();
// When we clear the content editable the browser leaves a <br> behind
// If strip-br attribute is provided then we strip this out
if( attrs.stripBr && html == '<br>' ) {
html = '';
}
ngModel.$setViewValue(html);
}
}
};
});
Plunkr
Here is my understanding of Custom directives.
The code below is basic overview of two way binding.
you can see it working here as well.
http://plnkr.co/edit/8dhZw5W1JyPFUiY7sXjo
<!doctype html>
<html ng-app="customCtrl">
<head>
<script src="http://code.angularjs.org/1.2.0-rc.2/angular.min.js"></script>
<script>
angular.module("customCtrl", []) //[] for setter
.directive("contenteditable", function () {
return {
restrict: "A", //A for Attribute, E for Element, C for Class & M for comment
require: "ngModel", //requiring ngModel to bind 2 ways.
link: linkFunc
}
//----------------------------------------------------------------------//
function linkFunc(scope, element, attributes,ngModelController) {
// From Html to View Model
// Attaching an event handler to trigger the View Model Update.
// Using scope.$apply to update View Model with a function as an
// argument that takes Value from the Html Page and update it on View Model
element.on("keyup blur change", function () {
scope.$apply(updateViewModel)
})
function updateViewModel() {
var htmlValue = element.text()
ngModelController.$setViewValue(htmlValue)
}
// from View Model to Html
// render method of Model Controller takes a function defining how
// to update the Html. Function gets the current value in the View Model
// with $viewValue property of Model Controller and I used element text method
// to update the Html just as we do in normal jQuery.
ngModelController.$render = updateHtml
function updateHtml() {
var viewModelValue = ngModelController.$viewValue
// if viewModelValue is change internally, and if it is
// undefined, it won't update the html. That's why "" is used.
viewModelValue = viewModelValue ? viewModelValue : ""
element.text(viewModelValue)
}
// General Notes:- ngModelController is a connection between backend View Model and the
// front end Html. So we can use $viewValue and $setViewValue property to view backend
// value and set backend value. For taking and setting Frontend Html Value, Element would suffice.
}
})
</script>
</head>
<body>
<form name="myForm">
<label>Enter some text!!</label>
<div contenteditable
name="myWidget" ng-model="userContent"
style="border: 1px solid lightgrey"></div>
<hr>
<textarea placeholder="Enter some text!!" ng-model="userContent"></textarea>
</form>
</body>
</html>
Hope, it helps someone out there.!!
Check this angularjs directive
https://github.com/clofus/angular-inputnlabel
http://clofus.com/viewarticles/109
You may run into issues using #Vanaun's code if a scope.$apply is already in progress. In this case I use $timeout instead which resolves the issue:
Html:
<!doctype html>
<html ng-app="customControl">
<head>
<script src="http://code.angularjs.org/1.2.0-rc.2/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<form name="myForm">
<div contenteditable
name="myWidget" ng-model="userContent"
strip-br="true"
required>Change me!</div>
<span ng-show="myForm.myWidget.$error.required">Required!</span>
<hr>
<textarea ng-model="userContent"></textarea>
</form>
</body>
</html>
JavaScript:
angular.module('customControl', []).
directive('contenteditable', function($timeout) {
return {
restrict: 'A', // only activate on element attribute
require: '?ngModel', // get a hold of NgModelController
link: function(scope, element, attrs, ngModel) {
if(!ngModel) return; // do nothing if no ng-model
// Specify how UI should be updated
ngModel.$render = function() {
element.html(ngModel.$viewValue || '');
};
// Listen for change events to enable binding
element.on('blur keyup change', function() {
$timeout(read);
});
read(); // initialize
// Write data to the model
function read() {
var html = element.html();
// When we clear the content editable the browser leaves a <br> behind
// If strip-br attribute is provided then we strip this out
if( attrs.stripBr && html == '<br>' ) {
html = '';
}
ngModel.$setViewValue(html);
}
}
};
});
Working Example: Plunkr

Resources