I read an answer about "dynamic factory" from How to create Dynamic factory in Angular js?
It works great but some additional conditions required for my project make me tired.
conditions are simple.
There is a button that trigger dynamic directive element. From now we call the original background's scope 'ParentScope' and directive's 'ChildScope'.
At the same time, 'ParentScope' dynamically make factory named "ex2-service".
This directive's controller needs to inject "ex2-service".
above simple example is http://jsfiddle.net/chaht01/QM52v/29/
jscode below
var app = angular.module("myApp", [])
.service("echo", function() {
return {
echo: function(msg) {
console.log(msg);
return msg;
}
};
}), makeService = function(module, identifier) {
module.factory(identifier+'-service', ['echo', function(echo) {
return {
run: function(msg) {
return echo.echo(identifier + ": " + msg);
}
};
}]);
};
makeService(app, 'ex1');
app.controller("myController", ['ex1-service',
'$injector',
'$scope',
function(service, $injector, $scope) {
$scope.service1 = service.run('injected.');
$scope.test = function(){
$scope.isReady = true;
makeService(app, 'ex2');
}
}]).controller("child",['ex2-service','$scope',function(service,$scope){
$scope.service2 = service.run('dynamically injected')
}])
when I click button, get error like
Error: [$injector:unpr] Unknown provide: ex2-serviceProvider <- ex2-service
how can I solve this problem??
Related
Service:
app.service('myService', ['$scope', '$timeout', function($scope, $timeout){
return {
fn: function(messageTitle, messageContent) {
$timeout(function() {
$scope.fadeMessageSuccess = true;
}, 3000);
}
}
}]);
Controller:
app.controller("AccountCtrl", ["$scope", "Auth", "$timeout", "myService",
function($scope, Auth, $timeout, myService) {
myService.fn();
$scope.createUser = function() {
$scope.message = null;
$scope.error = null;
// Create a new user
Auth.$createUserWithEmailAndPassword($scope.accountEmailAddress, $scope.accountPassword)
.then(function(firebaseUser) {
$scope.message = "User created with uid: " + firebaseUser.uid;
console.log($scope.message);
}).catch(function(error) {
$scope.error = error;
console.log($scope.error);
});
};
}
]);
I'm trying to create a service so that I can use a function in multiple controllers but I'm have trouble getting this first one working. This is the error message I'm getting in console:
angular.js:13550Error: [$injector:unpr]
Just an observation: doesn't look like you're passing anything to the function when you're calling it. And not sure if you're wanting to add any more functionality to the service, but I think you can return the function directly and just call "myService(title, content);". But I don't think those issues would cause what you're encountering.
It looks like you were trying to return an object (a la the .factory() function) when you were trying to use .service(). Here is a dead simple explanation for .factory, .service, and .provider.
As pointed out by user2341963, injecting $scope into a service doesn't make much sense.
Also, are you sure all of your dependencies are defined and available to Angular?
Here is an example Plunkr of using a service in a controller.
I have simple controller with one method:
app.controller('MyApp', function($scope) {
$scope.myMethod() {...}
}
I have also many components for input fields (for example for text input, number input, checkbox, radio etc.):
app.component('TextInput', {
template: "<input type='text' ng-change='$ctrl.myMethodInComponent()' ng-model='inp' />",
bindings: {
myMethod: '&',
},
controller: function() {
var ctrl = this;
ctrl.myMethodInComponent = function() {
/* some customizations */
ctrl.myMethod();
}
}
});
I create this input in following way:
<text-input myMethod="myMethod()"></text-input>
Everything works as expected, but the problem is that I have many components which want to use method 'myMethod' from main controller and I don't want to transfer it to each component using bindings ('&').
Rather I want to have this method in something like mainScope. I know that Angular provides rootScope but I don't have idea how to use it in my case. Is there possibility to attach 'myMethod' to some main (root) scope which will be shared between all my components?
What you want to do can be achieved by using services and factories. Take alook into it, and if you need help or a template, just ask me.
EDIT template
app.factory('myFactory', function($scope)
var ret = {
myMethod: myMethod,
myMethodWithParams: myMethodWithParams
}
return ret;
function myMethod() {...}
function myMethodWithParams(param1, param2) {...}
}
And now, in your controllers, you can use it as a dependency
app.controller('myController', function(myFactory) {
var x = myFactory.myMethod();
var y = myFactory.myMethodWithParams('hello', 'world');
});
Not sure this is the sort of use case you are looking for using $rootScope, but here's a solution along those lines:
angular.module('myApp', [])
.controller('MyController', function ($scope, $rootScope) {
$scope.message = 'Hello from Controller 1';
$scope.$watch('message', function () {
$rootScope.$emit('controller1_scope_change', {
message: $scope.message
});
});
}).controller('MyController2', function ($scope, $rootScope) {
$scope.message = 'Hello from Controller 2, here is the output from Controller 1:';
$rootScope.$on('controller1_scope_change', function (event, args) {
console.log('arguments received by the handler for the event: ', args);
$scope.message2 = args.message;
});
// really hacky way of doing it
/*var controller1_scope = angular.element(document.querySelector('#controller1')).scope();
controller1_scope.$watch(function () {
$scope.message2 = controller1_scope.message
});*/
});
View example here on codepen
I want 'MyController2' to inherit 'MyController1', however, both controllers are lazyloaded using ocLazyLoad. According to Jussi Kosunen's answer to this question (https://stackoverflow.com/a/15292441/2197555), I have made a function registerDynamic() to register the controllers, but it still reports the following error at the line with '$controller' in controller1.js:
Error: [$injector:unpr] Unknown provider: $elementProvider <- $element <- DataTableController
My codes are like this.
First file controller1.js:
angular.module( 'myApp',[])
.controller( 'MyController1', [ function($scope){
// ...
}]);
Second file controller2.js:
angular.module( 'myApp')
.controller( 'MyController2', [ '$controller', '$scope',function($controller, $scope){
$controller('MyController1', {$scope: $scope }); // here triggers error '[$injector:unpr] Unknown provider'
// ...
}]);
In the third File lazy-load.js, I lazyload the above two .js files:
var app = angular.module('myApp'),
queueLen = app._invokeQueue.length;
app.directive( 'LazyLoad', [ function( ){
return {
restrict: 'EA',
scope: {
src: '#',
},
link: function( scope, element, attr ){
var registerDynamic = function() {
// Register the controls/directives/services we just loaded
var queue = syncreonApp._invokeQueue;
for(var i=queueLen;i<queue.length;i++) {
var call = queue[i];
// call is in the form [providerName, providerFunc, providerArguments]
var provider = syncreonApp.providers[call[0]];
if(provider) {
// e.g. $controllerProvider.register("Ctrl", function() { ... })
$log.debug("Registering " + call[1] + " " + call[2][0] + " ...");
provider[call[1]].apply(provider, call[2]);
}
}
queueLen = i;
},
loadMultipleJs = function ( js_files ){
var deferred = $q.defer();
var js_file1 = js_files.shift(),
js_file2 = js_files.shift();
$ocLazyLoad.load( js_file1 )
.then ( function(){
registerDynamic();
$ocLazyLoad.load( js_file2 )
.then ( function(){
registerDynamic();
deferred.resolve();
}, function(){
deferred.reject();
});
}, function(){
deferred.reject();
});
};
jsonOfJsFilesToLoad = JSON.parse(scope.src);
loadMultipleJs(jsonOfJsFilesToLoad );
}
};
}]);
UPDATE
The official Angular documentation for 'Unknown Provider' error says:
Attempting to inject one controller into another will also throw an Unknown provider error:
Maybe we just cannot injector controller into anther even using $controller service?
You are taking the error message out of context.
Attempting to inject one controller into another will also throw an Unknown provider error:
angular.module('myModule', [])
.controller('MyFirstController', function() { /* ... */ })
.controller('MySecondController', ['MyFirstController', function(MyFirstController) {
// This controller throws an unknown provider error because
// MyFirstController cannot be injected.
}]);
That is not the way you are instantiating controllers.
It specifically says:
Use the $controller service if you want to instantiate controllers yourself.
Which is the way you are instantiating your controllers.
Look for your problem elsewhere.
Your error message:
Unknown provider: $elementProvider <- $element <- DataTableController
The way I read this is that in your DataTableController, you are trying to inject $element. $element is not a service is a local. To inject $element as a local with the $controller service:
$controller('DataTableController', {$scope: $scope, $element: value });
My code as follows :
var module = ons.bootstrap('my-app', ['onsen','ngSanitize','ngCookies','ngStorage']);
module.factory('methodService', [ '$scope', '$timeout', '$http',
'$localStorage', 'alertService',
function($scope, $timeout, $http, $localStorage, alertService){
}]);
module.factory('alertService', function () {
var data = {
title: 'Alert',
message: ''
}
return {
getTitle: function () {
return data.title;
},
setTitle: function (title) {
data.title = title;
},
getMessage: function () {
return data.message;
},
setMessage: function (message) {
data.message = message;
},
alert : function(){
ons.notification.alert({
message: data.message,
title: data.title
});
}
};
});
And there is a error Error: [$injector:unpr] Unknown provider: $scopeProvider <- $scope <- methodService
Anybody knows the reason and how to solve it?
You're misunderstanding about 2 things :
As said in comments the $scope is a special variable that can only be injected in controllers
The scope object injected in the controller is different for every controller. It's not the same instance of the object. Angular is build like this : you have a $rootScope. Every other $scope inherits from that $rootScope or from another $scope. It's a tree hierarchy. Thanks to the inheritance, you can retrieve data stored in a parent $scope.
My advise : don't store $scope, don't spam the $rootScope. Just store what you need in the $scope of your controller and call your factory layer properly like this :
$scope.myObjectToCreate = {};//object that wll be field in a form
$scope.createObject = function(){
myService.create($scope.myObjectToCreate);
}
//or
$scope.createObject = function(object){
myService.create(object);
}
// but NEVER DO THIS
$scope.createObject = myService.create;
The last point is about the scope of the function that will change, every use of this in the function myService.create will make it crash. Because this wil refer to $scope instead of myService
Given the following service that is meant to create a "dialog" element (i.e. a modal):
app.service('dialog', ['$document', '$compile', '$rootScope',
function($document, $compile, $rootScope) {
var body = $document.find('body');
var scope = $rootScope.$new();
this.createDialog = function() {
var dialogElem = angular.element('<div ng-include="\'/dialog.html\'"></div>');
$compile(dialogElem)(scope);
body.append(dialogElem);
};
}
]);
which can be utilized in a controller like so:
$scope.someFunction = function() {
dialog.createDialog();
};
Is there a way that I can use $compile or anything else to not have HTML in my service? I'd really prefer to just invoke a directive, so that running createDialog() immediately injects a directive into my DOM and thus the directive is responsible for linking a new controller and template together. If I'm going about this the wrong way I'm totally open to constructive ideas.
Of course you can!, here you go:
app.factory('modalService', function ($document, $compile, $rootScope, $templateCache, $http) {
var body = $document.find('body'),
modals = [];
var service = {
show: function (template, data, modal) {
// The template's url
var url = 'template/modal/' + template + '.html';
// A new scope for the modal using the passed data
var scope = $rootScope.$new();
angular.extend(scope, data);
// Wrapping the template with some extra markup
modal = modal || angular.element('<div class="modal"/>');
// The modal api
var api = {
close: function () {
modal.remove();
scope.$destroy();
modals.splice(modals.indexOf(api), 1);
},
replace: function (template, data) {
return angular.extend(api, service.show(template, data, modal));
}
};
// Adding the modal to the body
body.append(modal);
// A close method
scope.close = api.close;
// Caching the template for future calls
$http.get(url, {cache: $templateCache})
.then(function (response) {
// Wrapping the template with some extra markup
modal.html('<div class="win">' + response.data + '</div>');
// The important part
$compile(modal)(scope);
});
modals.push(modal);
return api;
},
showOrReplaceLast: function (template, data) {
return service.show(template, data, modals.length > 0 ? modals[modals.length - 1] : null);
}
};
return service;
});
Some notes:
You need to insert the modal somewhere in the DOM, that's why the $document is injected.
Yes, you can take the modal markup out of here.
Remember to create new scopes for the dialog and to destroy them ($rootScope.$new).
This is a WIP, I hope it's clear enough.
You could try this as below, just to render your ng-include before opening the dialog
app.service('dialog', ['$http', '$compile', '$q', '$templateCache'
function($http, $compile, $q, $templateCache) {
this.compileInclude = function(scope, url) {
var deferred = $q.defer();
$http.get(url, {cache : $templateCache}).then(function(response){
deferred.resolve($compile(response.data)(scope));
});
return deferred.promise;
};
}
]);
From the controller write as below
dialog.compileInclude($scope, 'dialog.html').then(function(){
// open dialog here
});