AngularJS custom directive scope error - angularjs-directive

I am having hard time to figure out why scope inside directive does not work. What I am doing wrong. Program should be able to remove an item by clicking button, but scope does not work, if I comment out scope in directive data is there otherwise nothing is happening. User is supposed to search for an item in menu and based on criteria items will be shown, for example enter chicken and number of items should show, beside each item should show up button and by pressing that button that item should be removed. This does not work.
if anyone can have look and give me an answer what is wrong I'd kindly appreciate. By the way I am new to AngularJS and trying to learn.
function FoundItemsDirective(){
var ddo = {
scope: {
//items: '<',
//myTitle: "#",
onRemove: '&'
//visible: '='
},
templateUrl: 'foundItems.html'
// controller: NarrowItDownDirectiveController,
// controllerAs: 'narrowDown',
// bindToController: true
};
return ddo;
}
Here is link to plunk http://plnkr.co/edit/s9Fh4RkXIhrhLoVv8ZT2?p=preview

Thanks to everyone who tried to help, I figure out by myself this one.
function FoundItemsDirective(){
var ddo = {
templateUrl: 'foundItems.html',
scope: {
found: '<',
onRemove: '&'
},
controller: NarrowItDownDirectiveController,
controllerAs: 'narrowDown',
bindToController: true
};
return ddo;
}
Here is plunk http://plnkr.co/edit/s9Fh4RkXIhrhLoVv8ZT2?p=preview

Related

Angular Binding In Template Not Resolving

I have the following directive...
app.directive('layoutPreview', function () {
return {
restrict : 'E',
transclude : false,
scope: {
layout: '#',
previewid : '='
},
controller : function($scope){
console.log($scope.layout);
console.log($scope.previewid);
layoutPreview($scope.layout, "canvas-layout-" + $scope.previewid);
},
template:
'<canvas height="200" width="350" id="canvas-layout-{{previewid}}">' +
'</canvas>'
}
})
Which, once placed renders a canvas with a preview. However, {{previewid}} inside the template never resolves and I'm unsure why. Both of the log outputs show the correct values too. Even an output in my layoutPreview() function shows the correct id of the element it should be searching for.
Inspecting the page shows that the angular binding hasn't resolved.
Any ideas?
I think it's because the template gets rendered before the controller is created, and therefore the bindings do not work; ie $scope does not exist at the time of rendering.
Try:
template: function($attrs) {
return '<canvas height="200" width="350" id="canvas-layout-'
+ $attrs.previewid
+ '"></canvas>';
}
Also, if previewid is just a string, use:
scope: {
previewid : '#'
},
= is for two-way binding and objects.
This will insert the previewid before rendering the template.
Sidenote: You don't have to include transclude: false if it's false, and I would recommend using component instead of directive if you use Angular 1.5+.

angularjs routing without links

I have two problems.
First my test.html doesn't "see" the products array from his controller.
I don't know why, I have connected the controller with the html in the routing.config.js.
var myapp = angular.module('myapp', ["ui.router", "ngAnimate"]);
myapp.config(function($stateProvider, $urlRouterProvider){
$stateProvider
.state("foo", {
url: "/foo",
templateUrl:'start.html'
})
.state("test", {
url: "/test",
templateUrl: 'test.html',
controller:'product-controller',
parent:'foo'
})
$urlRouterProvider.otherwise("/foo");
})
myapp.controller('navCtrl', function($scope, $state){
$scope.navigateTo = function(target){
$state.go(target);
}
});
the second thing I want is that the test.html will be activated without the click on the link.
If the mainpage is loaded, the test.html should be loaded too.
At the end I want to delete the <ul> but the result should be the same.
(the foo and the dropdown).
I have made a PLUNK where you can see the problem.
The only solution I had, is to integrate the test.controller.js in the routing.config.js and the test.html in the start.html.
But i hope to avoid this because it makes the code more confusing.
I also looked at the ui-router docs but it doesn't work for me.
.state("foo.test", {
url: "/test",
templateUrl: 'test.html',
controller:'product-controller',
parent:'foo'
})
I hope someone has an idea... thank you! =)
I would take #charlietfl's advice and read up on nested views. Other than that, you had a few errors in your plunkr:
Forgot to load test-controller.js in index.html
Forgot to inject the product-selection module to myapp (so it can resolve the respective controller)
Forgot to add the ngModel to the select element in test.html for data binding to work as expected
Here's your plunkr updated with the select control populated properly.

