Angularjs: ng-click has no method '$apply' - angularjs

I append DOM element to body. Wrote the code in factory.
var templateElement = angular.element('<div class="popup modal-body"><div class="button-cancel" type="button" ng-click="closePopup()"></div>'+content+'</div>');
var scope = {};
scope.closePopup = function(){
var popup = angular.element(document.querySelector('.popup'));
popup.remove();
}
var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) {
body.append(clonedElement);
});
everything works except the ng-click. I got this error when I click the div:
Uncaught TypeError: Object #<Object> has no method '$apply'
What did I do wrong? Thanks

According to the docs, the function returned by $compile() takes a Scope object as its first argument. You supply a normal JS object (which does not have an $apply method of course).
If you want to create a new scope, you can inject the $rootScope (through Dependency Injection) and use its $new() method:
app.factory('myFactory', function($rootScope) {
var scope = $rootScope.$new();
...
});
It seems a little bizarre to create an new scope inside a factory though, so providing more details on what you are ultimately trying to achieve might help someone suggest a better approach.

Related

Modifying factory value in directive Angular

I have the following code : http://codepen.io/Andarius/pen/Ggryge .
When the user draws a 'crop_area', the crop button should not be disabled anymore.
Why is the value no_crop_area (from the Image factory) not updated when drawing ?
Is it a scope problem ?
Also, I'm pretty new to AngularJS and was wondering what is the best practice when passing a factory to a controller (if there is one)
Given a factory :
myApp.factory('myFactory', function () {
return {foo:{bar:2}};
});
Is it better to do :
myApp.controller('myCtrl', ['myFactory',function (myFactory) {
var self = this;
self.foo = myFactory;
self.bar = myFactory.bar;
}]);
or
myApp.controller('myCtrl', ['myFactory',function (myFactory) {
var self = this;
self.foo = myFactory;
self.bar = self.foo.bar;
}]);
I have forked your code. Here is a working one.
http://codepen.io/anon/pen/qERpew?editors=101
The button is not getting enabled because, you are doing all the processing related to creating crop area by using javascript event handlers, so the code related to $scope (ie.angular related), will not come into effect. To have them in effect, you have to wrap the code related to $scope into $scope.$apply(function(){ // Your $scope variable update code.}).
PS: As per convention you should not use $scope name inside directive.Instead of that use scope.

Cannot get the scope os a controller in anugular.js

I am trying to test a javascript file which has in it a controller and some HTML DOM elements which it interacts with.
The class under test is:
function BaseConceptMenu(options) {
var baseConceptMenu = new BaseMenu(options);
//Public function -->Method under Test
function showCodeConceptToolbar() {
var scope = angular.element('#toolbar').scope();
scope.$apply(function() {
scope.toolbarController.show(baseConceptMenu.someObject);
});
}
I am trying to mock the controller and create the HTML DOM element "toolbar" on the fly without trying to create an external HTML template just for the sake of testing.
I am trying to create the div "toolbar" inside the before each and mocking the "CodeConceptToolbarController" controller
beforeEach(inject(function ($rootScope, $compile) {
elm = document.createElement('div');
elm.id = 'toolbar';
scope = $rootScope.$new();
createController = function() {
return $controller('CodeConceptToolbarController', {
$scope: scope
});
};
$compile(elm)(scope);
scope.$digest();
}));
However when I try to test it as below
it('Test Code ConeptToolbarContoller', function() {
// var toolbar = angular.element('#toolbar');
document.getElementById("toolbar").scope().toolbarController = createController();
//As of now,not doing any-idepth testing
//Just a base test call
var menu = new BaseConceptMenu({});
expect(menu.show()).toBe(true);
});
I get this error
TypeError: Cannot read property 'scope' of null
Could anyone provide a way to test this?
or is there a better way to test this?
currently I am using Maven-jasmine plugin
Two problems:
As per https://developer.mozilla.org/en-US/docs/Web/API/document.getElementById, "Elements not in the document are not searched by getElementById." $compile doesn't insert the element into the DOM - it just sets up appropriate bindings (and does smart things like handling nested directives inside your template string). Your getElementById will fail to find a match. You need to insert the element into the DOM somewhere.
getElementById returns a raw HTML DOM element. To get the Angular scope from it, the docs call for wrapping it in angular.element():
var element = document.getElementById(id);
angular.element(element).scope()...
This pattern will provide the Angular wrapper around the element to do the rest of the magic. It's all based on jqLite, which isn't jQuery but does follow a lot of the same patterns. For those used to jQuery, think of it like writing $(element).

Trigger angular from outside of angular-app

Ho to trigger an event inside ngApp from the outside world?
This is my current code:
// Inside old code
console.log("Start Triggering angular");
$('#issueSelectedPid').val(pid);
$('#issueSelectedPid').trigger('input');
console.log("Triggering angular");
// inside ngApp
<input id="issueSelectedPid" ng-model="vm.selectedPid" ng-change="vm.doMyStuffTriggerdFromTheOutsideWorld()" />
Are there better ways of doing this?
Thanks for any help
Larsi
You can access the scope using the below code :
var appElement = document.querySelector('[ng-app=appName]');
var appScope = angular.element(appElement).scope();
var controllerScope = appScope.$$childHead;
controllerScope.functionName(controllerScope.vm.doMyStuffTriggerdFromTheOutsideWorld() in your case)
you need to get your html element by yourDivId which is defining your input scope, then call $apply with something or without
var scope = angular.element($("#yourDivId")).scope();
scope.$apply(function(){
scope.something= 'something';
})

Angular apply scope changes from global function cause unknown provider error

jsfiddle: http://jsfiddle.net/3gd8a/1/
(function () {
var app = angular.module("index", []);
app.run(function ($log, $controller) {
$log_service = $log;
$controller_service = $controller;
});
app.controller("AlertsController", function () {
this.alerts = [ "first alert" ];
this.innerFunction = function() {
this.alerts.push("inner alert");
$log_service.debug(alerts.alerts);
};
});
})();
function outerFunction() {
var alerts = $controller_service("AlertsController");
alerts.alerts.push("outer alert");
$log_service.debug(alerts.alerts);
};
In this example I changed the property of controller instance in the outer js function,
now I don't known how can I apply it just like use $scope.apply().
I already read angular-tips watch-how-the-apply-runs-a-digest,
$scope has the $apply function, but controller instance didn't have one.
You may want to ask why I use controller instance not $scope, because I learn angular js from codeschool so I want use the same way I learned if possible.
And you may want to ask why not use ng-click, I known use ng-click will work but I want figure out how angular js watch properties of controller instance and how to apply the changes of them manually.
Edit:
I figured out controller instance is just a property named alerts under $scope when I use it by ng-controller="AlertsController as alerts".
Now I had another problem, If I access $scope from outerFunction will cause an error.
The first time is Error: [$injector:unpr] Unknown provider: $scopeProvider <- $scope
http://errors.angularjs.org/1.2.1/$injector/unpr?p0=%24scopeProvider%20%3C-%20%24scope angular.js:78
And the second time is
Error: [$injector:cdep] Circular dependency found:
http://errors.angularjs.org/1.2.1/$injector/cdep?p0=
Since nobody answer, I leave my answer here.
Controller instance is a object under $scope after initiazlied, in the example I alias AlertsController to alerts so it named alerts under $scope.
Then $scope.alerts and this are equivalent.
In the first comment (http://jsfiddle.net/3gd8a/5) I tried put $scope to global then access it from outerFunction but failed, I still need figure out why.
In the second comment (http://jsfiddle.net/3gd8a/6) I found $scope can get from the element inside of controller, and it's can be access normally.
So the way to update and apply property of controller instance from global function is
add id to any element inside controller
use angular.element find that element then call .scope() to get the scope.
use scope.{alias of controller}.{property name} to update property, then call $scope.apply to apply.

How to mock ng-grid when unit testing a controller

I'm currently trying to write tests for existing blocks of code and running into an issue with a controller that has a nested ng-grid inside of it. The issue comes from the controller trying to interact with the grid on initialization.
Testing Software
node#0.10.14
karma#0.10.2
karma-jasmine#0.1.5
karma-chrome-launcher#0.1.2
My Test:
define(["angularjs", "angular-mocks", "jquery",
"js/3.0/report.app",
"js/3.0/report.controller",
"js/3.0/report.columns"
],
function(angular, ngMocks, jquery, oARModule, oARCtrl, ARColumns) {
"use strict";
describe("Report Center Unit Tests", function() {
var oModule;
beforeEach(function() {
oModule = angular.module("advertiser_report");
module("advertiser_report");
});
it("Advertiser Report Module should be registered", function() {
expect(oModule).not.toBeNull();
});
describe("Advertiser Report Controller", function() {
var oCtrl, scope;
beforeEach(inject(function($rootScope, $controller, $compile) {
var el = document.createElement('div');
el.setAttribute('ng-grid','gridOptions');
el.className = 'gridStyle';
scope = $rootScope.$new();
$compile(el)(scope);
oCtrl = $controller('ARController', {
$scope: scope
});
}));
it("Advertiser Report controller should be registered", function() {
expect(oCtrl).not.toBeNull();
});
});
});
});
You'll see where I've tried to create and compile an element with the ng-grid attribute. Without doing this I get the following error:
TypeError: Cannot read property 'columns' of undefined
Which is a result of the controller attempting to call things like
$scope.gridOptions.$gridScope.columns.each
So I added the creation of a div with ng-grid attribute, and got a new error:
TypeError: Cannot set property 'gridDim' of undefined
So, I tried to add scope.gridOptions before the $controller call, but this brought me back to the original error. I've been searching for way to make this work without rewriting the controller and/or templates, since they are currently working correctly in production.
Your (major!) problem here is that the controller is making assumptions about a View. It should not know about and thus not interact with ng-grid. Controllers should be View-independent! That quality (and Dependency Injection) is what makes controllers highly testable. The controller should only change the ViewModel (i.e. its $scope), and in testing you validate that the ViewModel is correct.
Doing otherwise goes against the MVVM paradigm and best practices.
If you feel like you must access the View (i.e. directives, DOM elements, etc...) from the controller, you are likely doing something wrong.
The problem in the second Failing test is gridOptions and myData is not defined prior to the compilation. Notice the sequence of the 2 statements.
Passing
oCtrl = $controller('MainCtrl', { $scope: $scope });
$compile(elm)($scope);
Failing
$compile(elm)($scope);
oCtrl = $controller('MainCtrl', { $scope: $scope });
In both cases you are trying to use the same html
elm = angular.element('<div ng-grid="gridOptions" style="width: 1000px; height: 1000px"></div>');
I suggest you get rid of
oCtrl = $controller('MainCtrl', { $scope: $scope });
maneuvers and use the following HTML element instead
elm = angular.element('<div ng-controller="MainCtrl"
ng-grid="gridOptions" style="width: 1000px; height: 1000px"></div>');
Notice ng-controller="MainCtrl".
So the end story is that you need gridOptions defined somewhere so
that it ngGrid can access it. And make sure gridOptions dependent
code in controller is deferred in a $timeout.
Also take a look at the slight changes in app.js
$timeout(function(){
//your gridOptions dependent code
$scope.gridOptions.$gridScope.columns.each(function(){
return;
});
});
Here is the working plnkr.

Resources