Bind a controller $scope to objects in service - angularjs

I'm creating a multi-step wizard using angularjs. The steps in the wizard could potentially change by actions the user takes. I'm storing the steps in a navigation service. I want the navigation controller to respond to changes in the navigation steps (which occurs in the service). How do I do this? The below code does not work. Also I am using ui-router for the wizard steps, I'm wondering if I'm going about this the wrong way and the steps should be stored in the parent $scope rather than in a service. But examples I've found online suggest using a service.
Here's a jsfiddle I wrote to exhibit the behavior (http://jsfiddle.net/uLytj/16/)
Wizard Navigation Service:
angular.module("myApp").factory("wizardNavigationSvc", [function () {
var service = {};
var steps = []
var currentStep = {};
var currentStepIndex = 0;
return {
init: function() {
steps = [{ state: "wizard.options", display: "Options", isActive: true, isFirstStep: true },
{ state: "wizard.newmodel", icon: "glyphicon-plus", otherActions: ["Add Another Model"] },
{ state: "wizard.other", display: "Other" },
{ state: "wizard.customer", display: "Customer Info" },
{ state: "wizard.shipping", display: "Shipping Info" },
{ state: "wizard.review", display: "Review", isLastStep: true }];
currentStep = steps[0];
currentStepIndex = 0;
},
steps: steps,
currentStep: currentStep
};
}])
Wizard Navigation Controller:
myApp.controller('wizardNavigationCtrl', ['wizardNavigationSvc', '$scope', '$state', function (wizardNavigationSvc, $scope, $state) {
wizardNavigationSvc.init();
$scope.steps = wizardNavigationSvc.steps;
$scope.currentStep = wizardNavigationSvc.currentStep;
}])
Wizard Navigation view:
<div>
<ul class="nav nav-tabs wizard-tabs">
<li ng-repeat="step in steps"
ng-class="step.isActive ? 'active': ''">
<a ui-sref="{{step.state}}">
<span ng-if="step.icon" class="glyphicon" ng-class="step.icon"></span>
<span ng-if="!step.icon">{{step.display}}</span></a>
</li>
</ul>
</div>

looking at your jsfiddle, its not defined because the service is not returning anything
Ex: on one of the lines (in your service) you have:
steps: steps,
currentStep: currentStep,
these should be:
steps: function(){return steps),
currentStep: function(){return currentStep},

