directive scope not binding with $resource - angularjs

I'm experiencing a very strange issue and cannot see what I'mn doing wrong...
I have a pretty simple directive:
var widgets = angular.module('widgets', []);
widgets.directive('wdgtImageThumbnail', function ($compile) {
return {
restrict: 'E',
replace: true,
templateUrl: '/utilities/widgets/wdgtImageThumbnailPrtl.html',
scope: {
item: '=',
imageSettings: '=',
gotoItem: '&',
showName: '='
}, controller: function ($scope) {
// get basePath and placeholderImage from plsConfig via controller
// and configure item.image property as required
var imageBasePath = $scope.imageSettings.imageBasePath;
var placeholderImage = $scope.imageSettings.placeholderImage;
$scope.thumbnailImage = imageBasePath + ($scope.item.image || placeholderImage);
}
};
});
Here is the markup partial for the directive:
<div data-ng-click="gotoItem()">
<div title="Click to view {{item.name}}'s details">
<h4 class="logo-label" data-ng-show="showName">{{item.name}}</h4>
<img data-ng-src="{{thumbnailImage}}" class="img-thumbnail logo-pic"/>
</div>
</div>
When I'm on the URL and refresh my browser, the directive works as required and displays item.image.
However, if I navigate away from the page to a list of "items" and then navigate back to an "item", {{item.image}} is not binding in the markup.
I have checked using Batarang and, in both cases "item" is on the directive's scope.
So for some reason, in the second scenario, the binding is not occurring. Why would this happen?
EDIT:
I'm having trouble simulating this issue in Plunker, because the issue is related to the way calls are made to our api, which isn't public yet.
However, we have established that the issue we're facing has something to do with Angular resourcesw. For some reason, when we refresh the browser, the directive's controller knows what $scope.item is, but when we navigate away from the page and then back again, $scope.item is undefined. In our test to date, we're using $resource.
This is the controller function calling our datacontext service:
function getSubscriber() {
vm.subscriber = subscriberCtx.getSubscriberById($routeParams.subscriberId);
}
and this is the function in the datacontext:
function getSubscriberById(id) {
return $resource('/api/v1/Subscribers/:id').
get({id:id});
}
vm.subscriber is the object being passed into the directive as the item attribute.
What i'm finding really strange is that if I log $scope.item form the directive in both the passing and failing cases, the object is logged and in each case the logs are identical.
So, why can the directive not see $scope.item in cases when the browser is not refreshed?
If I pass a simple json object in, the directive works perfectly, but we're struggling to ge3t it working with both $resource and restangular.

Related

unable to get $scope form name on load

