controller and link functions in directive do not have same scope - javascript

I have an issue with my angular.js directive.
It should be a kind of autocomplete, in directive's controller property I'm loading an array of values and inside link function compiling template to show the results.
But when I update scope inside link it doesn't reflect on controller and template, please take look at the example here - http://plnkr.co/edit/Lz3QGwklghPo3as2QTqU
Should I apply scope changes or smth similar?

Your code has two problems
Attach click event to document instead of body
Use $apply() inside bind
Below code will resolve your problem
$document.bind('click', function (e) {
scope.results = [];
scope.$apply();
});

I update your $body.bind('click',...) method to
$body.bind('change', function (e) {
scope.results = [];
});
and it seemed to work (I mean that after 0.5 sec I typed a letter, the list of name is re-displayed).

Related

function inside Controller inAngularjs

I have two functions drag_drop and drag_start , inside these functions I nedd some data from a controller scope.
My code is somthing like that
function drag_start(event) {
event.dataTransfer.dropEffect = "move";
event.dataTransfer.setData("text", event.target.getAttribute('id'));
}
function drag_drop(event) {
// here I want to use $scope from a controller
}
controller code :
institutionController.controller('institutionController',function
$http.get('/myResponses').then(function(myres) {
$scope.myRps=myres.data;
// I want to use $scope.myRps when I drag and drop an element inside a div
HTML code :
ul(ng-hide="siwtchCI",class="list-inline")
li(ng-repeat="ownInst in ownInsts",draggable='true',ondragstart='drag_start(event)')
a(class="btn btn-default btn-org" ,role="button") {{ownInst.org.name}}
#drop_zone(ondrop='drag_drop(event)', ondragover='return false')
the problem is when I put my two functions inside the controller my html elements are not draggable anymore
Try declaring the functions drag_start and drag_stop in scope.
Also on html you need to specify something as below
ondragstart="angular.element(document.getElementById('xyz')).scope().drag(event)"
This would surely call the drag method you have defined in the controller
where xyz represents id of the html element to be dragged.
I'm assuming that you used 'ng' directives on the HTML side, that's why regular functions don't work. Please be a habit that if you are going to create a function inside a controller, might as well put a scope into it.
$scope.drag_start = function(event){
}
$scope.drag_drop = function(event){
}
This should do the trick

Angularjs directive scope accessing in a factory

I'm running Angular 1.4.3.
I'm trying to create a 'factory' in angular that helps me create a common menu system in my app. The 'create' function of the 'gui' factory creates a ul and the li elements are clickable with ng-click.
This ul is attached to the document body.
The ng-click should execute the 'callMe' function in my factory, but I'm not sure what scope to use....
Code:
var App = angular.module('App', ['ngRoute']);
App.factory('gui', function() {
var menu = function {
"create" : function(){
var menu_container = $('<div id="menu"></div>');
var menu_ul = $('<ul></ul>');
menu_ul.append('<li class="menu-item-purple" ng-click="gui.menu.callMe()"><a>About <span style="float: right;">></span></a></li>');
menu_container.append( menu_ul );
menu_container.prependTo(document.body);
},
"callMe" : function(){
console.log("I HAVE BEEN HIT");
}
}
return {
"menu" : menu
};
})
.controller('ExampleController', function($scope, gui){
$scope.gui = gui;
gui.menu.create();
})
So in the above code, when I click the li menu button - I do not get any response.
I have tried the following in the li element:
ng-click="this.callMe()"
I thought the original should work because if I hard code the html into the view with that ng-click directive, it works. I assume it could be something to do with load order as the gui.menu object should be present in the view as it's passed in the controller's scope?
Since your factory actually involves with some DOM operation, I would suggest you defining a directive.
You can define your ul, li elements in the directive template and also handles the ng-click events. A directive is the best choice to share some DOM structure as well as the logic across different places in your application.
To access the method contained within your factory in the template, you need to change the 'this' variable to gui, that's because your methods are contained in a variable named gui inside your scope.
Although, it may be easier to create a function inside your scope for the thing you want to do.
$scope.createMenu = function () { gui.menu.create(); };
And then, you can call the function directly in the template with
ng-click="createMenu()"
But, as #Joy said it would be better with a directive since your DOM is modified

Angular.js $scope.$on initial value

I'm using angular.js $scope.$on for listening for events from my service(event got triggered on service data change).
I use the following code snippet:
$scope.people = contacts.people;
$scope.$on('contacts-changed', () => {
$scope.people = contacts.people;
});
Is there any way to extend angular to avoid the first line and trigger $scope.$on('contacts-changed') or similar by default on the time of event bind?
I was looking in angular.js decorators but not sure how can I decorate $scope to add $scope.$bind or similar method
Ideas?
Sometimes the solution is so simple, but you need to think outside of the box. Move the event callback block into a function and just run it when the controller loads, after the event binding.
See this:
function handlePeople() {
$scope.people = contacts.people;
}
$scope.$on('contacts-changed', handlePeople);
handlePeople();
You can also keep the original code and do a trigger manually.
Temporally went with the following solution:
$rootScope.$bind = (bindName, bindHandler) => {
$rootScope.$on(bindName, bindHandler);
bindHandler();
};
But I'd want to replace it so I could use $scope instead of $rootScope and without defining this in each $scope...
I could try to use $scope._ proto _ but it seems _ proto _ is deprecated

AngularJS Scope Apply does not working on click

I'm trying to se scope variable value after on click event using the following code:
$('#teamDetailTabs a').click(function(data) {
$scope.$apply(function(){
console.log($(this).attr('data-target'));
$scope.activeTab = $(this).attr('data-target');
console.log($scope.activeTab);
});
});
Problem is that value $scope.activeTab is undefined even if I used $scope.apply.
How can I solve it please?
Thanks for any advice.
The context (this) inside apply call is not what you expect, it's no longer a DOMElement. So you can fix it like this:
var self = this;
$scope.$apply(function() {
console.log($(self).attr('data-target'));
$scope.activeTab = $(self).attr('data-target');
console.log($scope.activeTab);
});
However, I strongly encourage you to go with ngClick and never use jQuery style approach in Angular app. Take a look at this very detailed thread.

Is there a way to watch attribute changes triggered from outside the AngularJS world?

Iā€™m trying to understand interactions between the Angular world and the non-Angular world.
Given a directive that one declares like this:
<dir1 id="d1" attr1="100"/>
If code outside angular changes the directive this way:
$("#d1").attr("attr1", 1000);
How can the directive know that one of its attribute has changed?
It would be best to make this change inside the directive instead. If, for whatever reason, that's not possible, then there are a couple of options.
Outside the app, get a reference to any DOM element within the app. Using that reference, you can then get a reference to its scope. You could use your element with id d1. For example:
var domElement = document.getElementById('d1');
var scope = angular.element(domElement).scope();
Here are a couple of options:
Option 1
Modify the model instead of making a direct change to the view. In the link function, store the initial attribute value in a scope variable like:
scope.myvalue = attrs.attr1;
Then you can change the value outside the app (using the above reference to scope) like:
scope.$apply(function(){
scope.myvalue = 1000;
console.log('attribute changed');
});
Here is a fiddle
Option 2
If the view is manipulated directly with jQuery, I don't know of any use of $observe, $watch, or an isolate scope binding to the attribute that will work, because they all bind to the attribute expression itself, just once, when the link function is first run. Changing the value will cause those bindings to fail. So you'd have to $watch the attribute on the DOM element itself (rather than through attrs):
scope.$watch(function(){
return $(el).attr('attr1'); // Set a watch on the actual DOM value
}, function(newVal){
scope.message = newVal;
});
Then you can change the value outside the app (using the above reference to scope) like:
scope.$apply(function(){
$("#d1").attr("attr1",1000);
});
Here is a fiddle
Use a Web Components library like x-tags by Mozilla or Polymer by Google. This option works without maunally calling $scope.$apply every time the attribute changes.
I use x-tags because of their wider browser support. While defining a new custom tag (directive) you can set the option lifecycle.attributeChanged to a callback function, which will fire every time an argument is changed.
The official docs aren't very helpful. But by trial and error and diving into the code I managed to find out how it works.
The callback function's context (the this object) is the element itself. The one whose attribute has changed. The callback can take three arguments:
name ā€“ the name of the attribute,
oldValue and
newValue ā€“ these speak for themselves.
So now, down to business:
The code
This will watch the attribute for changes:
xtag.register('dir1', {
lifecycle: {
attributeChanged: function (attribute, changedFrom, changedTo) {
// Find the element's scope
var scope = angular.element(this).scope();
// Update the scope if our attribute has changed
scope.$apply(function () {
if (attribute == 'attr1') scope.style = changedTo;
});
}
}
});
The attributeChanged callback only fires when the arguments' values actually change. To get their initial values you need to scan the lot manually. The easiest way seems to be while defining the directive:
myApp.directive('dir1', function () {
return {
... ,
link: function (scope, element, attributes) {
scope.attr1 = element[0].getAttribute('attr1');
}
};
});

Categories

Resources