angularjs: injecting async service into directive with $resource - angularjs

I'm very much trying to get my head around angularJS and directives still.
I have an existing REST service that outputs JSON data as follows (formatted for readability):
{"ApplicationType":
["Max","Maya","AfterEffects","Nuke","WebClient","Other"],
"FeatureCategory":
["General","Animation","Compositing","Management","Other"],
"FeatureStatus":
["Completed","WIP","NotStarted","Cancelled","Rejected","PendingReview"],
"BugStatus":
["Solved","FixInProgress","NotStarted","Dismissed","PendingReview"]}
I then have a service (which appears to be working correctly) to retrieve that data that I wish to inject into my directive.
(function () {
'use strict';
var enumService = angular.module('enumService', ['ngResource']);
enumService.factory('Enums', ['$resource',
function ($resource) {
return $resource('/api/Enums', {}, {
query: { method: 'GET', cache: false, params: {}, isArray: false }
});
}
]); })();
My intentions are to use the data from the json response to bind to html selector 'options' for the purposes of keeping the data consistent between the code behind REST service and the angular ( ie. the json data is describing strongly typed model data from c# eg. Enum.GetNames(typeof(ApplicationType)) )
projMgrApp.directive('enumOptions', ['Enums',
function (Enums) {
return {
restrict: 'EA',
template: '<option ng-repeat="op in options">{{op}}</option>',
scope: {
key: '#'
},
controller: function($scope) { },
link: function (scope, element, attrs) {
scope.options = Enums.query(function (result) { scope.options = result[scope.key]; });
}
};
}
]);
the intended usage would be to use as follows:
<label for="Application" class="control-label col-med-3 col">Application:</label>
<select id="Application" class="form-control col-med-3 col pull-right">
<enum-options key="ApplicationType"></enum-options>
</select>
which would then produce all of the options consistent with my c# enums.
In this case it appears the directive is never being called when the tag is used.
Note. I assume the factory is working fine, as i can inject it into a separate controller and it works as anticipated.

1) I guess projMgrApp is the main module. Have you included enumService as dependency to this module?
angular.module('projMgrApp',['enumServices'])
Otherwise your main module won't be aware of the existence of your service.
2) Are you aware how the directives are declared and used. When you declare
projMgrApp.directive('EnumOptions', ['Enums', function (Enums) {}])
actually should be used in the html code as:
<enum-options></enum-options>
I m not quite sure about the name but it should start with lowercase letter like enumOptions
3) I don't know why you use this key as attribute. You don't process it at all. scope.key won't work. You either have to parse the attributes in link function (link: function(scope, element, attributes)) or create isolated scope for the directive.
Add as property of the return object the following:
scope : {
key:'#' //here depends if you want to take it as a string or you will set a scope variable.
}
After you have this , you can use it in the link function as you did (scope.key).
Edit:
Here is a working version similar (optimized no to use http calls) to what you want to achieve. Tell me if I'm missing anything.
Working example