I'm trying the retrieve the form name inside my angularjs component while the form is being loaded as I wanted to set the form state to dirty based on some data validations that were resolved in to the component. I'm able to access the form name once the form is completely loaded say inside a submit, however i'm unable to do that on the load how can I do that. I'm using ui.router hence the controller name is being set based on the state.
<form class="form-horizontal" name="detail.myForm">
<button ng-click="detail.submit">
</form>
app.component('myDetail', {
bindings: {
alldetails: '<'
},
templateUrl: '/app/detail.html',
controllerAs: 'detail',
controller: function ($state, $transitions, $scope) {
var detail=this;
/*validateData in the alldetails here */
$scope.detail.myForm.$setDirty(); // issue here saying undefined
detail.submit = () =>{
$scope.detail.myForm.$setPristine() //works without any issue
}
}
This happens since the DOM isn't ready on your controller's construction. You have to use the $onInit callback instead. From AngularJS docs:
$onInit() - Called on each controller after all the controllers on an element have been constructed and had their bindings initialized (and before the pre & post linking functions for the directives on this element). This is a good place to put initialization code for your controller.
Also, it'd be better to inject the ngFormController by using the require object instead of assigning it to your model.
Here's a fiddle with a working example. The relevant code is:
.component('myDetail', {
template: '<h1>Details Component</h1>',
controllerAs: 'detail',
// By requiring the form controller, angular will
// create a 'formCtrl' property on your controller with the
// ngFormController instance of the parent form.
require: {
formCtrl: '^form'
},
controller: function() {
// We can't just acces the formController here, couse it will be
// undefined, since the dom isn't ready yet. So we have to use the
// $onInit callback that will be executed by angularjs.
this.$onInit = function() {
/*validateData in the alldetails here */
this.formCtrl.$setDirty();
}
}
});

angular array item not updating on scope change

Spent a few hours on this already, sifted through numerous stack posts and blogs but can't seem to get this to make my model update. More specifically, I am trying to update an array item (ng-repeat). In the simple case below, I iterate over venues list, and upon toggling a "like" button, I update the server appropriately, and reflect the change on the venues item on the $scope.
in my search.html I have a directive:
<ion-content>
<venues-list venues="venues"></venues-list>
</ion-content>
and search controller I have:
app.controller('bleh', function(Service) {
...
$scope.venues = [{ id: 1, name: 'Venue1', like: false },{ id: 2, name: 'Venue2', like: false }];
...
});
Nothing unusual there. Then in my venues-list directive:
app.directive('venues-list', function() {
function venueListController($scope, Service) {
$scope.likeToggle = function(venue, $index) {
Service.likeVenue(venue.id, !venue.like).then(function() {
$scope.venues[$index].like= !venue.like;
});
}
}
return {
strict: 'E',
templateUrl: 'venue.html',
controller: venueListController,
scope: {
venues: '='
}
}
});
then in my venue.html I have:
<div ng-repeat="venue in venues">
<p>{{venue.name}}</p>
<button ng-click="likeToggle(venue, $index)">Like</button>
</div>
I have tried many different options:
$scope.$apply() // after updating the $scope or updating scope within apply's callback function;
$scope.$digest()
$timeout(function() { // $scope.venues[$index] .... }, 0);
safe(s,f){(s.$$phase||s.$root.$$phase)?f():s.$apply(f);}
so
safe($scope, function() { $scope.venues[$index].like = !venue.like });
I haven't yet used the link within the directive, but my venues.html template is obviously a little more elaborate than presented here.
EDIT:
Just to keep the discussion relevant, perhaps I should have mentioned - the data is coming back from the server with no issues, I am handling errors and I am definitely hitting the portion of the code where the $scope is to be updated. NOTE: the above code is a small representation of the full app, but all the fundamental pieces are articulated above.
Search Controller
Venues Service
venue-list directive and venue.html template to accompany the directive
directive controller
EDIT #2
$scope.foo = function() {
$scope.venues[0].like = !$scope.venues[0].like;
}
Just to keep it even simpler, the above doesn't work - so really, the bottom line is my items within an array are not reflecting the updates ...
EDIT #3
$scope.foo = function() {
$scope.venues[0].like = !$scope.venues[0].like;
}
My apologies - just to re-iterate what I was trying to say above - the above is changing the scope, however, the changes are not being reflected on the view.
Perhaps the issue is with your service and promise resolution.. Can you put a console.log there and see if the promise resolution is working fine? or Can you share that code bit. Also where are you checking for scope update? within directive or outside
OK after some refactoring I finally got it working.
The "fix" (if you want to call it that) to my specific problem was:
instead of passing an array of venues, I was iterating over the array on the parent controller, passing in a venue as an element attribute that would bind (two-way) on the isolated scope of the directive.
so, instead of:
<ion-content>
<venues-list venues="venues"></venues-list>
</ion-content>
I now have:
<ion-content>
<venues-list ng-repeat="venue in venues" venue="venue"></venues-list>
</ion-content>
and my directive now looks like:
app.directive('venues-list', function() {
function venueController($scope, Service) {
$scope.likeToggle = function(venue) {
Service.likeVenue(venue.id, !venue.like).then(function() {
$scope.venue.like = !venue.like;
});
}
}
return {
strict: 'E',
templateUrl: 'venue.html',
controller: venueController,
scope: {
venue: '='
}
}
});
This did the trick!

Syntax highlighted code snippet wont display with AngularJs ngBind

I used some Syntax highlighting API for highlighting code snippet for my web application.To do that i have used highlightjs .I created popup model and inside model i have put <pre> tag and when model open it should display my highlighted xml string.
HTML Code snippet
<pre id="tepXml" ><code class="xml">{{tepXml}}</code></pre>
In AngularJs controller dynamically bind the value to tepXml from server.
AngularJs controller
...$promise.then(function(data){
$scope.tepXml=data.xml;
}
But the problem was that when i open popup model my xml content is empty.nothing display anything.But when i removed <code class="xml"></code> from <pre> xml content would display with out highlighting.I referred some posts and used $compile in angularJs controller but the problem was still the same.
AngularJs controller with $compile
var target = angular.element($window.document.querySelector('#tepXml'));
var myHTML = data.xml;
target.append( $compile( myHTML )($scope) );
If someone knows where i went wrong please point me out.
Plunker
The quick answer is to do:
$promise.then(function(data){
$scope.tepXml=data.xml;
// Call highlight api
$timeout(function() {
$('pre#tepXml code').each(function(i, block) {
hljs.highlightBlock(block); //or whatever the correct highlightjs call is.
});
});
The more Angular way of doing things is to call a jQuery function from Angular is to write a Directive. Something like this:
.directive("highlightCode", function($interval) {
return {
restrict: "A",
scope: { highlightCode: "=" },
link: function(scope, elem, attrs) {
$scope.$watch('highlightCode', function() {
$(elem).find('code').each(function(i, block) {
hljs.highlightBlock(block); //or whatever the correct highlightjs call is.
});
}
}
});
Used like this:
<pre id="tepXml" highlight-code="tepXml"><code class="xml">{{tepXml}}</code></pre>

Confused refreshing Angular custom directive / communicating between adjacent directives

I am two weeks into Angular. I have watched several Pluralsite videos and read several post and this has resulted in great progress but also some confusion. I want to notify one directive of some change in another directive. Then refresh the directive. In other words it needs to go back to the server with the selection from the first and pull the appropriate data.
I have read about eventing and things like $watch() but then I have seen others say to avoid watch and to use $emit and $on. I have even seen one article say to use transclusion.
I have access to Pluralsight and other resources. I will self educate if someone could just point my nose in the right direction.
My directive markup html:
<div class="col-md-3">
<dashboard-main-nav></dashboard-main-nav>
</div>
<div class="col-md-3">
<dash-select ng-show="vm.isDashSelectionVisible">Selections</dash-select>
</div>
My app declaration: NOTE I know I need to get the parm from scope but not sure how...
(function ()
{
"use-strict";
...snip controller setup etc..
.directive("dashboardMainNav", function () {
return {
restrict: "E",
templateUrl: "/Navigation/GetDashItems",
scope: true
}
})
.directive("dashSelect", function () {
return {
restrict: "E",
templateUrl: "/Navigation/GetDashSelections/:" + $scope.??,
scope: true
}
});
})();
routingController:
(function () {
...snip...
function routingController($http, $scope) {
var vm = this;
var isDashSelectionVisible = false;
var dashSelectionId = 0;
$scope.LetterSearch= function (dashSelId) {
vm.isDashSelectionVisible = true;
vm.dashSelectionId = dashSelId;
alert("Letters Clicked: " + dashSelId);
}
}
})();
Rendered HTML:
<dashboard-main-nav>
....snip....
Letters
</dashboard-main-nav>
..... snip.....
<dash-select>
Numbers
</dash-select>
I am not showing the $routeProvider config that wires up the routingController as that works fine. I just need to get that custom directive to grab the parm from scope..refresh then update the dom.
Thank You for your patience and knowledge sharing.

Angularjs: redraw directive with different template when user logs in

in our general layout we have a sidebar which contains a few user details like name and thumbnail. but also a few different things.
we had the idea to load the sidebar with a directive, and inside of that, have 2 templates, 1 when logged in and 1 when logged out.
to display and not display the user details.
however, we have a directive called sidebar working with either of the 2,
App.directive('sidebar', ['UserService', '$compile', function(User, $compile) {
var getTemplate = function() {
console.warn(User.isLogged);
var templateUrl = "#sidebar" + ((User.isLogged) ? '_loggedin' : '') + "_template";
console.log('requesting template: [%s]', templateUrl);
return $(templateUrl).html();
};
return {
restrict: 'A',
link: function link(scope, element, attrs) {
var tmpl = getTemplate();
element.html(tmpl);
element.replaceWith($compile(element.html())(scope));
},
template: getTemplate()
};
}]);
and we load our user details in a service
App.factory('UserService', [function userService() {
var User = {
isLogged: false,
username: ''
};
return User;
}]);
the login form accepts this UserService as dependency, and sets it's isLogged to true
but how can I let the directive redraw the sidebar, when the isLogged is changed?
are we doing this the right way?
There is a stage known as the compile phase where angular
walks the DOM to identify all the registered directives in the
template. For each directive, it then transforms the DOM based on the directive’s
rules (template, replace, transclude, and so on), and calls the compilefunction
if it exists. The result is a compiled templatefunction, which will invoke the link
functions collected from all of the directives.
Basically, you can't conditionally load templates - it will compile the first one you give it. If you want to dynamically render your view, you might use two divs with the ng-show directive inside the template:
<div ng-show="user.isLogged">// logged in content</div>
<div ng-show="!user.isLogged">// logged out content</div>
You might think you need to inject a factory into your directive - I have just tried this and it does not work! I believe this is because directives can only set up one and two-way binding's with those on the scope chain. With that in mind, I brought the user object into application wide scope:
App.controller("AppCtrl", function($rootScope, UserService) {
$rootScope.user = UserService;
})
And then used an open scope from the directive:
app.directive("sidebar", function() {
return {
restrict: "A",
template: '<div>' +
'<div ng-show="user.isLogged">Logged In</div>' +
'<div ng-show="!user.isLogged">Logged Out</div></div>',
}
})
The user object here is being found through scope chaining. Because the directive does not have an isolated scope (I haven't declared a scope property), it is finding it through the scope chain - right up to the root scope. This is of course a "global" in a sense, and could easily be hidden:
$scope.user = somethingElse
Not the prettiest solution, but it does the job.
You could also conditionally manipulate the DOM in the link function, or better yet - off load this kind of logic to the router making use of nested templates and resolve.

Resources