Restarted initialization angular when updating DOM - angularjs

I have page:
<div>111</div><div id="123" ng-init='foo=555'>{{foo}}</div>
in browser:
111
555
Code js refresh id=123 and get new html. I get:
<div id="123" ng-init='foo="444new"'><span>..</span><b>NEW TEXT<b> {{foo}}</div>
in browser
111
...NEW TEXT {{foo}}
I want get in browser:
111
...NEW TEXT 444new
Is it possible to re-run the initialization angular in this situation?
DEMO: jsfiddle.net/UwLQR
Solution for me: http://jsbin.com/iSUBOqa/8/edit - this BAD PRACTICE!
UPD two months later: My God, what nonsense I wrote. :(

See my notes in the included code and the live demo here (click).
The two reasons that angular will not register data-binding or directives are that the element isn't compiled, or the change happens outside of Angular. Using the $compile service, the compile function in directives, and $scope.$apply are the solutions. See below for usage.
Sample markup:
<div my-directive></div>
<div my-directive2></div>
<button id="bad-button">Bad Button!</button>
Sample code:
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.foo = '123!';
$scope.bar = 'abc!';
//this is bad practice! just to demonstrate!
var badButton = document.getElementById('bad-button');
badButton.addEventListener('click', function() {
//in here, the context is outside of angular, so use $apply to tell Angular about changes!
$scope.$apply($scope.foo = "Foo is changed!");
});
});
app.directive('myDirective', function($compile) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
//when using a link function, you must use $compile on the element
var newElem = angular.element('<div>{{foo}}</div>');
element.append(newElem);
$compile(newElem)(scope);
//or you can use:
//$compile(element.contents())(scope);
}
};
});
app.directive('myDirective2', function($compile) {
return {
restrict: 'A',
compile: function(element, attrs) {
//compile functions don't have access to scope, but they automatically compile the element
var newElem = angular.element('<div>{{bar}}</div>');
element.append(newElem);
}
};
});
Update based on your comment
It makes me cringe to write this, but this is what you would need to make that code work.
var elem = document.getElementById('123');
elem.innerHTML = "<div ng-init=\"foo='qwe123'\">{{foo}}</div>";
$scope.$apply($compile(elem)($scope));
Just as I said, you need to compile the element AND, since that is in an event listener, you need to use $apply as well, so that Angular will know about the compile you're doing.
That said, if you're doing anything like this at all, you REALLY need to learn more about angular. Anything like that should be done via directives and NEVER with any direct DOM manipulation.

Try next:
$scope.$apply(function() {
// your js updates here..
});
or
$compile('your html here')(scope);
Look $compile example at bottom of page.

Related

Accessing ng-click when rendered via $sce.trustAsHtml

How do i get access to the ng-click function (updateRating) in the below?
https://jsfiddle.net/by2jax5v/171/
I'm using $sce.trustAsHtml to render $scope.content
$scope.bindHTML = $sce.trustAsHtml($scope.content);
Your above code does get compiled but it is sanitized by angular js considering an anchor tag as insecure, therefore ng-click does not work.
what you want to achieve can be achieved by using ng-html-compile by francis bouvier instead of ng-bind-html. It the thinnest library i have seen just 1kb. https://github.com/francisbouvier/ng_html_compile
also refer to https://stackoverflow.com/a/41790235
As it's not $compiled. so it doesn't tell Angular to search through that HTML and compile directives within it. You will have to use custom directive for this.
Updated fiddle.
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', function($scope) {
$scope.content = "This text is <em>html capable</em> meaning you can have <a ng-click='updateRating(1)' href=\"#\">all</a> sorts <b>of</b> html in here.";
$scope.updateRating = function(message) {
alert(message);
}
});
myApp.directive('compile', ['$compile', function ($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
// watch the 'compile' expression for changes
return scope.$eval(attrs.compile);
},
function(value) {
// when the 'compile' expression changes
// assign it into the current DOM
element.html(value);
// compile the new DOM and link it to the current
// scope.
// NOTE: we only compile .childNodes so that
// we don't get into infinite loop compiling ourselves
$compile(element.contents())(scope);
}
);
};
}]);

Unit testing controller triggering custom directive

