I'm trying to initialize a test.service.js from an angular1.5 component and I don't really know how to that. I have used this structure for angular components:
var testComponent = {
templateUrl: "app/components/test.component.html",
controllerAs: "mvm",
bindings:{
bindingInput: "=",
},
controller: function(){
var mvm = this;
}
};
angular.module('myApp').component('testComponent', testComponent);
Just inject it using the controller as following :
...
},
controller: ['testService', function(testService){
var mvm = this;
}],
...
You can inject the service right into the controller like this:
....
controller: function(testService) {
var mvm = this;
}
};
To prevent testService getting obfuscated by a minification plugin, let the injector service know about the dependencies as well.
var Controller = function(testService) {
// Your controller code here
};
Controller.$inject = ['testService'];
var testComponent = {
...
controller: Controller, // Assign controller in component
...
};
I will explain what exactly I'm trying to do before explaining the issue. I have a Directive which holds a form, and I need to access that form from the parent element (where the Directive is used) when clicking on a submit button to check fi the form is valid.
To do this, I am trying to use $scope.$parent[$attrs.directiveName] = this; and then binding some methods to the the Directive such as this.isValid which will be exposed and executable in the parent.
This works fine when running locally, but when minifying and building my code (Yeoman angular-fullstack) I will get an error for aProvider being unknown which I traced back to a $scopeProvider error in the Controller.
I've had similar issues in the past, and my first thought was that I need to specifically say $inject for $scope so that the name isn't lost. But alas.....no luck.
Is something glaringly obvious that I am doing wrong?
Any help appreciated.
(function() {
'use strict';
angular
.module('myApp')
.directive('formDirective', formDirective);
function formDirective() {
var directive = {
templateUrl: 'path/to/template.html',
restrict: 'EA',
scope: {
user: '='
},
controller: controller
};
return directive;
controller.$inject = ['$scope', '$attrs', 'myService'];
function controller($scope, $attrs, myService) {
$scope.myService = myService;
// Exposes the Directive Controller on the parent Scope with name Directive's name
$scope.$parent[$attrs.directiveName] = this;
this.isValid = function() {
return $scope.myForm.$valid;
};
this.setDirty = function() {
Object.keys($scope.myForm).forEach(function(key) {
if (!key.match(/\$/)) {
$scope.myForm[key].$setDirty();
$scope.myForm[key].$setTouched();
}
});
$scope.myForm.$setDirty();
};
}
}
})();
Change the directive to a component and implement a clear interface.
Parent Container (parent.html):
<form-component some-input="importantInfo" on-update="someFunction(data)">
</form-component>
Parent controller (parent.js):
//...
$scope.importantInfo = {data: 'data...'};
$scope.someFunction = function (data) {
//do stuff with the data
}
//..
form-component.js:
angular.module('app')
.component('formComponent', {
template:'<template-etc>',
controller: Controller,
controllerAs: 'ctrl',
bindings: {
onUpdate: '&',
someInput: '<'
}
});
function Controller() {
var ctrl = this;
ctrl.someFormThing = function (value) {
ctrl.onUpdate({data: value})
}
}
So if an event in your form triggers the function ctrl.someFormThing(data). This can be passed up to the parent by calling ctrl.onUpdate().
I create a directive based on ES6 style:
export default class myDirective {
constructor() {
this.restrict = 'E';
this.scope = {};
this.link = link;
}
link() {
console.log('link myDirective');
}
}
then in index.js:
import angular from 'angular';
import myDirective from './myDirective';
export default angular
.module('app.directives', [])
.directive('myDirective ', () => new myDirective())
.name;
But when I call myDirective on html like: <my-directive><my-directive> it does not call link function or compile function. What can I do?
We use ES6 here, and our directives dont really look like that, I'll give an example:
import templateUrl from './some.html';
import SomeController from './someController';
export default () => ({
templateUrl,
controller: SomeController,
controllerAs: 'vm',
scope: {
someVariable: '='
},
link: (scope, element, attrs, controller) => {
scope.link = {
someFunction: function some() { }
}
},
bindToController: true
});
You get the idea anyway. Try structuring it like that and see if the link function works as you would expect.
I have the same problem using AngularJS + ES6 + Webpack. Maybe you could add this in your Directive, in your compile function:
compile() {
//console.log("compile");
return this.link.bind(this);
}
Check this links for more acurated info:
https://github.com/geniuscarrier/webpack-angular-es6/blob/master/app/modules/home/directive/footer.js
https://www.michaelbromley.co.uk/blog/exploring-es6-classes-in-angularjs-1.x/
First of all, I'm using components.
I have this "parent" component:
(function() {
'use strict';
angular
.module('parentModule', [])
.component('parent', {
templateUrl: 'parent.tpl.html',
controller: ParentCtrl,
transclude: true,
bindings: {
item: '='
}
});
function ParentCtrl() {
var vm = this;
vm.item = {
'id': 1,
'name': 'test'
};
}
})();
And I'm simply trying to share the object item with another component, like this:
(function() {
'use strict';
angular
.module('childModule', [])
.component('child', {
templateUrl: 'child.tpl.html',
controller: ChildCtrl,
require: {
parent: '^item'
}
});
function ChildCtrl() {
console.log(this.parent)
var vm = this;
}
})();
View (Parent):
Parent Component:
<h1 ng-bind='$ctrl.item.name'></h1>
<child></child>
View (Child):
Child component:
Here I want to print the test that is in the parent component
<h2 ng-bind='$ctrl.item.name'></h2>
Actually I'm getting the following error:
Expression 'undefined' in attribute 'item' used with directive
'parent' is non-assignable!
Here's the DEMO to illustrate better the situation
Can you explain to me how I can make it work?
You need to remove the bindings from yor parent component.
bindings binds to the component controller like scope binds to a directive's scope. You're not passing anything to <parent></parent> So you have to remove it.
Then your child component requires a parent component, not an item.
So
require: {
parent: '^parent'
}
Of course the child template should be modified to:
<h2 ng-bind='$ctrl.parent.item.name'></h2>
Finally, if from the child controller you want to log the item that is inside the parent, you will have to write:
function ChildCtrl($timeout) {
var vm = this;
$timeout(function() {
console.log(vm.parent.item);
});
}
I never need the timeout in my components, so there might be something obvious that I missed.
http://plnkr.co/edit/0DRlbedeXN1Z5ZL45Ysf?p=preview
EDIT:
Oh I forgot, you need to use the $onInit hook:
this.$onInit = function() {
console.log(vm.parent.item);
}
Your child should take the item as input via bindings.
(function() {
'use strict';
angular
.module('childModule', [])
.component('child', {
templateUrl: 'child.tpl.html',
controller: ChildCtrl,
bindings: {
item: '='
}
});
function ChildCtrl() {
console.log(this.parent)
var vm = this;
}
})();
So your parent template will look like
<h1 ng-bind='$ctrl.item.name'></h1>
<child item="$ctrl.item"></child>
The rest should work same.
I've ran into problem with ng-controller and 'resolve' functionality:
I have a controller that requires some dependency to be resolved before running, it works fine when I define it via ng-route:
Controller code looks like this:
angular.module('myApp')
.controller('MyController', ['$scope', 'data', function ($scope, data) {
$scope.data = data;
}
]
);
Routing:
...
.when('/someUrl', {
templateUrl : 'some.html',
controller : 'MyController',
resolve : {
data: ['Service', function (Service) {
return Service.getData();
}]
}
})
...
when I go to /someUrl, everything works.
But I need to use this controller in other way(I need both ways in different places):
<div ng-controller="MyController">*some html here*</div>
And, of course, it fails, because 'data' dependency wasn't resolved. Is there any way to inject dependency into controller when I use 'ng-controller' or I should give up and load data inside controller?
In the below, for the route resolve, we're resolving the promise and wrapping the return data in an object with a property. We then duplicate this structure in the wrapper service ('dataService') that we use for the ng-controller form.
The wrapper service also resolves the promise but does so internally, and updates a property on the object we've already returned to be consumed by the controller.
In the controller, you could probably put a watcher on this property if you wanted to delay some additional behaviours until after everything was resolved and the data was available.
Alternatively, I've demonstrated using a controller that 'wraps' another controller; once the promise from Service is resolved, it then passes its own $scope on to the wrapped controller as well as the now-resolved data from Service.
Note that I've used $timeout to provide a 1000ms delay on the promise return, to try and make it a little more clear what's happening and when.
angular.module('myApp', ['ngRoute'])
.config(function($routeProvider) {
$routeProvider
.when('/', {
template: '<h1>{{title}}</h1><p>{{blurb}}</p><div ng-controller="ResolveController">Using ng-controller: <strong>{{data.data}}</strong></div>',
controller: 'HomeController'
})
.when('/byResolve', {
template: '<h1>{{title}}</h1><p>{{blurb}}</p><p>Resolved: <strong>{{data.data}}</strong></p>',
controller: "ResolveController",
resolve: {
dataService: ['Service',
function(Service) {
// Here getData() returns a promise, so we can use .then.
// I'm wrapping the result in an object with property 'data', so we're returning an object
// which can be referenced, rather than a string which would only be by value.
// This mirrors what we return from dataService (which wraps Service), making it interchangeable.
return Service.getData().then(function(result) {
return {
data: result
};
});
}
]
}
})
.when('/byWrapperController', {
template: '<h1>Wrapped: {{title}}</h1><p>{{blurb}}</p><div ng-controller="WrapperController">Resolving and passing to a wrapper controller: <strong>{{data.data ? data.data : "Loading..."}}</strong></div>',
controller: 'WrapperController'
});
})
.controller('HomeController', function($scope) {
$scope.title = "ng-controller";
$scope.blurb = "Click 'By Resolve' above to trigger the next route and resolve.";
})
.controller('ResolveController', ['$scope', 'dataService',
function($scope, dataService) {
$scope.title = "Router and resolve";
$scope.blurb = "Click 'By ng-controller' above to trigger the original route and test ng-controller and the wrapper service, 'dataService'.";
$scope.data = dataService;
}
])
.controller('WrapperController', ['$scope', '$controller', 'Service',
function($scope, $controller, Service) {
$scope.title = "Resolving..."; //this controller could of course not show anything until after the resolve, but demo purposes...
Service.getData().then(function(result) {
$controller('ResolveController', {
$scope: $scope, //passing the same scope on through
dataService: {
data: result
}
});
});
}
])
.service('Service', ['$timeout',
function($timeout) {
return {
getData: function() {
//return a test promise
return $timeout(function() {
return "Data from Service!";
}, 1000);
}
};
}
])
// our wrapper service, that will resolve the promise internally and update a property on an object we can return (by reference)
.service('dataService', function(Service) {
// creating a return object with a data property, matching the structure we return from the router resolve
var _result = {
data: null
};
Service.getData().then(function(result) {
_result.data = result;
return result;
});
return _result;
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.27/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.27/angular-route.min.js"></script>
<div ng-app="myApp">
By ng-controller |
By Resolve |
By Wrapper Controller
<div ng-view />
</div>
Create a new module inside which you have the service to inject like seen below.
var module = angular.module('myservice', []);
module.service('userService', function(Service){
return Service.getData();
});
Inject newly created service module inside your app module
angular.module('myApp')
.controller('MyController', ['$scope', 'myservice', function ($scope, myservice) {
$scope.data = data;
// now you can use new dependent service anywhere here.
}
]
);
You can use the mechanism of the prototype.
.when('/someUrl', {
template : '<div ng-controller="MyController" ng-template="some.html"></div>',
controller: function (data) {
var pr = this;
pr.data = data;
},
controllerAs: 'pr',
resolve : {
data: ['Service', function (Service) {
return Service.getData();
}]
}
})
angular.module('myApp')
.controller('MyController', ['$scope', function ($scope) {
$scope.data = $scope.pr.data; //magic
}
]
);
Now wherever you want to use
'<div ng-controller="MyController"></div>'
you need to ensure that there pr.data in the Scope of the calling controller. As an example uib-modal
var modalInstance = $modal.open({
animation: true,
templateUrl: 'modal.html',
resolve: {
data: ['Service', function (Service) {
return Service.getData();
}]
},
controller: function ($scope, $modalInstance, data) {
var pr = this;
pr.data = data;
pr.ok = function () {
$modalInstance.close();
};
},
controllerAs:'pr',
size:'sm'
});
modal.html
<script type="text/ng-template" id="modal.html">
<div class="modal-body">
<div ng-include="some.html" ng-controller="MyController"></div>
</div>
<div class="modal-footer">
<button class="btn btn-primary pull-right" type="button" ng-click="pr.ok()">{{ 'ok' | capitalize:'first'}}</button>
</div>
</script>
And now you can use $scope.data = $scope.pr.data; in MyController
pr.data is my style. You can rewrite the code without PR.
the basic principle of working with ng-controller described in this video https://egghead.io/lessons/angularjs-the-dot
Presuming that Service.getData() returns a promise, MyController can inject that Service as well. The issue is that you want to delay running the controller until the promise resolves. While the router does this for you, using the controller directly means that you have to build that logic.
angular.module('myApp')
.controller('MyController', ['$scope', 'Service', function ($scope, Service) {
$scope.data = {}; // default values for data
Service.getData().then(function(data){
// data is now resolved... do stuff with it
$scope.data = data;
});
}]
);
Now this works great when using the controller directly, but in your routing example, where you want to delay rendering a page until data is resolved, you are going to end up making two calls to Service.getData(). There are a few ways to work around this issue, like having Service.getData() return the same promise for all caller, or something like this might work to avoid the second call entirely:
angular.module('myApp')
.controller('MyController', ['$scope', '$q', 'Service', function ($scope, $q, Service) {
var dataPromise,
// data might be provided from router as an optional, forth param
maybeData = arguments[3]; // have not tried this before
$scope.data = {}; //default values
// if maybeData is available, convert it to a promise, if not,
// get a promise for fetching the data
dataPromise = !!maybeData?$q.when(maybeData):Service.getData();
dataPromise.then(function(data){
// data is now resolved... do stuff with it
$scope.data = data;
});
}]
);
I was trying to solve the problem using ng-init but came across the following warnings on angularjs.org
The only appropriate use of ngInit is for aliasing special properties
of ngRepeat, as seen in the demo below. Besides this case, you should
use controllers rather than ngInit to initialize values on a scope.
So I started searching for something like ng-resolve and came across the following thread:
https://github.com/angular/angular.js/issues/2092
The above link consists of a demo fiddle that have ng-resolve like functionality. I think ng-resolve can become a feature in the future versions of angular 1.x. For now we can work around with the directive mentioned in the above link.
'data' from route resolve will not be available for injection to a controller activated other than route provider. it will be available only to the view configured in the route provider.
if you want the data to the controller activated directly other than routeprovider activation, you need to put a hack for it.
see if this link helps for it:
http://www.johnpapa.net/route-resolve-and-controller-activate-in-angularjs/
Getting data in "resolve" attribute is the functionality of route (routeProvider) , not the functionality of controller.
Key( is your case : 'data') in resolve attribute is injected as service.
That's why we are able fetch data from that service.
But to use same controller in different place , you have fetch data in controller.
Try this
Service:
(function() {
var myService = function($http) {
var getData = function() {
//return your result
};
return {
getData:getData
};
};
var myApp = angular.module("myApp");
myApp.factory("myService", myService);
}());
Controller:
(function () {
var myApp = angular.module("myApp");
myApp.controller('MyController', [
'$scope', 'myService', function($scope, myService) {
$scope.data = myService.getData();
}
]);
//Routing
.when('/someUrl', {
templateUrl : 'some.html',
controller : 'MyController',
resolve : {
data: $scope.data,
}
})
}());