AngularJS Async isolated directive is not working - javascript

I want to send an object returning from the DB to a child directive. When I am sending any kind of data in sync mode it works. However when I am doing the same thing but fetching the data from remote server - async method, the directive is getting fired before the results are coming.
This is the controller which fetched the data from the server:
app.config(function($routeProvider, $locationProvider) {
$routeProvider.when("/root/cardboards/employees/:_employee", {
templateUrl: "screens/root/cardboards/employees-detail/employees-detail.html"
});
// $locationProvider.html5Mode(true);
});
app.controller("employees-detail", ($rootScope, $scope, $location, $routeParams, Http, Cast) => {
Http.GET({
resource: `employees/${$scope._employee}`
}, (err, response) => {
if (err) return Cast.error(err);
$scope.employee = response.data; // employee's object
$scope.$apply();
});
});
This is the directive element in HTML:
<edit-employee employee="employee"></edit-employee>
And this is the edit-employee directive js file:
app.directive("editEmployee", ($rootScope, Http, Cast) => {
return {
templateUrl: "/screens/root/cardboards/employees-detail/components/edit-employee/edit-employee.html",
scope: {
employee: "="
},
link: function($scope, element, attrs) {
console.log($scope.employee); // undefined
}
}
});
I thought that when I am using the = operator it means that it's now two way binding and the directive will watch for changes and then having the functionality based on the data that will come after the request to the server.
However it doesn't work like that.
How should it work and what should I do, the standard way, to make thing working?

When <edit-employee being rendered it will try to get the employeeto do a console.log on this line
link: function($scope, element, attrs) {
console.log($scope.employee); // undefined
}
But unfortunately, at that time, the employee is still undefined since it waiting for the response from the server.
To understand more clearly about this you can initiate a $watch to watch the employee inside edit-employee directive, and whenever the HTTP is finished it will update the employee to newest value.
How should it work and what should I do, the standard way, to make thing working?
It really depends, I meet that problem once and I used an ng-if on <edit-employee ng-if='employee' which mean the edit-employee directive will be rendered after the employee is initial (!= undefine).
Another way is you watch the employee inside edit-employee directive and check if employee has value then continue the business logic

Related

Directive Update then run Query to Update Dom on Click

I've looked at more than 10 different directive questions and none have worked for me thus far. I'm trying to have my controller recognize site.selectedUnit has changed which updates selectedChannel in my custom directive. The first time works great, but after that the ng-click does not change the view info. The selectedChannel is registered and stored on my chatList controller, but the http requests are not called again.
If i click on a unit in site.html, the selectedChannel for the unit number changes in the chatList view, so I'm guessing the http requests are not being called again.
How do I add a listener to either the directive or my chatList controller so I can call the http requests again when selectedChannel changes?
Best I can tell from the code you've provided, you need to make a few changes
site.controller
Add a definition for $scope.site otherwise site.selectedUnit will only be available in the context that it is defined in the HTML via ng-click.
$scope.site = {
selectedUnit: null
};
chatList.directive
Remove the isolate scope and inject the selectedChannel property via bindToController so that it will be accessible in the controller.
.directive('chatList', function() {
return {
restrict: 'E',
bindToController: {
selectedChannel: '='
},
templateUrl: 'chatList.html',
controller: 'ChatListController',
controllerAs: 'chatList'
};
})
chatList.controller
Watch for changes to selectedChannel.id to make your $http.get() call
vm.tenants = [];
$scope.$watch(angular.bind(vm.selectedChannel.id, function() {
// make http.get call here
}))
You have other issues as well that are too many to list so here's a working plunker

$templateCache is undefined In directive though I set `{cache: $templateCache}` in $http call