If you get the baddir error try to rename your directive name to enumOptions according to the doc (don't forget the injection):
This error occurs when the name of a directive is not valid.
Directives must start with a lowercase character and must not contain leading or trailing whitespaces.

Thanks to Tek's suggestions I was able to get this fully functioning as intended. Which means now my angularJS + HTML select tags/directives are completely bound to my APIs enums. I know down the line that I am going to be needing to adjust add to these occasionally based on user feedback, plus I use these enums to represent strongly typed data all over the app. So it will be a big time saver and help reduce code repetition. Thanks to everyone that helped!
The service and directive that I used is copied below, in case other people starting out with Angular run into similar issues or have similar requirements.
Service:
(function () {
'use strict';
var enumService = angular.module('enumService', [])
.service('Enums', ['$http', '$q', function ($http, $q) {
return {
fetch: function () {
var defer = $q.defer();
var promise = $http.get("/api/Enums").then(function (result) {
defer.resolve(result);
});
return defer.promise;
}
}
}]);})();
Directive:
angular.module('projMgrApp').directive('enumOptions', ['Enums', function (Enums) {
return {
restrict: "EA",
scope: {
key: "#"
},
template: "<select><option ng-repeat='enum in enumIds' ng-bind='enum'></option><select>",
link: function (scope) {
Enums.fetch().then(function (result) {
scope.enumIds = result.data[scope.key];
});
}
};
}]);

Related

get scope in directive

everybody. I am new to AngularJS and find it very interesting, but I am a bit unclear about the following situation.
app.controller("myCtrl", ['$scope', '$http', '$filter', function ($scope, http, filter)
{
$http({
method: CTHocUri,
url: 'get',
async: true,
}).then(function (response) {
$scope.CTH = response.data; //response.data=two Object
})
}])
app.directive("myCustom1",['$http', function ($compile,$http) {
return {
link: function (scope, element, attr) {
console.log(scope.CTH); // I can't get... scope.CTH=undefined
}
}])
I can't get value scope.CTH. ??
There is a VERY simple way to SEE what the issue is:
In your html, merely surround your directive with an ng-if conditional based on CTH:
<span ng-if="CTH">
<my-custom-1></my-custom-1>
</span>
That's it.
What this does is that your directive will only be born/instantiated when CTH is set to non-null/non-undefined, i.e. when $http returns asynchronously. With this, your code will work. As such, there is no need for watching or broadcasting for this type of simple serialization of asynchronous events when you can simply leverage Angular's built-in '$watch's.
NOTE 1: I do not know what your architecture is and am not suggesting what you need to do. I am merely showing you why your code won't work and how you have been caught in a simple asynchronicity trap.
NOTE 2: I assume your directive is 'as -is'. In other words you have access to the parent's scope (i.e. the controller's scope). If your directive's scope were isolated (i.e. you had a scope:{..(attrs)..} defined in the directive) you will not have 'simple' access to the parent scope. Your code will be different--eg you can pass bits and pieces of your scope to the directive attrs. However, the ng-if will still work since it is on the controller's scope.
I hope this helps.
The directive and the controller are two completely different entities. If it helps you can think of them as different classes. They will not share the same scope.
You could create an isolated scope on the directive and pass the CTH variable into it. Conceptually something like this:
app.directive("myCustom1",['$http', function ($compile,$http) {
return {
scope { cth : "=" },
link: function (scope, element, attr) {
console.log(scope.cth);
}
Then in your HTML, do something like this:
<div ng-controller="myCtrl">
<my-Custom1 cth="CTH">
</div>
when the directive initializes, the scope.CTH is still not initialized since its initialization accurses inside an $http call.
one way to overcome this is to broadcast and event from the controller and catch it from inside the directive. see this plnkr and angularjs scope's docs
app.controller('MainCtrl', function($scope, $timeout) {
$scope.name = 'World';
$timeout(function() {
$scope.test = "test";
$scope.$broadcast('MyEvent')
}, 500);
});
app.directive('test', function() {
return {
link: function(scope, elm, attr) {
scope.$on('MyEvent', function() {
console.log(scope.test);
})
}
}
})

How do you require angular template html?

When creating custom directives, if you want to put your view/template html in separate files, Angular seems to load the template from a public URL, making an HTTP request for it.
How do you include this template HTML inline while keeping it in a separate file?
With ES6 nothing is impossible:
import yourTemplate from 'path/to/file';
// inject $templateProvider in start point of your application
$templateProvider.put('path/to/file', yourTemplate);
$templateProvider at its own is simple $cacheFactory instance, where you can put any html, by any key, that can be used in ng-include or simply used in your directive as shown below:
//Directive
import yourTemplate from 'path/to/file';
that is used within directive configuration:
...,
controller: xxx,
template: yourTemplate,
link: () => { ... }
...
Use directive to fix this
HTML
<div custom-include url="{{url}}"></div>
Directive
app.directive('customInclude', ['$http', '$compile', '$timeout', customInclude]);
function customInclude($http, $compile, $timeout) {
return {
restrict: 'A',
link: function link($scope, elem, attrs) {
//if url is not empty
if (attrs.url) {
$http({ method: 'GET', url: attrs.url, cache: true }).then(function (result) {
elem.append($compile(angular.element(result.data))($scope));
//after sometime we add width and height of modal
$timeout(function () {
//write your own code
}, 1, false);
});
}
}
};
}
"inline" in a separate file is contradicting itself.
Since angular is client side code, loading a template that is stored in it's own separate file will always require a http request.
The only way to avoid these requests is to add them inline with your other code, so not in a separate file.

Scope is not updated with JSON call, but updated with simple integer assign in the custom directive

I have 2 different values assigned to my $scope. One is $scope.summary which is a JSON object, and other one is $scope.myNumber which assigned to 3.
From controller:
myService.loadSummary(function(data) {
$scope.summary = data;
$scope.$apply();
});
$scope.myNumber = 3;
myService contains a function called loadSummary which works perfectly and I can see in debug $scope.summary gets the data that it supposed to get. I also see that $scope.myNumber is assigned to 3 in debug.
When I go to my directive in debug it only shows myNumber value. There is nothing about $scope.summary. It is undefined.
From directive:
define([ 'angular', 'directives-module', 'text!./tmpl/table.html' ], function(
angular, directives, tmpl) {
directives.directive('summaryTable', function($timeout) {
return {
restrict: "E",
template: tmpl,
link: function(scope, elm, attrs) {
require([ 'jquery', 'datagrids' ], function($) {
var data = scope.summary;
What might be the reason for it?
I have a very limited knowledge in Angular.
I think this is happens because your update should be inside $apply() expression, like this:
myService.loadSummary(function(data) {
$scope.$apply(function(){
$scope.summary = data;
});
});
See this nice article. Quote:
You do need to use it ($apply()) if you are going to run code in a new turn. And only if that turn isn’t being created from a method in the AngularJS library. Inside that new turn, you should wrap your code in $scope.$apply().

AngularJS - how to focus on an element via a controller

I know the question has been asked multiple time, but I can't seem to find anywhere how to focus to an element from within a controller. What is the best approach? Would it be better to do a directive? But if so, then how would I call it within my controller? Or is it better to create a service then?
What I already have and works properly from within HTML code is a directive:
.directive('ngxFocus', ['$timeout', function($timeout) {
return function(scope, element, attr) {
$timeout(function () {
element.focus();
}, 10);
};
}])
Can I call directive within controller? I'm still learning AngularJS and I'm a bit confused on what the best approach is in this case. I really want to do it via the controller, at the moment I use a simple 1 line of jQuery to focus, but yeah it's not the Angular way and so I'd like to go with the correct way.
Note
To be more specific with an example, let say I have 10 inputs in the HTML and let say that inside the execution of a function (defined in the controller), I want to focus on 1 of the multiple inputs directly from the function (again this is all declared inside the controller). I would rather not write anything inside the HTML code, if possible, but instead call a focus function or something that will focus to the input I chose. I know I could write it simply in jQuery with $('input12').focus(); but I want to know how to do it the AngularJS way. All the answers I get so far are based on writing a Directive, that also equals to writing something inside the HTML, isn't there any other way???
Example
For more explicit example of my form... I have a first input connected to a Yahoo web service (stock market), this input filled by the user will hold a stock quotes symbol that can be anywhere in the world and then the user will choose (from a dropdown) his Bank account... now from there, my controller will check that the stock quotes market is in the same currency as the user's bank account (ex.: GOOG is US currency, if user's account is in $CAD, it will fail because GOOG is in $USD). If currency isn't the same, I want to advise my user and to do so I would seriously prefer to focus on the field so he could change his symbol if he made an error.
I you're trying to work with elements in controller, be sure you're going wrong, the controller's target in to bind data received from services to view, not to manipulate view.
If you want to focus on an element with route change:
app.directive('focuser', ['$location', function ($location) {
return {
restrict: 'A',
link: function ($scope, $element) {
$scope.$watch(function () {
//simply focus
$element.focus();
//or more specific
if ($location.$$url == '/specific/path') {
$element.focus();
}
});
}
};
}]);
I've made this directive:
app.directive('rfocus',function(){
return {
restrict: 'A',
controller: function($scope, $element, $attrs){
var fooName = 'setFocus' + $attrs.rfocus;
$scope[fooName] = function(){
$element.focus();
}
},
}
});
It adds to controller's $scope function to set focus on element. Name of the function is based on value given in attribute.
Using: <input type="text" rfocus="Input1"/> will create function setFocusInput1() which you can use in your controller.
Here is the fiddle: http://jsfiddle.net/aartek/2PJMQ/
I've recently started to learn Angular, too, but hopefully I can provide you a different way of approaching this.
I've been using some basic jQuery to focus, too, so in that regard, I can't really help you. However, with regard to calling a directive within a controller, I can't find any articles that say "yes, you can", or "no, you can't". I know that you can declare a controller within a directive, though, so you miiiiight be able to do something like this:
.directive('ngxFocus', function() {
return {
restrict: 'A',
controller: //do your controller stuff here
}])
I know it's an old question, but here's an other approach, where you set to true a variable in the controller, and that's this action that set the focus to your element.
Try this:
myAngularModule.directive('goFocus', ['$timeout', function ($timeout) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.$watch(attrs.goFocus, function (newValue) {
if (newValue) {
$timeout(function () {
element[0].focus();
}, 100);
}
});
element.bind("blur", function (e) {
$timeout(function () {
scope.$apply(attrs.goFocus + "=false");
}, 10);
});
element.bind("focus", function (e) {
$timeout(function () {
scope.$apply(attrs.goFocus + "=true");
}, 10);
});
}
}
}]);
In HTML template:
<input go-focus="focusvar1" type="text" ng-model="mytext1" />
<input go-focus="focusvar2" type="text" ng-model="mytext2" />
<input go-focus="focusvar3" type="text" ng-model="mytext3" />
<button ng-click="doFocus()">OK</button>
In javascript angular controller:
myAngularModule.controller('myController', function () {
var self = this;
self.doFocus = function () {
// do some logic and focus your field
self.focusvar2 = true;
};
});

AngularJS do link function after $http response returned

I need to perform a link function in a directive after an http response returns. The idea is something like this:
<input type="text" my-field>
<script>
angular.module("mine")
.controller ('myCtrl', function ($scope) {
$http.get("/my/service").success(function (data, status, headers, config) {
// OK, done with the query... now I know my field name to bind to. Somehow
// I have to get it down to the link function below...
});
})
.directive ('myField', function ($compile) {
return {
link: function (scope, element, attrs) {
var my_field = attrs.myField;
element.removeAttr('my-field');
// Somehow figure out the field here in ngFieldSpec
element.attr('ng-model', ngFieldSpec);
$compile(element)(scope);
};
});
</script>
Here, I need to bind the input field to an element of the response, but I don't know what the element will be called until I get the response. But when I run it, the directive's link runs before $http gets done: the actual sequence is
$http.get starts
directive's link function run
$http.get returns success
I'm somewhat familiar with $q, but am not sure how that would be used to do what needs to be done. BTW, I have shown only one input field invoking the myField directive, but there are potentially many of them on the page, and they all need the same information.
Edited to add more information in response to request:
I have a service that returns a JSON data structure. I do not know in advance exactly what that data structure will look like, but I can figure it out and match the fields up with my page's input fields. I'm attempting to do this matching up in the link function. I'm glad to do it somewhere else; I could do it in the $http.success function, but that would be doing DOM manipulation in a controller; and my understanding is that DOM manipulation should only be done in a directive.
Here's what my HTML needs to look like:
<input type="text" my-field="[MY_EXTENSION_NAME]myFieldName">
<input type="text" my-field="[MY_EXTENSION_NAME]myFieldName2">
<input type="text" my-field="[MY_EXTENSION_NAME_2]myFieldName">
The response from the server will be something like:
{
realField1: "Diddly",
realField2: "Squat",
extensions: [
{
name: "MY_EXTENSION_NAME",
fields: [
{ name="myFieldName" value="Foo" },
{ name="myFieldName2" value="Bar" }
]
},
{
name: "MY_EXTENSION_NAME_2",
fields: [
{ name="myFieldName" value="Baz" },
{ name="myFieldName2" value="Buz" }
]
}
]
}
The server's response may vary because:
There may be any number of extensions ("MY_EXTENSION_NAME", etc.)
The extensions may be returned in any order
There may be any number of fields
The fields may be returned in any order
The whole problem here is I want to convert "[MY_EXTENSION_NAME]myFieldName" into the ng-model "model.extensions[0].fields[0].value. However, I am now thinking transforming the data into a canonical form during reading will be easier, so ng-model can just be "model.my_extension_name.myFieldName".
It is not clear what you are trying to achieve (I'm pretty sure there will be some better way), but you could do it like this:
1.
Define a promise in your scope:
app.controller('myCtrl', function ($http, $scope) {
$scope.model = {
promise: $http.get('/my/service'),
myField01: 'Hello, world, from 01 !',
myField02: 'Hello, world, from 02 !',
myField03: 'Hello, world, form 03 !'
};
});
2.
From your HTML, reference that promise in order to pass it to your directive:
<input type="text" my-field="model.promise" />
3.
Get this promise into your directive's isolate scope:
app.directive ('myField', function ($compile) {
return {
scope: { promise: '=myField' },
...
4.
In your link function, register a callback for when the promise gets resolved (i.e. you get a response to your request) and do all necessary manipulation:
...
link: function (scope, elem, attrs) {
scope.promise.success(function (data) {
elem.removeAttr('my-field');
elem.attr('ng-model', 'model.' + data.fieldName);
$compile(elem)(scope.$parent);
});
}
See, also, this short demo.

Resources