AngularJS using UI-JQ with Two-Way Data Binding - javascript

Good Evening,
I have a small issue when I am trying to create a wizard form using jQuery Steps inside AngularJS. I can pass data into the plugin, but I cannot get the plugin to send data back to my controller. Here is how I have it setup:
jqConfig
myApp.value('uiJqConfig', {
steps: {}
};
Conroller
myApp.controller('MainCtrl', function ($scope) {
$scope.user = {
name = "",
address = ""
};
$scope.wizardOptions = {
onStepChanging: function (event, currentIndex, newIndex) {
console.log($scope.user);
}
};
HTML Template
<form ui-jq="steps" ui-options="wizardOptions">
<h1>Step 1</h1>
<div>
<input ng-model="user.name" />
</div>
<h1>Step 2</h1>
<div>
<input ng-model="user.address" />
</div>
</form>
I get the wizard to load up properly and everything. Now the issue is when I type into the inputs, it does not update the user object inside the controller.
Is there a way that I can accomplish this at all? Or should I look for a different solution?
Thanks,
Joshua

Related

Getting user input text with $watch in AngularJS not working on ng-if

I have a cross platform app built using AngularJS, Monaca and OnsenUI.
I have a login view that checks if the user has logged in before by querying a SQLite database. Based on whether there is data in the database, I display a welcome message to the user OR I display the login text field.
I have a method that queries the SQLite database and this is working as intended. When a entry is found on the database I set a boolean value to display the welcome message - else the boolean displays the login text field.
On my view I do the following;
<!-- User not in DB -->
<div ng-if="showLoginUI">
<div class="input">
<input type="password" placeholder="User ID" ng-model="userID"/>
</div>
</div>
I watch for changes in the text field to save the user input, but no actions are registered in the example as above. This is my method to register user action on the text field.
$scope.$watch("userID", function (newVal, oldVal)
{
if (newVal !== oldVal) {
$scope.newUserID = newVal; // Not getting here
}
});
However, when I remove the ng-if from the example above - the user events are registered. How do I keep my ng-if while still registering events on the text-filed?
I tried adding a $timeout to my $watch function but this did not help either.
It happens because ngIf directive creates a child $scope.. the problem is that you're using ng-model without the Dot Rule or controller-as-syntax.
The whole problem was already explained by Pankaj Parkar in this question.
So, to make it work, you have to create a new object, ex:
$scope.model = {};
Then, build your ng-model like this:
ng-model="model.userID"
Take a look on this simple working demo:
angular.module('app', [])
.controller('mainCtrl', function($scope) {
$scope.model = {};
$scope.$watch("model.userID", function(newVal, oldVal) {
if (newVal !== oldVal) {
$scope.model.newUserID = newVal;
}
});
});
<html ng-app="app">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular.min.js"></script>
</head>
<body ng-controller="mainCtrl">
<button type="button" ng-click="showLoginUI = !showLoginUI">Hide/Show</button>
<div ng-if="showLoginUI">
<div class="input">
<input type="password" placeholder="User ID" ng-model="model.userID" />
</div>
</div>
<div ng-if="model.newUserID">
<hr>
<span ng-bind="'Working: ' + model.newUserID"></span>
</div>
</body>
</html>

Tracking many dynamic forms with ng-model

I'm generating dozens of forms on my page. Each form has several parameters (not the same for each form). I'm generating my forms as such (simplified):
<div ng-repeat='module in modules'>
<form ng-submit='submitModule(module)'>
<div ng-repeat='arg in module.args'>
<input ng-model='models[module.name][arg.name]' id="{{ arg.name }}">
</div>
</form>
</div>
You can see I'm trying to assign a unique ng-model to each input parameter by using a two dimensional array models[module.name][arg.name].
Because I am planning on submitting this as JSON, the idea was that I could just do models[some_module] in my controller to get the full JSON, and then just post along.
Unfortunately this isn't working, when trying models['test_module'] I get undefined, instead of my object. There are no errors elsewhere in the code, I've tested extensively. The problem comes from the use of multi-dimensional arrays here which is apparently a big no-no.
How should I handle my situation? IE: several forms, several inconsistent parameters, and a need to POST every param together as JSON.
EDIT: For info, my controller looks like:
angular.module('app')
.controller('InputCtrl', function($scope, InputSvc) {
$scope.models = {};
InputSvc.list().success(function(modules) {
$scope.modules = modules;
$scope.models['test_module'] = {}
});
$scope.submitModule = function(module) {
console.log($scope.models['test_module']);
};
});
Perhaps you could give each form a controller so the model is scoped to the form instance rather than the parent:
<div ng-repeat='module in modules'>
<form ng-controller="FormCtrl" ng-submit='submitModule(module)'>
<div ng-repeat='arg in module.args'>
<input ng-model='formData[arg.name]' id="{{ arg.name }}">
</div>
</form>
</div>
Then your FormCtrl would have the submit method and the model:
angular.module('app')
.controller('FormCtrl', function($scope) {
$scope.formData = {};
$scope.submitModule = function(module) {
console.log($scope.formData);
};
});
Here is a Codepen

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

Similar functionality as in Dev HTTP Client using AngularJS

Right now I am working with AngularJS on a web interface which should have similar behavior like Dev HTTP Client. I can't find a way how to add headers in the way like DHC does.
I'm trying to make it somehow like this, but it isn't working since array is initialized empty:
<div ng-repeat="header in headersCollection.headers">
<input ng-model="header.name" type="text"/> :
<input ng-model="header.value" type="text"/>
</div>
<button type="button" ng-click="addNewHeader()">Add</button>
Headers should be stored inside this object and be available for creating, editing and removing through web interface. Just like in DHC.
$rootScope.headersCollection = {
headers : []
}
Any idea / link / answer are highly appreciated and answered immidiately.
Thank you.
Just make an "empty" header object in the headers collection. See http://jsfiddle.net/e8MEx/
Of course you will want to throw in some validation to make sure they are values before adding another one and potentially add the ability to remove an item:
JavaScript:
var mod = angular.module("myApp", []);
mod.run(["$rootScope", function($rootScope) {
//start the array with one empty value for header
$rootScope.headersCollection = {
headers : [{name: "", value: ""}]
}
}]);
mod.controller("MainController", ["$scope", "$rootScope", function ($scope, $rootScope) {
$scope.headersCollection = $rootScope.headersCollection
$scope.addNewHeader = function () {
//push a new empty value onto the array.
$scope.headersCollection.headers.push({name: "", value: ""});
}
}]);
HTML:
<div ng-app="myApp" ng-controller="MainController">
<div ng-repeat="header in headersCollection.headers">
<input ng-model="header.name" type="text"/> :
<input ng-model="header.value" type="text"/>
</div>
<button type="button" ng-click="addNewHeader()">Add</button>
<p>{{headersCollection.headers}}</p>
</div>

Categories

Resources