Strange binding permanence between controllers - javascript

I've got a project in which you write a note in a formulary. Then, you submit that note into an information container (now it's just an array for testing purposes, but it's intended to be a DB later).
The formulary has the following controller:
app.controller('controlFormulario', ['$scope', 'SubmitService', function($scope, submitService) {
$scope.formData = {
"titulo":"",
"texto":"",
"fecha": new Date()
};
$scope.submit = function() {
var temp = $scope.formData;
submitService.prepForBroadcast(temp);
}
// more things we don't need now
... which is bound to this part of the DOM, which is added into it, via a directive:
<form ng-controller="controlFormulario as formCtrl">
<div class="element">
<div class="form-group" ng-class="{'has-error': formData.titulo.length > 50 }">
<label for="inputTitulo">Título</label>
<input type="titulo" class="form-control" id="inputTitulo" ng-model="formData.titulo">
<span ng-show="formData.titulo.length > 50" id="helpBlock" class="help-block">El título no puede exceder los 50 caracteres.</span>
</div>
<div class="form-group">
<label for="inputTexto">Texto</label>
<textarea class="form-control" id="inputTexto" ng-model="formData.texto"></textarea>
</div>
<div class="form-group">
<label for="fecha">Fecha</label>
<input type="fecha" class="form-control" id="fecha" ng-model="formData.fecha" disabled>
</div>
<div class="form-group" >
<button class="btn btn-primary" style="height:35px;width:100px;float:right;" id="submit"
ng-disabled="isDisabled()" ng-click="submit()">
Enviar
</button>
</div>
</div>
<div class="note" ng-show="formData.titulo.length > 0">
<div class="title" ng-model="formData.titulo" class="title">{{formData.titulo | limitTo:50}}</div>
<div class="text" ng-model="formData.texto" class="text">{{formData.texto}}</div>
<div class="date" ng-model="formData.fecha" class="date">{{formData.fecha | date}}</div>
</div>
</form>
This is my directive (I don't think it's really needed, but just in case):
app.directive('formulario', [function() {
return {
restrict: 'E', // C: class, E: element, M: comments, A: attributes
templateUrl: 'modules/formulario.html',
};
}]);
I use a service for passing the data between the previous controller, and the note controller (which controls the note objects of the array). This is the service:
app.factory('SubmitService', function($rootScope) {
var data = {};
data.prepForBroadcast = function(recvData) {
data.data = recvData;
this.broadcastItem();
};
data.broadcastItem = function() {
$rootScope.$broadcast('handleBroadcast');
};
return data;
});
... and I receive it in this part of my note controller:
app.controller('noteController', ['$scope', 'SubmitService', function($scope, submitService) {
var nc = this;
$scope.$on('handleBroadcast', function() {
nc.pruebaNota.push(submitService.data);
$scope.formData.titulo = "";
$scope.formData.texto= "";
$scope.formData.fecha = new Date();
});
// more things, the array, etc...
Ok. This should work, and it does, but something strange happens: as you can see, the preview note is binded with ng-model to the form. That works great, ok. But when I submit the form, the new note object keeps bound to the form (so if I delete the form text, the note appears in blank, and if I write something, it gets automatically updated both in the preview note, and the new note), when there isn't any relation between them. The new note, which appears dynamically on the screen, shouldn't be bound to anything.
Am I doing something wrong? Some help would be really nice!

You are forgetting something really important. Memory address. So, the rought idea is something like: imagine that $scope.formData is in the address 123123. You first create a temp var pointing to 123123 then you send it to the service and the service holds the same address 123123 into data.data.
Then in your second controller you say: hey, I want to work with that data.data (AKA your data in 123123) you have SubmitService.
Now when you modify $scope.formData again, you are updating what you have in that 123123 and everything that is "looking" into that address will be updated.
That is the rough idea. To point it simple, you're modifying the same piece of information everywhere.
See it here: http://plnkr.co/edit/zcEDQLHFWxYg4D7FqlmP?p=preview
As a AWolf suggested, to fix this issue, you can use angular.copy like this:
nc.pruebaNota.push(angular.copy(submitService.data));

Related

Unable to validate mutli-select input with AngularJS

I'm new to Angular, and I struggle with validating a multi select input with ngOptions attribute.
I want the field to be required, so the user must chose at least one option. However the validation methods Angular have simply doesn't work in my case, Here's what I've tried:
<form name="products" novalidate>
<div class="form-group">
<label for="select-product">Chose product/s</label>
<select id="select-product" name="selectedProduct" class="form-control"
required
multiple
ng-model="selectedProduct"
ng-options="product.name for product in products">
</select>
</div>
<button class="btn btn-primary pull-left" ng-click="products.selectedProduct.$valid && goToChildrenId()">Next</button>
</form>
Even if I chose a product and I can see that the form class switch from .ng-invalid to .ng-valid, the function goToChildrenId() doesn't run.
Also, if I add {{products.selectedProduct.$valid}} at the bottom, when I refresh the page I can see "false" for a second but it disappear, why?
If it's relevent, this is the controller:
adminCheckout.controller('productsCtrl', function($scope, $http, wpMiniapps, $location, $rootScope){
$scope.products = [];
var url = wpMiniapps.routeUrl("getProducts");
$http.post(url, {}).then(function(res){
$scope.products = res.data.data;
}, function(err){
console.log(err);
});
$scope.$watch('selectedProduct', function (newVal) {
$rootScope.globalData.products = newVal;
});
$scope.goToChildrenId = function(){
$location.path('/select-children');
};
});
I searched the web but nothing seems to work in my case.
I will really appreciate any help.
You have a variable name collision which is causing the problem: the form is named products and this ends up as a $scope variable. But it collides with $scope.products = [] from the controller. Simply renaming the form to, e.g., productsForm solves the problem:
<form name="productsForm" novalidate>
...
<button class="btn btn-primary pull-left" ng-click="productsForm.selectedProduct.$valid && goToChildrenId()">Next</button>
</form>

Event in Angular Controller Only Fires Once

I'm trying to implement an Angular version of an autocomplete textbox. I found some working examples, but none seem to exhibit the behavior I'm getting.
The autocomplete functionality itself works fine. When a suggested item is selected, the control correctly handles the selection. Subsequent uses of the control (typing in the autocomplete box, making a selection) fail to engage the 'selected' event/condition, although the autocomplete bit continues to work.
Here's my module & controller:
var app = angular.module('myapp', ['angucomplete-alt']); //add angucomplete-alt dependency in app
app.controller('AutoCompleteController', ['$scope', '$http', function ($scope, $http) {
//reset users
$scope.Users = [];
$scope.SelectedUser = null;
//get data from the database
$http({
method: 'GET',
url: '/UserRoleAdministration/Autocomplete'
}).then(function (data) {
$scope.Users = data.data;
}, function () {
alert('Error');
})
//to fire when selection made
$scope.SelectedUser = function (selected) {
if (selected) {
$scope.SelectedUser = selected.originalObject;
}
}
}]);
I'm guessing the problem is in there, but I don't know what it is. I include the bit from my view below, although there doesn't seem to be much there to fuss with:
<div class="form-group">
<div ng-app="myapp" ng-controller="AutoCompleteController">
<div angucomplete-alt id="txtAutocomplete" pause="0" selected-object="SelectedUser" local-data="Users" search-fields="RegularName" placeholder="People Search" title-field="RegularName" minlength="2" input-class="form-control" match-class="highlight"></div>
<!--display selected user-->
<br /><br />
<div class="panel panel-default" id="panelResults">
<div class="panel-heading"><h3 class="panel-title">Manage Roles for {{SelectedUser.RegularName}}</h3></div>
<div class="panel-body">
<div class="row">
<div class="col-md-2">
<img src="~/Images/avatar_blank.png" width="100%" />
</div>
<div class="col-md-4">
<div class="row">
<div class="col-md-4">Selected User:</div> <div class="col-md-6">{{SelectedUser.RegularName}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
Any help would be appreciated!
UPDATE
After fixing the mistake Yaser pointed out, I wasn't getting any information regarding the selected object. So I set the page to output the entire object, rather than the specified fields, and I noticed I was getting information about the selected object, and on subsequent attempts as well.
So this worked: {{SelectedUser}}
This did not: {{SelectedUser.Department}}
Then I looked at the object and noticed its format. It had "title" and "description", and description had inside it the key/value pairs.
So now this works: {{SelectedUser.description.Department}}
And that's it.
Because the first time you are setting $scope.SelectedUser as a function but inside that you are rewriting the same one with an object. so next time it is not a function any more, try to rename the function:
$scope.setUser = function (selected) {
if (selected) {
$scope.SelectedUser = selected.originalObject;
}
}

When to use a directive, when a service and when a controller in angularjs?

I am a bit confused on when to use what in angularjs. I know the basic concept of controller, service/factory and directive but I'm not sure what to use in my case.
Scenario: A form that allows a user to post a link. The form itself requests some information about the link from an external service and presents it immediately to the user. Posting is possible via the API of a NodeJS app (not that that matters). The form should be reusable so I want the code to be DRY. I don't like the use of ng-include since directives seem to be the way to go.
So far I have a factory to deal with requesting information (linkservice) and a factory to deal with creating posts (posts). I then use a directive with it's own controller to display the form and handle user actions. But I'm not sure if I should move the content of the directives' controller into a normal controller or even a service, since directives shouldn't deal with requesting data (as I understand). Or maybe this is already the right way.
The Directive
// The form to publish a new post
myModule.directive('postForm', [
'linkservice',
'posts',
'$state',
function(linkservice, posts, $state){
return {
templateUrl : '/js/app/views/partials/post-form.html',
controller: function ($scope) {
$scope.analyzeURL = function() {
$scope.filtered_url = $scope.link.url.match(/(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,#?^=%&:/~+#-]*[\w#?^=%&/~+#-])?/gmi);
if($scope.filtered_url !== null) {
linkservice.extractURL($scope.filtered_url).then(function(res) {
var website_info = res.data;
$scope.link = {
title: website_info.title,
description: website_info.description,
medium: website_info.provider_name,
medium_thumbnail_url: website_info.favicon_url,
url: $scope.filtered_url[0]
}
// Image
if(website_info.images.length > 0 && website_info.images[0].width >= 500) {
$scope.link.thumbnail_url = website_info.images[0].url;
} else { $scope.link.thumbnail_url = null; }
// Keywords
$scope.link.keywords = [];
if(website_info.keywords.length >= 2) {
$scope.link.keywords[0] = website_info.keywords[0].name;
$scope.link.keywords[1] = website_info.keywords[1].name;
}
$scope.show_preview = true;
});
}
},
// addPost
$scope.addPost = function(){
if(!$scope.post || $scope.post.text === '' || !$scope.link || $scope.link.url === '') { return; }
posts.create({
post: $scope.post,
link: $scope.link
}).success(function() {
delete $scope.post;
delete $scope.link;
});
}
}
}
}]);
The template
<form ng-submit="addPost()" style="margin-top:30px;">
<h3>Add a new Post</h3>
<div class="form-group">
<input type="text"
class="form-control"
placeholder="URL"
ng-model="link.url" ng-change="analyzeURL()"></input>
</div>
<div class="form-group">
<textarea type="text"
class="form-control"
placeholder="Description / TLDR"
ng-model="post.text" ></textarea>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Post</button>
</div>
<div class="form-group">
<input type="hidden" ng-model="link.title"></input>
<input type="hidden" ng-model="link.description"></input>
<input type="hidden" ng-model="link.thumbnail_url"></input>
<input type="hidden" ng-model="link.medium"></input>
<input type="hidden" ng-model="link.medium_thumbnail_url"></input>
<input type="hidden" ng-model="link.keywords"></input>
</div>
<div class="lp-container" ng-show="show_preview">
<span class="lp-provider"><img src="{{link.medium_thumbnail_url}}" class="lp-favicon"> {{link.medium}}</span>
<h2 class="lp-title">{{link.title}}</h2>
<div class="lp-description">{{link.description}}</div>
<img class="lp-thumbnail" ng-show="link.thumbnail_url" src="{{link.thumbnail_url}}">
<div class="lp-keywords">
<span ng-repeat="kw in link.keywords" class="lp-keyword">{{kw}}</span>
</div>
</div>
</form>
The best way to do it, is to keep in mind that Angular is a MVVM-like framework.
Your directives define the view, how to print data, events, etc.
Your services are singletons so they are the best place to store data and to put all data management (web services requests, etc). As they will be instanciated only once, your data won't be duplicated.
Your controllers are instanciated each time you link them to a directive (ng-controller etc.). So you should avoid to store data here. Controllers should be used as link between services and directives. They could contains low-level data check etc, then call the services.
In your example you can simplify you code by moving your controller in another place to avoid to mix it all. Ex: Here your directive directly depends to linkservice when it's only the controller which needs it.

How to add multiple items to a list

I'm building an app where users can add items to a list and I decided, for the sake of learning, to use Angular (which I'm very new to). So far, I've been able to successfully add a single item to that list without any issues. Unfortunately, whenever I try to add more than one without a page refresh, I get an error - specifically a "Undefined is not a function."
I've spent more time than I care to think about trying to resolve this issue and I'm hoping an expert out there can give me a hand. Here's what I have so far:
Controllers:
angular.module('streakApp')
.controller('StreakController', function($scope) {
// Removed REST code since it isn't relevant
$scope.streaks = Streak.query();
// Get user info and use it for making new streaks
var userInfo = User.query(function() {
var user = userInfo[0];
var userName = user.username;
$scope.newStreak = new Streak({
'user': userName
});
});
})
.controller('FormController', function($scope) {
// Works for single items, not for multiple items
$scope.addStreak = function(activity) {
$scope.streaks.push(activity);
$scope.newStreak = {};
};
});
View:
<div class="streaks" ng-controller="FormController as formCtrl">
<form name="streakForm" novalidate >
<fieldset>
<legend>Add an activity</legend>
<input ng-model="newStreak.activity" placeholder="Activity" required />
<input ng-model="newStreak.start" placeholder="Start" type="date" required />
<input ng-model="newStreak.current_streak" placeholder="Current streak" type="number" min="0" required />
<input ng-model="newStreak.notes" placeholder="Notes" />
<button type="submit" ng-click="addStreak(newStreak)">Add</button>
</fieldset>
</form>
<h4>Current streaks: {{ streaks.length }}</h4>
<div ng-show="newStreak.activity">
<hr>
<h3>{{ newStreak.activity }}</h3>
<h4>Current streak: {{ newStreak.current_streak }}</h4>
<p>Start: {{ newStreak.start | date }}</p>
<p>Notes: {{ newStreak.notes }}</p>
<hr>
</div>
<div ng-repeat="user_streak in streaks">
<!-- Removed most of this for simplicity -->
<h3>{{ user_streak.fields }}</h3>
</div>
</div>
Could you post the html of StreakController too? Your solution works fine in this fiddle:
http://jsfiddle.net/zf9y0yyg/1/
.controller('FormController', function($scope) {
$scope.streaks = [];
// Works for single items, not for multiple items
$scope.addStreak = function(activity) {
$scope.streaks.push(activity);
$scope.newStreak = {};
};
});
The $scope inject in each controller is different, so you have to define the "streaks" in FormController.
Your problems comes from :
.controller('FormController', function($scope) {
// Works for single items, not for multiple items
$scope.addStreak = function(activity) {
$scope.streaks.push(activity);
^^^^^^
// Streaks is initialized in another controller (StreakController)
// therefore, depending of when is instantiated StreakController,
// you can have an error or not
$scope.newStreak = {};
};
});
A better design would be to implement a StreakService, and to inject that service in the controller you need it. Of course, initializing $scope.streaks in FormController will make your code work, but that's not the responsibility of FormController to initialize this data.
I assume FormController is a nested controller of StreakController, so they share the same scope.
if that works for single object, it should work for mulitiple objects, the problems is you can't just use push to push an array of object to the streaks, you can for loop the array and add them individually or use push.apply trick. I thought the reason of Undefined is not a function. is because the Stack.query() return an element instead of an array of elements so, the method push doesn't exists on the $scope.streaks.
http://jsbin.com/jezomutizo/2/edit

Angular $scope undefined on form submit

I don't know what I am doing wrong here, can anyone see what I am doing wrong? I just want to pass my form inputs (in this case a date picker) to a controller function, but everything I try to access just shows up as undefined!
Here is my form
<form ng-submit="showRefinedGameMarkers(refinements)">
<div class="list">
<div class="datepickers">
From Date:
<label class="item item-input">
<input ng-controller="DatepickerCtrl"
type="text"
placeholder="Pick date"
ng-model="refinements.from_datepicker"
name="from_datepicker"
ng-click="opendateModal()"
readonly>
</label>
</div>
Here is my controller class
.controller('MapController', function($scope, $ionicLoading, gameFactory, $compile) {
$scope.showRefinedGameMarkers = function(refinements) {
$scope.refinements = refinements;
console.log($scope.refinements);
...
}
})
Everything I have tried is just showing up as undefined, so annoying! Any help would be greatly appreciated thank you
You dont need pass refinements to your submit function, because its bound using ng-model and angular will change $scope.refinements for you.
Also,you need to initialize your refinements variable in your controller.
$scope.refinements = {}
Add $scope.refinements = {}; at the top of your controller.

Categories

Resources