Unable to inject service in angular js - angularjs

I'm constantly failing to inject the $log service in a minify save way into a controller class of a component.
To check the injection is save, I added ng-strict-di to my app. This in terms causes an SearchResultController is not using explicit annotation and cannot be invoked in strict mode which is fine, since I'm relying on implicit injection right now.
So I added an explicit injection to my component implementation:
import htmlTemplate from './searchInput.html';
class SearchInputController {
constructor($log) {
this._log = $log;
this.searchText = 'Text to search for';
}
handleUpdate() {
this.onChange({value: this.searchText});
}
doubleMe(i) {
this._log.debug('SearchInputController.doubleMe: i='+i);
return i+i;
}
}
SearchInputController.$inject = ['$log'];
let searchInputComponent = {
template: htmlTemplate,
controller: SearchInputController,
bindings: {
onChange: '&'
}
};
export default searchInputComponent;
This has no effect the error message is still complaining about the missing injection.
I also tried ng-annotate-loader and ng-annotate-webpack-plugin. And also tried the /*#ngInject*/ and 'ngInject'; type annotations. All of this has no effect.
Any ideas how to get the dependency injection working?

Your annotation appears to be correct for SearchInputController, but the error message is complaining about a different controller SearchResultController. I suggest you need to annotate that one also.
Use https://babeljs.io/repl/ to check how ES6 compiles down to ES5. This can be useful when trying to trace errors in annotation. Using ng-annotate in some form should also work when you annotate the correct controller.

Related

Why getting provider error when trying to inject resolved property in controller using ui-router?

I am unable to inject resolve property of ui-routing in controller.
It is giving
Error: $injector:unpr
Unknown Provider
When I'm using controller property in state definition object as following
.state('widget', {
url: '/widgets',
template: '<h1>{{name}}</h1>',
controller: function(widget, $scope) {
$scope.name = widget.name;
},
resolve: {
// standard resolve value promise definition
widget: function() {
return {
name: 'myWidget'
};
},
// resolve promise injects sibling promise
features: function(widget) {
return ['featureA', 'featureB'].map(function(feature) {
return widget.name+':'+feature;
});
}
}
});
Then it is working fine and I'm able to get the widget in controller and able to use in html.
Please see the fiddle for code.
http://jsfiddle.net/sunilmadaan07/ugsx6c1w/8/
Might be I'm making a silly mistake.
Before posting this question I have tried returning with simple object, promise object to the property.
Thanks In Advance.
You can not get resolved data in the directive with the code you did. Basically, you are trying to implement component based structure with an older version of angular 1.3.x.
You have two options to achieve this.
Create route controller then you can access resolve to the controller as local dependency then use that dependency as binding to the directive.
Here is example - http://plnkr.co/edit/TOPMLUXc7GhXTeYL0IFj?p=preview
Upgrade angular version to 1.5.x and use "ui-router-route-to-components": "^0.1.0"
Here working example of your code - http://jsfiddle.net/ugsx6c1w/25/
In order for the controller to be able to use resolvers, it should be route component (only in UI Router 1.x) or route controller.
widget and features are local dependencies, they aren't registered in the injector and aren't available anywhere in the application except route controller/component.
As explained here, resolvers can be passed to nested components in UI Router 0.3.x and injected directly to route components in 1.x.

how to inject momentjs in typescript in AngularJs 1.5