I do not understand why this worked or was needed. I had to put my scope variables inside the service within another object. Here is the fiddle: http://jsfiddle.net/uLytj/25/
And here is the applicable code:
myApp.service("wizardNavigationSvc", [function () {
var navigation = {
steps: [],
currentStep: {},
currentStepIndex: 0
};

Related

Ng-repeat - "Are you sure to delete ?" from a modal

I'm retrieving a list of objects (item) from a Django API.
my_app.factory('list_of_items', function($resource) {
return $resource(
'/api/petdata/') });
Then I display everything in a html page within a ng-repeat:
<div ng-controller="ModalDemoCtrl">
<div ng-repeat="item in items | filter:{display:'1'} | orderBy: 'item_name'">
<div class="box box-widget widget-user">
{{ item.pet_name }}{% endverbatim %}
<button type="button" class="btn btn-box-tool" ng-click="askDelete(item)" href="#"><i class="fa fa-times"></i></button>
</div>
<div>
Everything's fine so far.
Then I want the user to be able to delete one of the item by clicking on the button from the html page.
What means deleting here :
1. Update the API database by changing the property "display:1" to "display:0".
2. Remove the item from the ng-repeat.
I want to make a "Are you sure" modal to confirm the delete process.
This is the askDelete function.
angular.module('djangular-demo').controller('Ctrl_List_Of_Pets', function($scope, $http, $window,$filter,list_of_pets,pet_by_id,$uibModal) {
$scope.items = list_of_items.query()
$scope.askDelete = function (idx,item,size,parentSelector) {
// console.log("PET",$scope.pet_to_be_undisplayed);
var parentElem = parentSelector ?
angular.element($document[0].querySelector('.modal-demo ' + parentSelector)) : undefined;
var modalInstance = $uibModal.open({
animation: true,
ariaLabelledBy: 'LOL',
ariaDescribedBy: 'modal-body',
templateUrl: "myModalContent.html",
controller: function($scope) {
$scope.ok = function() {
modalInstance.close();
};
$scope.cancel = function() {
modalInstance.dismiss('cancel');
};
},
size: size,
appendTo: parentElem,
resolve: {
}
});
modalInstance.result.then(function() {
reallyDelete(item);
});
};
var reallyDelete = function(item) {
$scope.entry = items_by_id.get({ id: item.id }, function() {
// $scope.entry is fetched from server and is an instance of Entry
$scope.entry.display = 0;
$scope.entry.$update({id: $scope.entry.id},function() {
//updated in the backend
});
});
$scope.items = window._.remove($scope.items, function(elem) {
return elem != item;
});
};
});
What works :
Updating the DB works with a PUT request (code hasn't been provided).
What doesn't work :
Removing the item from the ng-repeat never works. Or it throws me an error like here because it doesn't know window._.remove or it doesn't know $scope.items. It depends from what I try. Or the modal close and there is no update of the ng-repeat list, no refresh and every items remain whereas the PUT request to update worked.
I read every article on scope inheritance and I think I didn't make any mistake here but I'm might be wrong. I've been struggling for too long so I post here !
Would you suggest anything to make it work ?
Thank you for your rime.
First:
$scope.askDelete = function (idx,item,size,parentSelector) receives the item index, the item, size, and parent selector... and you are calling ng-click="askDelete(item)"
I assume you are attempting to pass the item, but in askDelete you are receiving as first parameter the index (maybe you should do ng-click="askDelete($index)"?)
Second:
In reallyDelete why are you removing the items array like this:
$scope.items = window._.remove($scope.items, function(elem) {
return elem != item;
});
?
IMHO, it would be a much cleaner code if we just do:
$scope.items.splice(idx, 1) //<- idx would be the idx of the entry in the items
You may want to take a look at Splice

angular js ng-class shows expression as class instead of processing it

I'm trying to make highlighted menu items by using angular js. I've read this question and tried implementing the anwser, but instead of angular evaluating the expression, it just shows it as the class name. I don't know what's going on.
I have the menu items listed as JSON, and the iterate trough it with ng-repeat. Once the list items are created, I want the angular to add a class of 'active', if the location url is the same as the link.href attribute of a menu item (it's a json attribute, not the html one).
Here's the relevant html:
<div class="header" ng-controller="NavbarController">
<ul>
<li ng-repeat="link in menu" ng-class="{ active: isActive({{ link.href }}) }"><a ng-href="{{ link.href }}">{{ link.item }}</a>
</li>
</ul>
</div>
and my controller:
.controller('NavbarController', function ($scope, $location) {
// navbar links
$scope.menu = [
{
item: 'PTC-Testers',
href: '#/PTC-Testers'
},
{
item: 'articles',
href: '#/articles'
},
{
item: 'PTC sites',
href: '#/sites'
},
{
item: 'account reviews',
href: '#/account_reviews'
},
{
item: 'forum',
href: '#/forum'
},
{
item: 'contact us',
href: '#/contact'
},
{
item: 'login',
href: '#/login'
}
]; // end $scope.menu
$scope.isActive = function (viewLocation) {
return viewLocation === $location.path();
};
});
This is the navbar part of a bigger project, and I tried only inserting the relevant code. If you need further info to understand the question properly, please let me know.
It should be ng-class="{'active' : isActive(link.href)}"
You didn't end the curly brace in ng-class and its better to put class name inside quotes

Persist data across views AngularJS with vs-google-autocomplete

I am building my first Angular App, which allows users to search a database of vendors based on a number of criteria. One of these criteria is their location and the distance to the vendor.
I have used the vs-google-autocomplete directive, which has been a great way to link into the google maps API. However, I have a problem around the persistency of this location data across views in the app.
To explain how it works at the moment. A user inputs their search criteria, a snapshot of all potential vendors is shown based on the users search criteria (this has been achieved through a custom directive). The user can then click to read more about a vendor, this takes them to the vendors full page (with more details etc.). It is at this stage that the location data is lost.
I have already created a service to store the information from other aspects of the search query, which is persisting just fine (except for the location data).
It appears that the problem is that the location input field automatically resets when a user leaves the page, thereby resetting the location field to null. But, I can't see where it is doing that in the code.
Does anyone have any thoughts about how to tackle this?
Code Below (unfortunately I could not get the custom directive actually working in JSFiddle, but hopefully it gives a good idea of what I am trying to achieve):
App.factory("searchquery", function() {
return {};
});
App.controller('searchController', ['$scope', '$routeParams', '$rootScope', 'searchquery',
function($scope, $routeParams, $rootScope, searchquery) {
//Create a search query object
$scope.searchquery = {};
//Inject the searchquery service to ensure data persistency when in searchController pages
$scope.searchquery = searchquery;
//DEAL WITH GOOGLE AUTOCOMPLETE ADDRESSES
//Restricts addresses to South Africa only
$scope.options = {
componentRestrictions: {
country: 'ZA'
}
};
//Creates an object to store the address information and parses it into its consitutent parts
$scope.searchquery.address = {
name: '',
components: {
streetNumber: '',
street: '',
city: '',
state: '',
countryCode: '',
country: '',
location: {
lat: '',
long: ''
}
}
};
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<form name="Search">
<!-- LOCATION -->
<!-- ask the user to specify their location -->
<div class="form-group">
<label for="address">Address</label>
<input vs-google-autocomplete="options" ng-model="searchquery.address.name" vs-street-number="searchquery.address.components.streetNumber" vs-street="searchquery.address.components.street" vs-city="searchquery.address.components.city" vs-state="searchquery.address.components.state"
vs-country-short="searchquery.address.components.countryCode" vs-country="searchquery.address.components.country" vs-latitude="searchquery.address.components.location.lat" vs-longitude="searchquery.address.components.location.long" type="text" name="address"
id="address" class="form-control">
</div>
</form>
Thanks!
Jack
I don't know your location data but you can persist data across views with two ways:
Service as you mentioned
Nested views
Please have a look at the demo below or here at jsfiddle.
The service data are not shared in the demo but that's also possible by injecting it in other views.
If the data are asynchronous you should have a look at resolve of ui-router. Then the data are loaded before the controller of the view is called.
angular.module('demoApp', ['ui.router'])
.config(AppConfig)
.factory('demoService', DemoService)
.controller('demoController', DemoController);
function AppConfig($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider.state('home', {
url: '/',
template: 'HELLO from home route <input ng-model="text"/>'+
'<div ui-view=""></div'
})
.state('home.profile', {
url: '/profile',
template: 'Hello from profile <input ng-model="text"/>'
})
.state('otherView', {
url: '/other',
controller: 'demoController',
/*resolve: {
text: function(demoService) {
console.log(demoService);
return demoService.get();
}
},*/
template: 'Hello from other scope <input ng-model="text" ng-change="updateService(text)"/>'
});
}
AppConfig.$inject = ['$stateProvider', '$urlRouterProvider'];
function DemoService() {
return {
_text: 'I am stored in service.',
set: function(text) {
this._text = text;
},
get: function() {
return this._text;
}
}
}
function DemoController($scope, demoService) {
$scope.text = demoService.get();
$scope.updateService = function(newValue) {
console.log(newValue);
demoService.set(newValue);
};
}
DemoController.$inject = ['$scope', 'demoService'];
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.15/angular-ui-router.js"></script>
<div ng-app="demoApp">
<div ui-view=""></div>
<a ui-sref="home">home</a>
<a ui-sref="home.profile">profile</a>
<a ui-sref="otherView">other</a>
</div>

Why don't comment upvotes work? thinkster.io, angularjs tutorial, mean, incrementUpvote()

I'm following the thinkster angular js tutorial here:
https://thinkster.io/angulartutorial/mean-stack-tutorial/
and clicking on the upvotes icon next to a comment fails to increment the upvote count.
In a ui-router template(included inline in index.html), an ng-click calls addUpvote()--but addUpvote() is defined by a controller specified in another state. So the only way the template can call addUpvote() is if the template's associated state/controller somehow inherits from the other state/controller. And in fact, according to the ui-router docs here:
https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views
the way the states are defined in my app.js,
app.js:
app.config([
'$stateProvider',
'$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('home');
$stateProvider
.state('home', {
url: '/home',
templateUrl: '/home.html',
controller: 'MainCtrl' }
)
.state('posts', {
url: '/posts/{id}',
templateUrl: '/posts.html',
controller: 'PostsCtrl' }
);
}]);
...the second state inherits from the first state Edit: A more careful reading of the docs makes me believe that isn't actually true; I think that in order for the posts state to inherit from the home state, I would have to change the name of the posts state to home.posts. However, when I try that, it breaks something else: the comments link next to a post no longer works. In any case, I don't think writing:
$stateProvider
.state(...)
.state(...);
creates any inheritance between the two states.
Here is the first state's controller:
app.controller('MainCtrl', ['$scope', 'posts', function($scope, posts) {
$scope.posts = posts.posts;
/*
[
{title: "post1", upvotes: 3, link: ''},
{title: "post2", upvotes: 5, link: ''},
{title: "post3", upvotes: 1, link: ''}
];
*/
$scope.addPost = function() {
var title = $scope.title;
if (title && (title.trim().length > 0) ) {
$scope.posts.push({
title: title,
upvotes: 0,
link: $scope.link,
comments: [
{author: 'Joe', body: 'Cool post!', upvotes: 0},
{author: 'Bob', body: 'Great idea, but everything is wrong!', upvotes: 0}
]
});
}
$scope.title = '';
$scope.link = '';
}
//*****HERE IS THE FUNCTION THAT IS NOT BEING CALLED*****
$scope.addUpvote = function(post) {
console.log("in addUpvote");
++post.upvotes;
}
}]);
As far as I can determine, my template should be able to call addUpvote(). However, the console.log() line doesn't output anything when I click on the upvote icon next to a comment, so obviously addUpvote() is not being called.
Here is a portion of index.html:
<body ng-app="flapperNews">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<ui-view></ui-view>
</div>
</div>
....
....
....
<script type="text/ng-template" id="/posts.html">
<div class="page-header">
<h3>
<a ng-show="post.link" href="{{post.link}}">
{{post.title}}
</a>
<span ng-hide="post.link">
{{post.title}}
</span>
</h3>
</div>
<div ng-repeat="comment in post.comments | orderBy:'-upvotes'">
<span class="glyphicon glyphicon-thumbs-up"
ng-click="addUpvote(comment)"></span>
{{comment.upvotes}} - by {{comment.author}}
<span style="font-size:20px; margin-left:10px;">
{{comment.body}}
</span>
</div>
</script>
</body>
Towards the bottom of that template, ng-click calls addUpvote(). Why isn't addUpvote() being called.
The explanation was a little further down in the docs:
https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views#scope-inheritance-by-view-hierarchy-only
Scope Inheritance by View Hierarchy Only
Keep in mind that scope properties only inherit down the state chain
if the views of your states are nested. Inheritance of scope
properties has nothing to do with the nesting of your states and
everything to do with the nesting of your views (templates).
It is entirely possible that you have nested states whose templates
populate ui-views at various non-nested locations within your site. In
this scenario you cannot expect to access the scope variables of
parent state views within the views of children states.
And:
https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views#what-do-child-states-inherit-from-parent-states
What Do Child States Inherit From Parent States?
Child states DO inherit the following from parent states:
Resolved dependencies via resolve
Custom data properties
Nothing else is inherited (no controllers, templates, url, etc).
...
...
Child states will inherit data properties from parent state(s), which
they can overwrite.
$stateProvider.state('parent', {
data:{
customData1: "Hello",
customData2: "World!"
}
})
.state('parent.child', {
data:{
// customData1 inherited from 'parent'
// but we'll overwrite customData2
customData2: "UI-Router!"
}
});
So to make the comment upvotes work, I did this:
app.controller('PostsCtrl', [
'$scope',
'$stateParams',
'posts',
function($scope, $stateParams, posts) {
$scope.post = posts.posts[$stateParams.id];
$scope.addUpvote = function(comment) {
console.log('comment upvote');
++comment.upvotes;
}
}]);
I had the same error, and when I ran the url in cURL, it returned req.comment.upvote is not a function at the top of the error stack.
C:\Users\ddavis>curl -X PUT http://localhost:3000/posts/57b380f850eaca0c1233a3b7/comments/57b42f4b305a94e40c75970d/upvote
req.comment.upvote is not a function
...
In my case, when I defined an upvote function in the comments.js model like below it worked:
var mongoose = require('mongoose');
var CommentSchema = new mongoose.Schema({
body: String,
author: String,
upvotes: {type: Number, default: 0},
posts: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Post'}]
});
CommentSchema.methods.upvote = function(cb) {
this.upvotes += 1;
this.save(cb);
};
mongoose.model('Comment', CommentSchema);
C:\Users\ddavis>curl -X PUT http://localhost:3000/posts/57b380f850eaca0c1233a3b7/comments/57b42f4b305a94e40c75970d/upvote
{"_id":"57b42f4b305a94e40c75970d","body":"another comment","author":"user","__v":0,"posts":[],"upvotes":3}

AngularJS Modal Dialog with File Upload Control not working

I am using AngularJS Modal Service. http://fundoo-solutions.github.io/angularjs-modal-service/
I setup it in a simple way
Button to open a Model
<div data-ng-controller="contest as vm">
<a class="btn btn-primary" data-ng-click="vm.createFileUploadDialog()">Upload Image</a>
</div>
Inisde Controller I have a function defined createFileUploadDialog and expose it from my viewModel.
vm.createFileUploadDialog = createFileUploadDialog;
vm.uploadme = {};
vm.uploadme.src = "";
function createFileUploadDialog() {
createDialog('/app/templates/fileuploadDialog.html', {
id: 'filuploadDialog',
title: 'Upload Contest Image',
backdrop: true,
success: { label: 'Upload', fn: uploadSuccess },
cancel: { label: 'Cancel' },
});
}
function uploadSuccess() {
console.log(vm.uploadme);
//need to call to the backend
}
And inside "fileUploadDialog.html" I have a simple markup
<div>
<input type="file" fileread="uploadme.src" />
</div>
"fileread" is a directive which return back the src of the File. Now the problem I have
As you can see I am doing console.log inside "UploadSuccess", in response I am getting the result "Object {src: ""}",
It looks like the Modal values not capture inside controller. But If I do the same with $rootScope, it logs out the File that need to upload. So, how can I access the value without using $rootScope? Please suggest
PS:
I am not define separate controller for Modal, want to use the same controller that treats my view.
** Modals scope is not the same as your controller scope!**
if you want to see your Controller scope inside of your modal and manupulate it , you're gonna have to use resolve inside of your modal markap like this :
createDialog('/app/templates/fileuploadDialog.html', {
id: 'filuploadDialog',
title: 'Upload Contest Image',
backdrop: true,
success: { label: 'Upload', fn: uploadSuccess },
cancel: { label: 'Cancel' },
resolve:{
controllerscope:function(){
return $scope;
}
}
});
And now , inside of your modal controller you can inject :** controllerscope ** and use it , also data binding works well like this :
app.controller('modalcontroller',function($scope,controllerscope){
// no you have access to your controller scope with **controllerscope**
})
So go and have a look at your modal plug in wich you are using and search for resolve and controller
thats it

Resources