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
Related
I am trying to follow style guide for angular and there wrote we should use this insted scope...
Styleguide
Could someone explain me when I am able to use this?
Here is my try..... What I am doing wrong?
I am trying to toggle form....
here is my html code:
REPLY
<a href="#" ng-click="formEdit(x)" ng-if="x.formEditShow" >CLOSE</a>
With classic $scope I would do like this inside my conroller :
$scope.formEdit = function(data){
data.formEditShow = !data.formEditShow;
}
But with this it should look something like this(but don't work):
var vm = this;
vm.formEdit = formEdit;
function formEdit(data){
data.formEditShow = !data.formEditShow;
}
Anyone can help me to understand this?
When you are using this(context) in controller instead of $scope, you must use controllerAs while defining html on page to access controller variables. Whenever you wanted to use variable bounded to this on view you could use alias of your controller. Below you can see vm is alias of controller.
ng-controller="myController as vm"
Then while accessing controller method an variable inside ng-controller div you need to use alias of your controller like ng-click="vm.formEdit(x)"
HTML
REPLY
<a href="#" ng-click="vm.formEdit(x)" ng-if="x.formEditShow" >CLOSE</a>
Assuming your controller is named FormController.
First step
The first step is to declare the route (or the ng-controller value if you are not using a router) as such:
FormController as form // name it semantically instead of a generic name
Due to the above configuration, angular will alias as form the instances of FormController.
HTML template
Then adapt your html template according to the alias you gave (form). I modified your html to keep only the essential part about the question. We are calling the functions form.reply and form.close.
REPLY
CLOSE
Controller declaration
According to what we wrote above, our controller should look like that:
myApp.controller('FormController', function () {
var vm = this;
vm.reply = function () {
// ...
}
vm.close = function () {
// ...
}
}
Notice the var vm = this; line? Theoretically we could get rid of this line, and store the functions reply and close in the this object. But depending of the context, this does not refer to the same object. In a callback function this would not refer to the controller but to the callback function. That's why we are caching the this that refers to the controller. We usually name this reference vm for viewmodel, as a controller controls a view.
My goal is to create an Angular module that displays popup dialog messages. This module contains a directive (HTML, CSS and JavaScript) containing the internal logic (and markup and styles). Plus there's a service (factory) which acts as an API that can be used by other services.
Now this service of course has an openDialog() function which should insert the dialog directive into the DOM and present it to the user.
All solutions to this problem I have found so far make use of the $compile function. But it needs scope as a parameter. In a service where there's no scope though. They only exist in controller or link functions.
The reason I chose this implementation is for separation of concerns (directive's link and controller for internal usage, factory for external usage because it can be dependency injected). I know I could pass the scope when calling the function like this:
popupDialogService.openDialog({ /* options */ }, $scope);
But I don't see the point. It doesn't feel right. What if I call that function from inside another service which doesn't use scope either?
Is there a way to easily put the directive into the DOM from inside the service function or is there a better way to solve this problem?
Another solution I'm thinking about is calling a function of the directive's controller from inside the directive's factory. Is that possible?
Code
popupDialog.directive.js
angular.module('popupDialog').directive('popupDialog', directive);
function directive() {
return { ... };
}
popupDialog.service.js
angular.module('popupDialog').factory('popupDialogService', factory);
function factory() {
return { openDialog, closeDialog }; // *ES2015
function openDialog(options) {
// this function should put the `popupDialog` directive into the DOM
}
function closeDialog() {
// and this one should remove it
}
}
some.random.service.js
angular.module('myApp').factory('someRandomService', factory);
factory.$inject = ['popupDialogService'];
function factory(popupDialogService) {
return { clickedButton };
function clickedButton() {
popupDialogService.openDialog({ /* options */ });
// Sample implementation.
// It shouldn't matter where this function is beeing called in the end.
}
}
I know I could pass the scope when calling the function ... And it doesn't feel right.
Well you anyway need scope for dialog HTML content, Angular needs to compile and render it in some scope, right? So you have to provide scope object for your template somehow.
I suggest you to take a look at popular modal implementations like they do it, for example Angular UI Bootstrap's $modal or this simple one I was creating for my needs. The common pattern is passing scope parameter with modal initialization or use new child scope of the $rootScope for dialog. This is the most flexible way that should work for your both cases.
After all, it's not necessarily has to be real scope instance. You can even make your service accept plain javascript object and use it to extend new $rootScope.$new() object with.
I am trying to append an item inside my dll by targetting its ng-class after a save function like this but I can't seem to figure out what's wrong, I am new to AngularJs, please kindly advice:
My DDL:
<productbatchselectorcreate-dropdown ng-class="batch-{{item.productId}}" name="productBatch" id="{{item.productId}}" ng-model="item.productBatchId" ng-click="$event.stopPropagation();" class="ui-select2"></productbatchselectorcreate-dropdown>
Directive:
$scope.save = function () {
$scope.batch.productId = productId;
productService.addBatch($scope.batch)
.success(function (data) {
$('.batch-' + productId).append(new Option(data.name, data.productBatchId, false, false));
$modalInstance.dismiss('cancel');
})
};
I think you're going about this the wrong direction. Using jQuery to modify your DOM directly usually is not a good approach with Angular. Your drop down list options should be bound to some value on the scope (I'm not sure what that is though for your directive). Instead of modifying the DOM with jQuery, you want to add the new object to the scope value that the drop down items are bound.
I made a quick example showing how this works in a simple select http://plnkr.co/edit/jdCzqy3LZ5UXfskGdUMr?p=preview
Since you're using a custom directive though, I'm guessing you're creating the options using an ng-repeat in your directive template. If so, the same applies, whatever you're repeating over, the scope object for that should be modified, not the DOM directly.
I need to scroll to a specific anchor tag on page reload. I tried using $anchorScroll but it evaluates $location.hash(), which is not what I needed.
I wrote a custom provider based on the source code of $anchorScrollProvider. In it, it adds a value to the rootScope's $watch list, and calls an $evalAsync on change.
Provider:
zlc.provider('scroll', function() {
this.$get = ['$window', '$rootScope', function($window, $rootScope) {
var document = $window.document;
var elm;
function scroll() {
elm = document.getElementById($rootScope.trendHistory.id);
if (elm) elm.scrollIntoView();
}
$rootScope.$watch(function scrollWatch() {return $rootScope.trendHistory.id;},
function scrollWatchAction() {
if ($rootScope.trendHistory.id) $rootScope.$eval(scroll);
});
return scroll;
}];
});
Now, when I try to call the scroll provider in my controller, I must force a digest with $scope.$apply() before the call to scroll():
Controller:
//inside function called on reload
$scope.apply();
scroll();
Why must I call $scope.$apply()? Why isn't the scroll function evaluating in the Angular context when called inside the current scope? Thank you for your help!
I'm not sure what your thinking is behind using $rootScope.$eval(scroll) - since the scroll() function is already executing in a context where it has direct access to the $rootScope.
If I understand correctly, you want to be able to scroll to a particular element as denoted by an id which is stored in $rootScope.trendHistory.id.
When that id is changed, you want to scroll to that element (if it exists on the page).
Assuming this is a correct interpretation of what you are trying to achieve, here is how I might go about implementing it:
app.service('scrollService', function($rootScope) {
$rootScope.trendHistory = {};
$rootScope.$watch('trendHistory.id', function(val) {
if (val) {
elm = document.getElementById($rootScope.trendHistory.id);
if (elm) elm.scrollIntoView();
}
});
this.scrollTo = function(linkId) {
$rootScope.trendHistory.id = linkId;
}
});
This is a service (like your provider, but using the simpler "service" approach) which will set up a $watch on the $rootScope, looking for changes to $rootScope.trendHistory.id. When a change is detected, it scrolls to the element indicated if it exists - that bit is taken directly from your code.
So to use this in a controller, you'd inject the scrollService and then call its scrollTo() method with the ID as an argument. Example:
app.controller('AppController', function($scope, scrollService) {
scrollService.scrollTo('some_id');
});
In your question, you mention this needing to occur on reload, so you'd just put the call into your reload handler. You could also just directly modify the value of $rootScope.trendHistory.id from anywhere in the app and it would also attempt to scroll.
Here is a demo illustrating the basic approach: http://plnkr.co/edit/cJpHoSemj2Z9muCQVKmj?p=preview
Hope that helps, and apologies if I misunderstood your requirements.
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).