Suppose you have an angular+jasmine app and your controller looks something like this:
app.controller('MyController', function($scope) {
$scope.myFunc = function() {
$scope.myArray = [];
// after some operations here
myArray.push(new_obj);
}
$scope.someOtherArray = [];
}
Additionally, I have a custom directive that is triggered by every element in myArray. Something like this:
app.directive('myDirective', function() {
return {
// directive goes here
// and some definitions
link: function(scope, element, attrs) {
scope.someOtherArray.push(other_obj)
}
}
}
and finally, in the HTML:
<div myDirective ng-repeat="obj in myArray"></div>
So, every time I add an element to myArray, another directive element is created and $scope.someOtherArray is modified. That works pretty well, but I found that myFunc() and the properties of the controller are difficult to test:
controller = $controller('MyController', { $scope: mockScope });
SpyOn(mockScope, 'myFunc').and.callThrough();
// expectGET and expectPOST here.
mockScope.myFunc();
However, after running that code, there were some objects (dependent on the custom directive) that were not being updated after calling mockScope.myFunc(); (such as someOtherArray). Apparently, I need to compile the custom directives manually:
var element = '<div myDirective ng-repeat="obj in myArray"></div>';
element = $compile(element)(mockScope);
Well, that seems a bit awkward. I was expecting that after running mockScope.myFunc() every side effect would take place automatically. Otherwise, I need to consider every directive involved in the process and compile each of them.
Is there a better way?

Angular script won't run when first loaded

On page load the console log prints but the toggleClass/click won't work I even use angular.element but it has the same result.I need to change state in order for the toggleClass to work.I dunno what's wrong in my code.
.run(['$rootScope', function ($rootScope) {
console.log('test');//this prints test and it's ok
//this part won't load at the first loading of page.
$('.toggle-mobile').click(function(){
$('.menu-mobile').toggle();
$(this).toggleClass('toggle-click');
});
//....
}])
even doing it this way doesn't work.
$rootScope.$on('$viewContentLoaded', function () {
angular.element('.toggle-mobile').on('click', function (event) {
angular.element(this).toggleClass('toggle-click');
angular.element('.menu-mobile').toggle();
event.preventDefault();
});
});
The Angular way to render items is different from "On DOM Ready" that is why we need to treat these as 2 separate things.
Angular could render items later on even after DOM is ready, this could happen for example if there is an AJAX call($http.get) and that is why a directive may be the recommended approach.
Try something like this:
<body ng-controller="MainCtrl">
<div toggle-Me="" class="toggle-mobile"> Sample <div class="menu-mobile">Sample 2</div>
</div>
<script>
var myApp = angular.module('myApp', []);
myApp.controller('MainCtrl', ['$scope', function ($scope) {}]);
myApp.directive("toggleMe", function() {
return {
restrict: "A", //A - means attribute
link: function(scope, element, attrs, ngModelCtrl) {
$(element).click(function(){
$('.menu-mobile').toggle();
$(this).toggleClass('toggle-click');
});
}
};
});
...
By declaring the directive myApp.directive("toggleMe",... as an attribute toggle-Me="" every time angular generates the input element it will execute the link function in the directive.
Disclaimer: Since the post lacks from a sample html I made up something to give an idea how to implement the solution but of course the suggested html is not part of the solution.

Angular dynamically created directive not executing

Plnkr sample: [http://plnkr.co/edit/jlMQ66eBlzaNSd9ZqJ4m?p=preview][1]
This might not be the proper "Angular" way to accomplish this, but unfortunately I'm working with some 3rd party libraries that I have limited ability to change. I'm trying to dynamically create a angular directive and add it to the page. The process works, at least in the sense where the directive element gets added to the DOM, HOWEVER it is not actually executed - it is just a dumb DOM at this point.
The relevant code is below:
<div ng-app="myModule">
<div dr-test="Static Test Works"></div>
<div id="holder"></div>
<a href="#" onclick="addDirective('Dynamic test works')">Add Directive</a>
</div>
var myModule = angular.module('myModule', []);
myModule.directive('drTest', function () {
console.log("Directive factory was executed");
return {
restrict: 'A',
replace: true,
link: function postLink(scope, element, attrs) {
console.log("Directive was linked");
$(element).html(attrs.drTest);
}
}
});
function addDirective(text){
console.log("Dynamically adding directive");
angular.injector(['ng']).invoke(['$compile', '$rootScope',function(compile, rootScope){
var scope = rootScope.$new();
var result = compile("<div dr-test='"+text+"'></div>")(scope);
scope.$digest();
angular.element(document.getElementById("holder")).append(result);
}]);
}
</script>
While appending the directive to DOM you need to invoke with your module as well in the injector, because the directive drTest is available only under your module, so while creating the injector apart from adding ng add your module as well. And you don't really need to do a scope apply since the element is already compile with the scope. You can also remove the redundant $(element).
angular.injector(['ng', 'myModule']).invoke(['$compile', '$rootScope',function(compile, rootScope){
var scope = rootScope.$new();
var result = compile("<div dr-test='"+text+"'></div>")(scope);
angular.element(document.getElementById("holder")).append(result);
}]);
Demo

Using Angular, how do I bind a click event to an element and on click, slide a sibling element down and up?

I'm working with Angular and I'm having trouble doing something that I normally use jQuery for.
I want to bind a click event to an element and on click, slide a sibling element down and up.
This is what the jQuery would look like:
$('element').click(function() {
$(this).siblings('element').slideToggle();
});
Using Angular I have added an ng-click attribute with a function in my markup:
<div ng-click="events.displaySibling()"></div>
And this is what my controller looks like:
app.controller('myController', ['$scope', function($scope) {
$scope.events = {};
$scope.events.displaySibling = function() {
console.log('clicked');
}
}]);
So far this is working as expected but I don't know how to accomplish the slide. Any help is very much appreciated.
Update
I have replaced what I had with a directive.
My markup now looks like this:
<div class="wrapper padding myevent"></div>
I have removed what I had in my controller and have created a new directive.
app.directive('myevent', function() {
return {
restrict: 'C',
link: function(scope, element, attrs) {
element.bind('click', function($event) {
element.parent().children('ul').slideToggle();
});
}
}
});
However, I still can't get the slide toggle to work. I don't believe slideToggle() is supported by Angular. Any suggestions?
I'm not sure exactly on the behaviour that you're talking about, but I would encourage you to think in a slightly different way. Less jQuery, more angular.
That is, have your html something like this:
<div ng-click="visible = !visible"></div>
<div ng-show="visible">I am the sibling!</div>
You can then use the build in ng-animate to make the sibling slide - yearofmoo has an excellent overview of how $animate works.
This example is simple enough that you can put the display logic in the html, but I would otherwise encourage you to as a rule to put it into the controller, like this:
<div ng-click="toggleSibling()"></div>
<div ng-show="visible"></div>
Controller:
app.controller('SiblingExample', function($scope){
$scope.visible = false;
$scope.toggleSibling = function(){
$scope.visible = !$scope.visible;
}
});
This kind of component is also a prime candidate for a directive, which would package it all up neatly.
app.directive('slideMySibling', [function(){
// Runs during compile
return {
// name: '',
// priority: 1,
// terminal: true,
// scope: {}, // {} = isolate, true = child, false/undefined = no change
// controller: function($scope, $element, $attrs, $transclude) {},
// require: 'ngModel', // Array = multiple requires, ? = optional, ^ = check parent elements
restrict: 'A', // E = Element, A = Attribute, C = Class, M = Comment
// template: '',
// templateUrl: '',
// replace: true,
// transclude: true,
// compile: function(tElement, tAttrs, function transclude(function(scope, cloneLinkingFn){ return function linking(scope, elm, attrs){}})),
link: function($scope, iElm, iAttrs, controller) {
iElm.bind("click", function(){
$(this).siblings('element').slideToggle();
})
}
};
}]);
Usage would be something like
<div slide-my-sibling><button>Some thing</button></div><div>Some sibling</div>
Note all the "code" above is just for the sake of example and hasn't been actually tested.
http://plnkr.co/edit/qd2CCXR3mjWavfZ1RHgA
Here's a sample Plnkr though as mentioned in the comments this isn't an ideal setup since it still has the javascript making assumptions about the structure of the view so ideally you would do this with a few directives where one requires the other or by using events (see $emit, $broadcast, $on).
You could also have the directive create the children "programmatically" via JavaScript and circumvent the issue of not knowing what context the directive is being used in. There are a lot of potential ways to solve these issues though none of the issues will stop it from functionally working they are worth noting for the sake of re-usability, stability, testing, etc.
As per this link : https://docs.angularjs.org/api/ng/function/angular.element
AngularJs element in your code snippet represents JQuery DOM object for related element. If you want to use JQuery functions, you should use JQuery library before angular loads. For more detail, please go through above link.
Best practice:
<div ng-if="view"></div>
$scope.view = true;
$scope.toggle = function(){
$scope.view = ($scope.view) ? false : true;
}

Resources