I have two html pages, snippet1.html & snippet2.html. I want use them inside my directive. Because I'm going to add multiple template by using single directive.
I tried this thing with by adding html templates inside <script> tag & gave type="text/ng-template" to them like Below.
<script type="text/ng-template" id="snippet1.html">
<div>Here is Snippet one</div>
</script>
<script type="text/ng-template" id="snippet2.html">
<div>Here is Snippet two</div>
</script>
And then I use $templateCache.get('snippet1.html'). This implementation is working Fine.
But in my case I need to load them from html itself, so I decided to load template by ajax and make $http cache: $templateCache
Working JSFiddle
Run Block
myApp.run(['$templateCache','$http', function($templateCache, $http){
$http.get('snippet1.html',{ cache : $templateCache });
$http.get('snippet2.html',{ cache : $templateCache });
}]);
But inside my controller $templateCache.get('snippet1.html') is undefined.
My question is, Why it is working while i declared template inside <script>' tag & Why it don't work when I html inside$templateCachewhile making$http` ajax call?
Plunkr With Problem
Can anyone help me out with this issue? Or I'm missing anything in code.
Help would greatly appreciated. Thanks.
This is an interesting issue, and I can offer an interesting workaround and my thoughts on what is going on. I think a better solution may exist, but finding such solutions also proved to be a challenge. Nonetheless, I think the main issue is simply your console.log($templateCache.get('snippet1.html')) is returning undefined because of the race condition with your $http.get's not resolving first.
Examining the api for $templateCache, I couldn't find any sort of helpful way of knowing when templates resolve requested via ajax. To see the simple issue, run this in your directive to see some basic information about what is currently stored in your $templateCache
console.log($templateCache.info())
With the result of
Object {id: "templates", size: 0}
For observing the core of the issue, run the same JS in the directive, but with a timeout as such
setTimeout(function() {
console.log($templateCache.info())
}, 1000);
With the result of
Object {id: "templates", size: 2}
Interesting, so they're in there... but getting a handle on them is now the challenge. I crafted the following workaround to at least give us something for now. Inject $q and $rootScope into your .run function as such
myApp.run(['$templateCache', '$http', '$q', '$rootScope', function($templateCache, $http, $q, $rootScope){
$q.all([
$http.get('snippet1.html',{ cache : $templateCache }),
$http.get('snippet2.html',{ cache : $templateCache })
]).then(function(resp){
$rootScope.templateCache = resp
})
}]
);
Examining this, you'll notice I place an arbitrary var on our $rootScope as such $rootScope.templateCache for the purpose of placing a $watch on it in our directive. Then in our directive, let's call into our $templateCache when we then know there is a value on $rootScope.templateCache, indicating the $q service has resolved our promises as such
link: function(scope, element, attrs) {
scope.$parent.$parent.$watch('templateCache', function(n, o) {
if(n) {
element.append($compile($templateCache.get('snippet1.html')[1])(scope));
}
});
}
And hey look! Our template directive is correctly rendered. The hacky looking scope.$parent.$parent is because in this directive, we have isolated our scope and now need to climb some ladders to get the value defined on $rootScope.
Do I hope we can find a cleaner more consise way? Of course! But, hopefully this identifies why this is happening and a possible approach to get up and running for now. Working plunker provided below.
Plunker Link
Edit
Here is a completely different approach to solve this which involves manual bootstrapping
var providers = {};
var $injector = angular.injector(['ng']);
var myApp = angular.module('myApp', []);
$injector.invoke(function($http, $q, $templateCache, $document) {
$q.all([
$http.get('snippet1.html',{ cache : $templateCache }),
$http.get('snippet2.html',{ cache : $templateCache })
]).then(function(resp){
providers.cacheProvider = $templateCache;
angular.bootstrap($document, ['myApp']);
});
});
myApp
.controller('test',function() {
})
.directive('myTemplate', function ($templateCache, $compile) {
return {
restrict: 'EA',
scope: {
snippets: '='
},
link: function(scope, element, attrs) {
element.append($compile(providers.cacheProvider.get('snippet1.html')[1])(scope));
}
};
});
Updated Plunker
This is the expected behavior. When you include a template inside of a script tag, angular finds it during the bootstrap process and adds it to the cache before any code runs. That's why it is available in your directive.
When you use $templateCache.put() (or use $http.get to retrieve an html file as you specify in your code, angular has to use ajax to resolve the template. While the request is "in flight", the template cache doesn't know anything about it - the file is only added to the template cache after the response is received.
Since your directive runs as part of the first digest cycle (on startup), there will never be any remote files in the cache, so you get the error you see.
The "correct" way to do what you are trying to do is to not use the $templateCache directly, but rather use the $http service to request the remote template. If the original response has returned, $http will just call $templateCache.get for you. If it hasn't, it will return the same promise that the original $http request generated.
Doing it this way, there will be no requirement to use $timeout or $watch. The template will be compiled as soon as it is available using promises.
myApp.controller('test',function(){})
.directive("myTemplate", function ($http, $compile) {
return {
restrict: 'EA',
scope: {
template: '&?myTemplate',
src: '&?'
},
link: function(scope, element, attrs) {
$http.get(scope.template() || scope.src()).then(function(result) {
element.append($compile(result.data)(scope));
});
}
};
});
<my-template src="snippet1.html"></my-template>
or
<div my-template="snippet1.html"></div>
Here is a working Plunk
EDIT: Alternative without $compile and $http
myApp.controller('test',function(){})
.directive("myTemplate", function ($http, $compile) {
return {
restrict: 'EA',
scope: {
snippets: '='
},
template: 'snippet1.html',
link: function(scope, element, attrs) {
}
};
});
As to your final question (why you have to use [1] to get the html - $http service does not store only html in the cache - it stores a data structure that may contain a promise or the element (if loaded from a script tag). Since it knows what it put in, it knows how to get it out. When you short circuit things you're only guessing.
Long story short - don't use $templateCache to resolve templates yourself.
EDIT: Code from $http demonstrating the different types of data that might be stored in the cache.
if (cache) {
cachedResp = cache.get(url);
if (isDefined(cachedResp)) {
if (isPromiseLike(cachedResp)) {
// cached request has already been sent, but there is no response yet
cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult);
} else {
// serving from cache
if (isArray(cachedResp)) {
resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]);
} else {
resolvePromise(cachedResp, 200, {}, 'OK');
}
}
} else {
// put the promise for the non-transformed response into cache as a placeholder
cache.put(url, promise);
}
}

ngBind equivalent of NgModelController (or best practice)

http://plnkr.co/edit/C4mFd5MOLBD2wfm8bMhJ?p=preview
Let's take a simple example and say you want to display the value of a cookie regardless of what it is, but this could be a customer name or whatever you want. There seem to be so many options available: directives, services, directives with services, controllers - and no matter how many docs I review or blog posts I read, I still have some fundamental questions about the appropriate way to access data and then update the scope accordingly.
What's clouding my thought right now is the fact that there doesn't seem to be the equivalent of NgModelController for non ngModel capable DOM elements like span or div or anything besides user input. Basically, seeing how ngModelCtrl is utilized in the link function of a directive seems to make a lot of sense, it doesn't allow you to drown in scope soup and it nicely organizes your thoughts, but how do we achieve this decoupling with ngBind elements?
I think the answer is just 'use services', but perhaps maybe not in all cases is the thing that's gnawing at me. Suppose you want to display a very specific cookie (or a customer name) and you don't know where you want to display it, you could continually inject your custom CookieService where ever you go, but what about a specific directive that cleans things up: <specific-cookie></specific-cookie> Would we just inject our CookieService into that directive, or just access it via $cookies like we've done elsewhere.
Does the answer simply lie in whether or not you'll be accessing more than one cookie in the future? That is, if you only need one <specific-cookie></specific-cookie>, then just use $cookies in you're directive and move on with your life, or it is always appropriate to abstract away this type of call into a service, or am I just being super pedantic about understanding this.
Directive
angular-myapp.js
var app = angular.module('myApp', ['ngCookies']);
app.directive('cookie', ['$cookies', function($cookies) {
return {
scope: true,
controller: function($scope, $element, $attrs) {
$scope.cookie = $cookies[$attrs.cookie];
}
}
}]);
index.html
<div cookie="__utma">Cookie: {{cookie}}</div>
Controller
angular-myapp.js
app.controller('CookieCtrl', function($attrs, $cookies) {
this.value = $cookies[$attrs['getcookie']];
});
index.html
<a ng-controller="CookieCtrl as cookie" getCookie="__utma" href="/{{cookie.value}}">{{cookie.value}}</a>
Service
angular-myapp.js
app.controller('SomeCtrl', function($scope, CookieService) {
$scope.cookie = CookieService.getCookie('__utma');
});
app.service('CookieService', function($cookies) {
var getCookie = function(cookie) {
return $cookies[cookie];
};
return ({ getCookie: getCookie });
});
index.html
<div ng-controller="SomeCtrl">Cookie: {{cookie}}</div>
Directive with service
angular-myapp.js
app.directive('specificCookie', function(CookieService) {
return {
scope: true,
template: 'Cookie: <span ng-bind="cookie"></span>',
controller: function($scope, $element, $attrs) {
$scope.cookie = CookieService.getCookie('__utma');
}
}
});
index.html
<specific-cookie></specific-cookie>
Unless I'm misunderstanding some of your scenarios, the simplest (and proper) way to do this is to create a reusable directive that displays a cookie based on a name passed to it via its attribute.
app.directive('cookie', ['$cookies', function($cookies) {
return {
scope: {},
template: "<span>{{cookie}}</span>",
restrict: "E",
link: function(scope, element, attrs) {
attrs.$observe("name", function(newVal){
scope.cookie = $cookies[newVal];
});
}
};
}]);
The usage would be trivial (and page controller-independent):
<cookie name="__utma"></cookie>
<input ng-model="cookieName" type="text">
<cookie name="{{cookieName}}"></cookie>
the resulting DOM would be:
<span class="ng-binding">137862001.838693016.141754...</span>
<span class="ng-binding">GA1.2.838693016.1417544553</span>

Angular JS Root Scope

I am working with angular js at view layer. I need to use some global variable that will be used and modified by all the controllers method in my application:
var app = angular.module('myWebservice', ['ngCookies']).run(
function($rootScope) {
$rootScope.authToken = null; });
app.controller('UserController', function($cookieStore, $scope, $location,
$routeParams, $http, $timeout, $rootScope) {
$scope.login= function() {
$http.defaults.headers.post = {'Accept' : 'application/json',
'Content-Type' : 'application/json'};
$http.post('/myServise/api/user/login-user', {
emailId : $scope.username,
password : $scope.password
}).success(function(data, status) {
if (status == 200) {
$rootScope.authToken = data.object.authToken;
}).error(function(data, status) {
console.debug(data);});
}// Else end
};// End Login
app.controller('MerchantController', function($cookieStore, $scope, $location,
$routeParams, $http, $timeout, $rootScope) {
$scope.getRootScopeValues = function()
//$scope.getRootScopeValues = function($rootScope)
{
$scope.token = $rootScope.authToken;
console.log($scope.token);// Undefined
console.log($rootScope.authToken);// Undefined
}
});
I am new to Angular JS, I have also tried to use service but I don't know why I am not able to access global variable (authToken). Please help me I am stuck at this point ....Thanks in advance...
I am not sure how your application works but looks like you have a problem of asynchronous calls (while $http is working you are trying to access the variable which is not set yet). Please look at this answer about Asynchronous calls. Maybe this is your case? Also I should note that using $rootScope is always a bad idea. Try to avoid it. You should use service instead which is also a singleton object and always a much better and maintainable approach.
I need to use some global variable that will be used and modified by all the controllers method in my application
The best way to achieve this is using a Service, but you don't have to if you would like to leave it at the $rootScope
I have tried to use service as well but same results "Undefined"
Because the call for the authToken is asynchronous, the controller could run before the token returns from remote server.
You can add the token retrieval logic to the route definition as a 'resolve' field like this:
.config(function($routeProvider, $locationProvider) {
$routeProvider
.when('/your/route', {
templateUrl: 'book.html',
controller: 'BookController',
resolve: {
token: function() {
//return a promise with the token here
}
}
})
However, this is NOT good in your case as you will have to setup the 'resolve' for every route. If you are using AngularUI Route that supports nested routes, you can simply add the 'resolve' to the root state/route. This is what I've been using in my project.
If you are not using AngularUI Route, there's another way of doing it. You can assign the returned value of the $http.post to a variable in the $rootScope e.g. $rootScope.tokenPromise.
The returned of $http.post is a promise, you can use the 'then; method to register callbacks, and these callbacks will receive a single argument – an object representing the response.
In your controller, you will have put some extra lines, you will get the token asynchronously like this
app.controller('MerchantController', function($cookieStore, $scope, $location,
$routeParams, $http, $timeout, $rootScope) {
$rootScope.tokenPromise.then( function(token){
console.log(token); //depends on the returned data structure
} )
});
In your application 'run' block, you will have to create a promise at $rootScope.tokenPromise. you can either use $http to retrieve the token and get the promise there in the run block, or create a pending promise and retrieve the token elsewhere in other places, after which you can resolve the promise.

Angular JS: How to fetch data together collectively over one ajax call?

Use case: I have comment sections like Facebook, associated with items (e.g. photos). Pages contain multiple comment sections. I'm fetching the comments from an API after the page is loaded. The API works like:
/api/comments?forItem=3
Every comment section is a directive/web component that I reuse and they fetch their data separately over different ajax calls as expected. I want to make them fetch the data together through one $http/ajax call.
So I made the API work with multiple items for efficiency, i.e.:
/api/comments?formItem=1,2,3
Major problems I have:
When to start fetching? How can I know all the loaded Angular directives?a
How to pass the data into directives?
Or is there a better solution for this use case?
Here's the Javascript Fiddle: http://jsfiddle.net/Seregwethrin/nDq2r/27/
When I use the directive multiple times, this is what happens. It makes a call for every directive, and I want to combine those calls in a single ajax request.
My directive code already works but has the problem I described above.
app.directive('commentGroup', function () {
return {
scope: {
id: '='
},
templateUrl: '/NgDirectives/CommentGroup.html',
controller: function ($scope, $http) {
$scope.comments = [];
$http.get('/services/get-comment?id=' + $scope.id)
.then(function(data) { $scope.comments = data; });
}
};
});
I would create a factory that you inject into a directive, where the factory utilizes $http to go and get your data - I am going to edit the answer soon to address the other issue(s) you have, this shows how to inject service into directive to use data from the Ajax request
You might want to look into $evalAsync http://docs.angularjs.org/api/ng.$rootScope.Scope which can function like a setTimeout until the initial digest has finished.
HTML
<div ng-controller="myCtrl">
<my-directive>{{data}}</my-directive>
</div>
JS
var app = angular.module('myApp', []);
app.factory('myData', function ($http) {
return {
getDataAsync: function (callback) {
$http.get('/someUrl').success(callback);
}
}
});
app.directive('myDirective', ['$compile', 'myData', function ($compile, myData) {
return {
restrict: 'E',
link: function (scope, element, attrs) {
scope.data = myData.getDataAsync(function (response) {
console.log(response);
});
}
}
}]);
app.controller('myCtrl', function ($scope) {});
working example:
http://jsfiddle.net/whiteb0x/yBn66/

Categories

Resources