Can I watch for changing attibutes in directive? - javascript

Can I watch for changing attributes in directive?
I have something like this:
<online-wathers></online-watchers>
It shows online connected users to the video broadcast and requires a broadcast-id. Obviously without this attribute it won't work.
This video broadcast starts not on document ready, but when a publisher actually wants to start it. And when he starts - the backend gives me the broadcast id.
For now I just append it to the DOM with broadcast-id="xx" when api returns me broadcast-id.
$(".online-watchers-container")
.html($("<online-watchers broadcast-id='"+broadcast.id+"' />"));
$(".chat-container")
.html($("<chat broadcast-id='"+broadcast.id+"' />"));
$(".request-container")
.html($("<live-requests broadcast-id='"+broadcast.id+"'></live-requests>"));
// compile directives
angular.bootstrap(angular.element("online-watchers, chat, live-requests"), ['MyApp']);
But is there any internal way to watch adding or changing an attributes? So on page load I will already have <online-watchers></online-watchers> in DOM and when I get response with broadcastId i just add attribute to element in DOM and directive reacts on it.
Please, if you treat it like shit-code, I'll much appreciate if you show some examples of better solution.
Thanks.

Yes, you can
function MyDirective() {
return {
link: function (scope, iElement, iAttrs) {
scope.$watch(iAttrs.scopeProp, function (val) {
console.log("prop: " + val);
});
iAttrs.$observe("interpolated", function (val) {
console.log("interpolate: " + val);
});
}
}
}
.
<my-directive scope-prop="someId" interpolated="{{someId + 1}}"
.
function MyCtrl($scope) {
$scope.someId = 12;
}
For more examples, check out: How to get evaluated attributes inside a custom directive

Related

AngularJS + Select2 (Multiple - Tags) - sometimes show tags others not

I am using AngularJS + select2 (not ui-select).
Then in my view I have:
<select
name="rubros"
id="rubros"
class="select2 form-control"
ng-model="vm.comercio.tags"
ng-options="rubro.nombre for rubro in vm.rubros track by rubro.id"
multiple>
</select>
As you can see , the select is bind to a variable called "comercio.tags", that is an array of objects.
Well, here is the funny thing: the tags are displayed sometimes, and sometimes they don't. Even though the binding is working.
And the behavior is random; I can press F5 in my browser and the error appear and goes randomly.
Please take a look at the images:
The tags are retrieved by a get request ($http).
I don't know what is going on here. Because the behavior is randomly reproduced.
Update:
Add code requested by helper member
//controller initialization before this
var scope = this;
var id = $routeParams.id; //the ID of the commerce/store I want to edit (preload) in the page
//variable where I save the retrievedcommerce/store
scope.comercio = {
tags:[]
};
/*
HTTP request to retrieve the commerce/store with "id"
The model retrieved has a tags attribute that is correctly filled (as you can see in the images,
in the input on top of the select2, I used to debug)
*/
$http.get("http://localhost:8000/api/comercio/" + id).then(function (response) {
scope.comercio = response.data.model;
},
function (response) {
scope.comercio = null;
});
//other controllers instructions and declarations
As people suggested , the reason of this issue is because select2 is a jQuery plugin and we have to attach it to the angular "refresh"/compile/digest/watch ... cycle .. in other words, we need to attach select2 to the angularJS app lifecycle.
How do we do it ? using a directive. The official documentation is really extensive but you can appreciate the solution with this little piece of code.
app.directive("appSelect2", function($timeout) {
return {
restrict: "A",
link: function (scope, element, attrs) {
jQuery(element).select2();
scope.$watch(attrs.ngModel, function () {
$timeout(function () {
element.trigger('change.select2');
}, 100);
});
}
};
});
With this directive , and adding the "app-select2" attribute to the select2 input declared in your html ... it works perfectly.
I appreciate the help provided very much, thanks a lot.

angular-ui-router#1.0.11 fire jarvis.widget.js warns "It looks like your using a class instead of an ID, dont do that!"