Filter table rows depending on value in searhbox

My application consists of the following:
One indexview which my navbar is placed in together with a searchbox, this has a navcontroller.
Two html pages which is placed in a ngview element with 2 different controllers.
customercontroller and contactscontroller. Theese 2 pages have tables which gets data from a service.
How do i pass the value from the navbarcontroller to the other 2 controllers to filter my table?
The Ng-View is not nested inside the navController scope.
This is my route.
app.config(function ($routeProvider) {
$routeProvider
.when("/Customers", {
templateUrl: "app/customers-list.html",
controller: "customerController"
})
.when("/Contacts", {
templateUrl: "app/contacts-list.html",
controller: "contactController"
})
.when("/Prospects", {
templateUrl: "app/saleprospect-list.html",
controller: "prospectController"
})
.when("/Prospects/add", {
templateUrl: "app/salesprospect-add.html",
controller: "addController"
})
.when("/Prospects/:index", {
templateUrl: "app/salesprospect-add.html",
controller: "editController"
})
.otherwise({
redirectTo: "/Customers"
})
});
You can use angularjs custom events to pass information from one place to another.
As shown in this link:
How do I create a custom event in an AngularJs service
Working with $scope.$emit and $scope.$on
You can pass data with these events which can be used by functions listening for them.
You will find many solutions on how to communicate between controllers. As #ArslanW has pointed out, you can use events. Some answers will ask you to use services.
I believe that, as a best practice, you need to store the search text in the URL as query parameters. This has the benefit that if the user wishes to refresh the screen, the search results will not be reset because you can read the search text from the URL's query parameters.
Check Github.com's issue tracker for example. There you can search for issues and even if you refresh, you search result or text is not lost.
To carry this out, you need two watchers.
One watcher in your NavController to watch the search text and to copy it to the URL.
So, if your search text has the following markup:
<input type="text" data-ng-model="searchText">
... you need to have the following watcher on the input model (in your NavController)
$scope.$watch('searchText', function () {
//This causes the URL to look like
//http://localhost/#!/path?searchText=my%20search%text
//where the search text is "my search text"
$location.search('searchText', $scope.searchText);
});
Now that the URL will get updated, you can use the URL itself to determine which search text to use.
Thus, in the controller for your two HTML views (CustomerController and ContactsController that will display tables, you can then watch the search text for changes:
$scope.$watch(function () {
return $location.search().searchText;
}, function (value) {
//The search text may be undefined - possible when the page
//loads and the user has not entered anything in the search box
if (value) {
//"value" is the search text entered by the user.
//You can then use this and proceed accordingly.
}
});
You have thus hit two bushes with one stone. Your search text is stored in the URL which causes the application to not lose the search text on refresh. You have also communicated with each controller about the search text entered.
Note that since you are adding query parameters to the search text, your page will refresh. To prevent this, add the reloadOnSearch property to your route and set it to false:
$routeProvider
.when("/Customers", {
templateUrl: "app/customers-list.html",
controller: "customerController",
reloadOnSearch: false
});

AngularJS directive with method called from outside

I created a directive with a method that should be called from other elements that are not part of the directive. However it looks like this method is not exposed.
Some example jade code to clarify:
//- a controller for the view itself
div(ng-controller="someController")
//- this is part of the view itself, not within the directive
div(ng-repeat="element in elements")
div(ng-click="methodFromDirective(element)") click element {{$index}} to trigger directive
//- this is the directive
div(some-directive)
The someController isn't too important here I think. It has methods but NOT the methodFromDirective(element) one. The methodFromDirective(element) is a method that exists only in the directive.
If I make a directive and put some logging on creation I can clearly see it's created. However the methodFromDirective(element) method isn't exposed so the calls aren't properly triggered.
The methodFromDirective(element) itself will only work on elements from within the directive's template.
some coffeescript to show the definition of the the directive (ignore indentation errors here):
'use strict'
define [], () ->
someDirective = () ->
restrict: 'A'
scope: {
show: '='
}
transclude: false
templateUrl: 'someTemplateHere.html'
controller = ($scope) ->
# exposing the method here
$scope.methodFromDirective(element)->
$scope.theMethod element
link = (scope, element, attr) ->
# this is logged
console.log "init someDirective"
# triggering this method form outside fails
scope.theMethod = (element)->
console.log "method triggered with element", JSON.stringify(element)
I found my issue.
From the angularJS documentation on directives I was looking into the transclude option since that states:
What does this transclude option do, exactly? transclude makes the contents of a directive with this option have access to the scope outside of the directive rather than inside.
I combined transclude=false with the controller function since that exposes the method, again from docs:
Savvy readers may be wondering what the difference is between link and controller. The basic difference is that controller can expose an API, and link functions can interact with controllers using require.
However what I missed completely was that I isolated scope within my directive. From docs:
What we want to be able to do is separate the scope inside a directive from the scope outside, and then map the outer scope to a directive's inner scope. We can do this by creating what we call an isolate scope. To do this, we can use a directive's scope option:
So even if you use transclude=false and the controller function you'll still fail to expose methods if you use isolated scope! Lesson learned!
While figuring out what went wrong I also made a fiddle for better understanding: http://jsfiddle.net/qyBEr/1/
html
<div ng-app="directiveScopeExample">
<div ng-controller="Ctrl1">
<p>see if we can trigger a method form the controller that exists in the directive.</p>
<ul>
<li>Method in Controller</li>
<li>Method in Directive</li>
</ul>
<simple-directive/>
</div>
</div>
javascript
angular.module('directiveScopeExample', [])
.controller('Ctrl1', function Ctrl1($scope) {
$scope.methodInController = function(){
alert('Method in controller triggered');
};
})
.directive('simpleDirective', function(){
return {
restrict: 'E',
transclude: false,
controller: function($scope){
$scope.methodInDirective = function(){
// call a method that is defined on scope but only within the directive, this is exposed beause defined within the link function on the $scope
$scope.showMessage('Method in directive triggered');
}
}
// this is the issue, creating a new scope prevents the controller to call the methods from the directive
//, scope: {
// title: '#'
//}
, link: function(scope, element, attrs, tabsCtrl) {
// view related code here
scope.showMessage = function(message){
alert(message);
}
},
//templateUrl: 'some-template-here.html'
};
})
Calling private methods inside directive's link function is very simple
dropOffScope = $('#drop_off_date').scope();
dropOffScope.setMinDate('11/10/2014');
where
$('#drop_off_date') - jQuery function
setMinDate() - private function inside directive
You can call directive function even from outer space.
By default the scope on directive is false meaning directive will use the parent's scope instead of creating a new one. And hence any function or model defined in the directive will be accessible in the parent scope. Check out this.
I think your problem can be solved as follows:
angular.module('directiveScopeExample', [])
.controller('Ctrl1', function Ctrl1($scope) {
$scope.methodInController = function(){
alert('Method in controller triggered');
};
})
.directive('simpleDirective', function(){
return {
restrict: 'E',
scope: false,
link: function(scope, element, attrs, tabsCtrl) {
// view related code here
scope.showMessage = function(message){
alert(message);
}
},
//templateUrl: 'some-template-here.html'
};
This approach might be an issue in case you want to create reusable directives and you are maintaining some state/models in your directive scope. But since you are just creating functions without side-effects, you should be fine.

Calling function inside angular directive and inject event in parameters

I need your help.
I have a directive with a function parameter ( scope: { myParam: '#'}). And I'm passing the parameters in HTML, like my-param="myFunc(param1, param2)"
This works perfectly. But, I need to inject the event object in to the parameters. Does some one know how can I do that?
I tried $provider.annotate and $provider.instantiate, but they did not work because it's taking the reference function in directive. ($a in my case), so it can't get the function arguments.
any idea?
When you're calling a function created by the & isolate scope syntax, you can pass parameters to it as a named map. If your directive is defined like this:
scope: { myParam: '&' },
link: function (scope, el) {
el.on('click', function (e) {
scope.myParam({$event: e});
});
}
and used like this:
<my-directive my-param="console.log($event)"></my-directive>
... you should see the desired behavior.
chrisrhoden's answer is great. I would suggest to expand upon it a bit with the following. Doing so will help prevent ng-click double-firing issues relating to mobile devices and/or AngularJS conflicts with jQuery.
myApp.directive('responsiveClick', function(){
return {
scope: { myCb: '&' },
link: function (scope, el,attr) {
el.bind('touchstart click', function (e) {
e.preventDefault();
e.stopPropagation();
scope.myCb({$event: e});
});
}
}
});
along with the markup as follows:
<a class="mdi-navigation-cancel" my-cb="removeBasketItem($event,item)" responsive-click></a>
have you tried passing it in the original function?
my-param="myFunc($event, param1, param2)"