I want to inject the momentJs library in my typescript code to do operations on a Date object. But I am not familiar with injecting typescript through angularJs, since it is little different from javascript.
angular.module("app")
.config(function ($mdDateLocaleProvider) {
$mdDateLocaleProvider.formatDate = function (date) {
return moment(date).format('YYYY-MM-DD');
};
});
In the above code, the moment function is not recognized, even though I have included the CDN.
1 - Install moment with your package manager (NPM for example)
2 - Reference it in your entry point application file :
<script src="node_modules/moment/moment.js"></script>
3 - Create a constant referencing moment as it can be used as a service (don't forget to import it) :
import moment = require('moment');
angular.module("app").constant("moment", moment);
4 - You should be able now to configure moment in your angular run function like this :
angular.run(["moment", function(moment) { moment.locale("fr")}]);
AND using it in your controllers aswell still using dependency injection.
TIPS - Using typescript you should create your application using classes like this (even if Typescript is just a superset of Javascript) :
class MyController implements ng.IController {
static $inject: string[] : ["moment"];
constructor(
private moment) {}
$onInit = function () {
// do something with moment
let date = this.moment();
}
}
Let me know if it helps you out.
After you add all necessary scripts of moment.js in your page.
Use it like this and try
angular.module("app")
.config(function ($mdDateLocaleProvider, moment) {
$mdDateLocaleProvider.formatDate = function (date) {
return moment(date).format('YYYY-MM-DD');
};
});
You forgot to add moment in config callback. You have to add moment in config callback as you have added $mdDateLocaleProvider.
There is a more popular angular-moment. you can see the steps in documentation of angular-moment. https://github.com/urish/angular-moment
let me know if any thing else is required

Angular 1.5+ components - using binding inside of templateUrl function

I'm using Angular 1.5+ with Typescript, preparing my code to be compatible with Angular 2. I've got a situation where many of my components need to use an application-wide repository location for the views that are mapped to their templateUrl properties, but sometimes a view needs a specific, local implementation.
So, normally the views are hosted on a fast-served CDN, they're re-used between multiple sites that all belong to the same general code base, api, etc. This is done to prevent duplicating them and to centralize what doesn't need to be repeated.
Rarely, I'll need to override this behavior and use a more specific, fine-tuned view. So my approach to this was to add a binding to the components called viewLocation, with the intent to use it like this;
<component-name></component-name> is the default. In this situation, the default CDN path is used.
<component-name view-location="local"></component-name> is a unique situation. If this happens, the templateUrl should be able to respond to that and switch to a path relative to the specific application and not from the CDN.
I thought it would be pretty simple until I realized that I wouldn't have access to the binding properties within the actual constructor or templateUrl function, as is demonstrated here;
export class SidebarComponent extends Component {
constructor() {
super();
this.bindings = { viewLocation: '=' };
// other properties
this.templateUrl = ($someService: IServiceInterface): string => {
// help! I don't have access to the viewLocation property!
}
}
}
So is there anything else I can do to get access to that property?
This is not done in TS, but the AngularJS 1.5 component generally provides the $element and $attrs to the $injector when you are using an injectable template.
This is an example in AngularJS using Javascript where the template URL is picked based on an attribute set on the component:
angular.module('example', []);
angular.module('example').component('myComponent', {
templateUrl: function($element, $attrs, $log) {
$log.info('determining template to be used');
if($attrs.useTemplate) {
return $attrs.useTemplate;
}
return 'default.html';
}
});
Template snippet:
<my-component use-template="hui.html"></my-component>
<my-component use-template="bu.html"></my-component>
<p></p>
<my-component></my-component>
Working example:
template injection in an angular 1.5 component

Angular 1.5 $routerOnActivate on Root Router

I want to make sure that the user is logged in properly before proceeding to any of the components he/she's trying to reach, if they're not logged in. Send them to login.
My idea is to do a check in the $routerOnActivate in the root router. Which to me would solve the issue for any sub routes.
However nothing seems to happen if i just try to log something. Example:
angular
.module('app')
.config(function($locationProvider) {
$locationProvider.html5Mode(true);
})
.value('$routerRootComponent', 'app')
.component('app', {
templateUrl:'landing.html',
controller: MainController,
$routeConfig: [
{ path: '/', name: 'Dashboard', component: 'dashboard', useAsDefault: true },
{ path: '/media', name: 'Media', component: 'media'}
...
]
});
function MainController(){
this.$routerOnActivate = function(next, previous){
console.log('activated', next, previous);
};
}
The same code this.$routerOnActivate works if i put it in any of the Components which are specified in the routeConfig. However obviously I don't want to make the same check in every component, but rather solve it once globally.
What is the approach for 1.5?
Instead of performing a check when you load a page, use following:
Angular has a special component lifecycle hook to handle these edge cases named $canActivate - to check whether it should try to activate it or not. You may talk to a authentication/authorization service to validate that for a given user's state, are they allowed to reach your component or not.
This way your component will be smart enough to encapsulate any checks required to activate itself.
Also, you can inject any service like $http or your custom service to talk to your server. In the code snippet below, I mimic this call using $timeout that just returns true, if you return false, your component will not activate.
angular.module('app').component('someComponent', {
template: 'this is an inline component template',
$canActivate: function ($timeout) {
return $timeout(function(){
return true;
}, 2000);
}
});
Use another hook named $routerOnActivate to read the next and previous routes. If you are interested in the params of a route, use next.params which is an object that will always have the parameters that were passed to that route. e.g. next.params.id where id is the parameter that was passed to the requested route.
Use $canActivate using TypeScript:
I've written a post regarding how to use write AngularJS components in TypeScript and have some drafts to use router lifecycle hooks that I'll publish today. Till then, here is the code to use some hooks using TypeScript below:
class ReviewDetailsComponent implements ng.IComponentOptions {
templateUrl = 'app/components/review-details.component.html';
// function member for this hook doesn't work!!
// so either use lambda expression or function directly.
$canActivate = $timeout => $timeout(() => true, 3000);
controllerAs = 'model';
controller = ['$http', ReviewDetailsController];
}
angular.module('app').component('reviewDetails', new ReviewDetailsComponent());
The typescript code above is same as javascript code snippet above that uses $canActivate.
Unfortunately, it didn't worked when this is defined as a function member in class like $canActivate() and the generated javascript is this member defined using prototype like ReviewDetailsComponent.prototype.$canActivate.
But it works well when written using lambda expression syntax or a function directly. If you are using a class to define a component, it is good to choose lambda expression in this case.
Use $routerOnActivate using TypeScript
The linked controller in this case also uses another lifecycle hook named $routerOnActivate and this works well if defined as a function member:
interface IReviewDetailsController {
id: number;
$routerOnActivate(next, previous);
}
class ReviewDetailsController implements IReviewDetailsController {
id: number;
constructor(private $http: angular.IHttpService) { }
$routerOnActivate(next, previous) {
this.id = next.params.id;
// talk to cache or server to get item by id
// & show that on UI
}
}
Paste from my comment as requested
What about perform your check on the loading on the page ? This would run perfectly in an angular.run.
And if you want to handle session expiration, you can add an interceptor to all requests and watch for a 401 response.

automatic class binding of es6 spread operator to constructor

I'm working with angular, jspm, and es6. I'm working with a base class to inject dependencies onto the constructor and automatically register themselves on 'this'.
This is actually a pattern that exists in React when you extend the base component class. I found this guy's little shortcut method here: http://www.newmediacampaigns.com/blog/refactoring-react-components-to-es6-classes
I am looking for a way to do this with Angular, using es6 Classes to bind the injected dependencies to the constructor's "this".
class baseClass {
constructor(...injections) {
this._bind(injections)
}
_bind(injections) {
injections.forEach( (injection) => {
this[injection.name] = injection;
});
}
}
class DiClass extends baseClass {
constructor($q, SomeAngularFactory) {
super($q, SomeAngularFactory);
}
}
This obviously doesn't work (injection.name is not a thing, i know)... but it almost does. My question is how do i get the "name" of the injected function or object. In this example, the _bind function just gives you the raw object or function... i don't know how to get "$q" or "SomeAngularFactory" as a string.
You can kind of get that by using "Object.getOwnPropertyNames(...injections)", but not inside the _bind function.
Thanks for any feedback or ideas you have.
You could do something like this:
class baseClass {
constructor(injections) {
Object.assign(this, injections);
}
}
class DiClass extends baseClass {
constructor($q, SomeAngularFactory) {
super({ $q, SomeAngularFactory });
}
}
Obs.: Like classes, Object.assign is an ES2015 feature and it merges the second (parameter) object into the first one.
In AngularJS this injectable arguments works only on development mode. If you go to production and minify your scripts, all injectables should be saved into special property. You can learn more about it in the angular docs. Here how it works with angular controllers
function MyController($q, SomeAngularFactory) {
}
MyController.$inject = ['$q', 'SomeAngularFactory']
Angular 2.0 will be powered by TypeScript and will use annotations to describe injectable variables. For now in anguar 1.3 you still can add static property $inject
class DiClass extends baseClass {
constructor($q, SomeAngularFactory) {
super($q, SomeAngularFactory);
}
}
DiClass.$inject = ['$q', 'SomeAngularFactory'];
Angular has a built in method, $injector.annotate(fn), which it uses for dependency injection, and which allows you to get arguments of the function you pass to it. You could utilize it to get the names of all constructor() parameters, and then use $injector.get() to retrieve them (which should be fine performance-wise, since they are cached).
Here's a link to answer in another thread, and a direct link to a fiddle with a demo.

Resources