I use the SmartAdmin for angular, Recently I update Angular from 1.4.12 to angular v1.6.6, After that i discovery when i switch between state where is used JarvisWidget, the JavaScript library for the JarvisWidgets (more specifically, the lines 319-325 of the uncompressed jarvis.widget.js)
/**
* Force users to use an id(it’s needed for the local storage).
**/
if (!$(‘#’ + self.objId).length) {
alert(‘It looks like your using a class instead of an ID, dont do that!’);
}
started alerting every time the page is opened.
How can I avoid this alert (in a correct way) on pages with no JarvisWidgets without modifying this part of the JarvisWidget library? Why is this alert triggered in my case?
The temporary solution until adapting to to new version of angular-ui-router#1.0.11 is revert back to angular-ui-router#0.3.2
then replace code as below:
// link function in directive in jarvisWidget.js
link: function(scope, element, attributes) {
if(element.data('widget-color'))
element.addClass('jarviswidget-color-' + element.data('widget-color'));
element.find('.widget-body').prepend('<div class="jarviswidget-editbox"><input class="form-control" type="text"></div>');
element.addClass('jarviswidget jarviswidget-sortable');
$rootScope.$emit('jarvisWidgetAdded', {widget: element, id: attributes.id });
}
// Body of the jarvisWidgetAdded listener function in widgetGrid.js:
var widget = widgetPackage.widget;
var widgetId = widgetPackage.id;
if (widgetIds.indexOf(widgetId) == -1) {
widgetIds.push(widgetId);
$timeout(function () {
setupWidgets(element, widgetIds)
}, 100);
}
Please write an other solution!

run angular compile only when a certian section of a page has changed

So I am working on a application that has many many sections that are loaded via jquery ajax, some when it starts and some in response to other sections, and some are even nested. What I wan't to be able to do is say "Oh, the content in mainContainer changed?" Well, go compile the code in that section.
The reason is that I would like to be able to put ui-sref's into parts of the code we don't otherwise want to mess with yet. I came up with a solution but it isn't working the way I thought, I added a $watch to a custom directive but it fires a bazillion times. Here is the code.
Hopefully some one can explain how to get it to run once only when the content in X <div> is changed via one of the many ajax calls that happen, that way I don't need to immediately turn all of the old code into angular and can link to new code that is using angular.
app.directive('ngMainContainer', function ($compile, $timeout) {
console.log("main container directive");
return function (scope, elem, attrs) {
scope.$watch(function () {
return elem;
}, function (val) {
console.log("Something changed. " + val);
$timeout(function () {
$compile(val)(scope);
}, 0);
});
};
});
again, I thought putting a watch on the element passed into the directive was the answer however this runs even when I move the mouse and click on the page somewhere outside of the container that was loaded via jquery ajax.
Here is a solution that may work for you. I place directive ngMainContainer on our element we want to observe the html contents of.
In our controller I append <p>{{ content }}</p> which I first $compile, then update the scope var afterwards like $scope.html = angular.element(myDiv).html(). This is where the our directive $watch fires.
Here is the full working example. The <button> is appending html, but this should emulate the resolution of an ajax call request complete.
JSFiddle Link
app.directive('ngMainContainer', [function () {
return {
restrict: 'A',
link: function (scope, elem, attrs) {
scope.$watch('html', function(n, o) {
if(n !== o) {
console.log(elem.contents())
// html has changed, do something!
}
});
}
}
}]);
app.controller('ctrl', ['$scope', '$compile', function($scope, $compile) {
$scope.content = "scope content";
var myDiv = document.getElementById('main');
$scope.appendContent = function() { // Replicate Ajax Callback
var dynamic = $compile('<p ng-cloak>{{ content }}</p>')($scope); // Compile content
angular.element(myDiv).append(dynamic);
$scope.html = angular.element(myDiv).html()
}
}]);
Edit
Here is a way to do this while appending content to the <div> with no notion of angular on the code that is performing the appending
Updated JSFiddle
<button id="noAngular" onclick="noAngularAppend()">Append Content - No Angular</button>
function noAngularAppend() {
angular.element(document.getElementById('main')).scope().externallyAppend('<p ng-cloak>{{ content }}</p>');
}
Directive Function (inject $compile)
scope.externallyAppend = function(element) {
var node = $compile(element)(scope);
elem.append(node);
scope.$apply(scope.html = elem.contents());
}
Sal, I up voted your answer because it ultimately did help me figure this out but in the end it wasn't quite what I was going for.
This is what I ended up with. In the controller for the main application there is function with only two lines in it.
$scope.doReshing = function (element) {
$compile(angular.element(element))($scope);
$scope.$apply();
}
Then in which ever partial that has an ng directive in it right at the top of the document.ready() I put the following.
var removeableContentContainer = $('#removableContentContainer');
angular.element(removeableContentContainer).scope().doReshing(removeableContentContainer);
This does exactly what I want. It lets me take an existing application change virtually nothing about how it works and lets me add ui-srefs in the "old" code and not have to worry about how or when it loads, when it does load, the document.ready() fires and it will tell angular that it needs to compile. This was my goal, you can pull bits of code in at any point in time and have them essentially act as though they were parts of that page with it's controller.

How do you trigger a reload in angular-masonry?

I got Masonry to work in my AngularJS app using the angular-masonry directive but I want to be able to call a function or method in my controller that will trigger a reload of items in the container. I see in the source code (lines 101-104) there is a reload method but I'm not sure how to invoke that. Any ideas?
Thanks!
In case it's of use to someone in the future, Passy watches for an event called masonry.reload.
So, you can issue this event and Passy should call 'layout' on the masonry element, e.g. call
$rootScope.$broadcast('masonry.reload');
In my case, I had some third-party javascript decorating my bricks, so I needed to repaint after that was done. For reason (I was not able to figure out why), I needed to wrap the event broadcast in a timeout, I think the Passy scheduler was eating up the event and not repainting. E.g. I did:
$timeout(function () {
$rootScope.$broadcast('masonry.reload');
}, 5000);
This way you don't have to modify Passy directly.
I don't think you can trigger the reload() method directly, since it is part of the controller which is only visible within the directives.
You might either ...
A) trigger a reload directly of masonry via the element, jQuery-style
(see http://jsfiddle.net/riemersebastian/rUS9n/10/):
$scope.doReloadRelatively = function(e) {
var $parent = $(e.currentTarget).parent().parent();
$parent.masonry();
}
$scope.doReloadViaID = function() {
$('#container').masonry();
}
B) or extend the directives yourself, i.e. add necessary watches on masonry-brick and call the reload() method within (depends on what use-case you have).
.directive('masonryBrick', function masonryBrickDirective() {
return {
restrict: 'AC',
require: '^masonry',
scope: true,
link: {
pre: function preLink(scope, element, attrs, ctrl) {
var id = scope.$id, index;
ctrl.appendBrick(element, id);
element.on('$destroy', function () {
console.log("masonryBrick > destroy")
ctrl.removeBrick(id, element);
});
scope.$watch(function () {
return element.height();
},
function (newValue, oldValue) {
if (newValue != oldValue) {
console.log("ctrl.scheduleMasonryOnce('layout');");
ctrl.scheduleMasonryOnce('layout');
}
}
);
...
In the code block above, I've simply added a watch on the element with the class 'masonry-brick' in order to trigger a reload of masonry whenever the elements' height changes.
I've created a jsFiddle for testing passys' angular-masonry myself, feel free to check it out!
http://jsfiddle.net/riemersebastian/rUS9n/10/
EDIT:
Just found a similar post on stackoverflow which could be another solution to this problem:
AngularJS Masonry for Dynamically changing heights
The answer from #SebastianRiemer helped me.
But for future persons that need help try instead use the following
scope.$watch(function () {
return element.height();
},
function (newValue, oldValue) {
if (newValue != oldValue) {
ctrl.scheduleMasonryOnce('reloadItems');
ctrl.scheduleMasonryOnce('layout');
}
});

AngularJS directive for storing DOM elements and redirecting on form submit

I am getting into AngularJS, and I've been trying to understand directives because they are pretty much mandatory if you want to work with the DOM (when using AngularJS, correct me if I'm wrong). So here is the scenario, I am trying to create a simple login system (I am actually using the MEAN stack - MongoDB, ExpressJS, AngularJS, NodeJS). I'm not too worried about security (or otherwise less than perfect code) because I am just trying to learn how to use the frameworks. Here is the relevant code:
MemberModule.js:
var MemberModule = angular.module('MemberModule', ['ui.bootstrap']);
MemberModule.controller('MemberListController', function ($scope, $html)) {
$scope.members = [];
$scope.newMember = {
done : false
};
$scope.doneFilter = { done : true };
$scope.notDoneFilter = { done : false };
//various methods...
});
MemberModule.directive('usernameDir', ['$interval', function($interval) {
function link(scope, element, attrs) {
var newMember,
timeoutId;
function updateUsername() {
element.text(scope.newMember.username);
}
scope.$watch(attrs.myCurrentTime, function(value) {
format = value;
updateTime();
});
element.on('$destroy', function() {
$interval.cancel(timeoutId);
});
// start the UI update process; save the timeoutId for canceling
timeoutId = $interval(function() {
UpdateTime(); // update DOM
}, 1000);
}
return {
link: link
};
});
MemberModule.directive('passwordDir', function () {
// The above name 'myDirective' will be parsed out as 'my-directive'
// for in-markup uses.
return {
restrict: 'E',
transclude: true,
scope: {
'sub' : '&ngSubmit'
},
template: 'home'
}
});
As you can see above, I created the main angular.module and called it MemberModule - which gets referenced in my HTML (I am using jade templates - so by HTML I mean layout.jade). After that I created the controller with its various methods that I need. Finally, I created the directives which is what I need help with. I am trying to assign a DOM input element (in a form) to an object attribute, and then redirect (or render) a jade template (home.jade).
The relevant form HTML ('index.jade'):
extends layout
block content
div.container(ng-controller="MemberListController", ng-init="setMembers( #{JSON.stringify(members)} )")
h1 Welcome
h2 Sign Up
form(novalidate, ng-submit="addNewMember()")
input( type="text", username-dir info="userdir")
br
input( type="password", password-dir info="passdir")
br
input( type="password" )
br
button.btn.btn-primary(class="sub", type="submit") Submit
h2 Adding...
span(username dir)
span(password dir)
I am just pasting what I have so far so you can see where I am at in terms of progress. I am fully aware that my code is not functional as is - I am just looking for some help in pointing out what needs to go where to accomplish my goal. I realize that the two directives (while trying to attain the same goal) are not using the same style of directive code - this is just because of where I am at in terms of trying things. Again, my goal is (specifically for the username and password):
I am trying to assign a DOM input element (in a form) to an object attribute, and then redirect (or render) a jade template (home.jade).
Thanks.
Big ups to Julian Hollmann (check comments):
"You don't need both directives at all. Just use ng-model (docs.angularjs.org/api/ng/directive/ngModel) to bind your scope data to the input elements. Then use ng-submit to call a function in the controller."
Bingo - thanks!

Categories

Resources