I have the following resource set up in my AngularJS app:
var phonecatServices = angular.module('phonecatServices', ['ngResource']);
phonecatServices.factory('Phone', ['$resource',
function($resource){
return $resource('phones/:phoneId.json', {}, {
query: {method:'GET', params:{phoneId:'phones'}, isArray:true}
});
}]);
And by default they are listed like so via the controller:
var phonecatControllers = angular.module('phonecatControllers', []);
phonecatControllers.controller('PhoneListCtrl', ['$scope', 'Phone',
function($scope, Phone) {
$scope.phones = Phone.query();
$scope.orderProp = 'age';
}]);
As you can see I already have a filter but I want a query string based search so that I can use the History to get back to the results after choosing a record, etc.
First question I have here, is that this list actually uses phones.json for the data but I don't specify this anywhere in the code... so how does this work? Presume some magic is happening but I can't see it.
So back to the initial question, I have built the following search controller:
phonecatControllers.controller('SearchCtrl', ['$scope', '$http', '$location', 'Phone',
function ($scope, $http, $location, Phone) {
$scope.keywords = $location.search()['q'];
// The function that will be executed on button click (ng-click="search()")
$scope.search = function() {
$location.path('phones').search('q', $scope.keywords);
$scope.phones= Phone.query($scope.keywords);
}
}]);
So it should use the query string to find the results. But how do I do this? It seems very transparent how the data is pulled from the JSON file. The method should also list data if the query string is there on page load... so perhaps this should be combined into one controller for both the list and search?
The code above doesn't filter the JSON data when I do a search... so the query string isn't being used... but I presume it's because I don't understand how the app knows to look in the 'phones.json' file?
The HTML for the filtering and search:
<div class="control-group">
<label class="control-label">Filter:</label>
<div class="controls">
<input ng-model="$parent.query">
</div>
</div>
<div class="control-group">
<label class="control-label">Sort by:</label>
<div class="controls">
<select ng-model="$parent.orderProp">
<option value="name">Alphabetical</option>
<option value="age">Newest</option>
</select>
</div>
</div>
<hr/>
<div ng-controller="SearchCtrl">
<form class="form-search" ng-submit="search()">
<label>Search:</label>
<input type="text" name="q" ng-model="keywords" class="input-medium search-query" placeholder="Keywords...">
<button type="submit" class="btn" ng-click="search()">Search</button>
</form>
</div>
The HTML for the list:
<ul class="phones">
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp"
class="thumbnail phone-listing">
<img ng-src="{{phone.imageUrl}}">
{{phone.name}}
<p>{{phone.snippet}}</p>
</li>
</ul>
OK so to ensure that I understand correctly:
you already have a as-you-type search using Angular's filter filter
this search is implemented using an input bound to a variable named query
you are trying to persist the search terms when changing view and coming back
you want to persist it in the URL
You don't need the new controller or the new input. In the PhoneListCtrl controller, add $scope.query = $location.search().q. This will read the q parameter from the URL and write the value in query, which will automatically fill the input and filter your results.
To do the reverse (ie writing the value of query to the URL), add a ng-change attribute to your input (<input ng-model="query" ng-change="queryChanged()"/>), and add the corresponding function to your controller:
$scope.queryChanged = function () {
$location.search('q', $scope.query)
}
This function will be executed every time the query changes and it will update the URL accordingly. Everything should work out now. See this fiddle.
As a side note, persisting the query in the URL might not be the best idea, as it will remain visible is the user's browser after they have left the search view. You could use session storage, for example.
Related
I am stuck with minor issue.
I want to render list of data from api call, on input type change event.
What my app does is, when user start typing in input type, onchange event is triggered, based on that, I want to return data from api call. So that I can give autosuggestion and let user select that data.
Basically its same like how google place autosuggest work. I just want to customize that in my own list view.
<ion-content ng-controller="Googleplacesuggestion">
<h1>Search</h1>
<label class = "item item-input">
<input type ="text" ng-model="search" ng-change="getGooglePlaceSuggestionAutocomplete(search)"
class="search-query" id="address_search" placeholder="Search">
</label>
<ul class="unstyled">
<li ng-repeat="data in returnedData">
<span> {{ data }} </span>
</li>
</ul>
this is my controller
.controller('Googleplacesuggestion',function($scope,$http){
$scope.getGooglePlaceSuggestionAutocomplete = function($scope){
console.log($scope);
$scope.returnedData = [
{text:'learn angular', done:true},
{text:'build an angular app', done:false}
];
Thanks
Better to post your code but here I would suggest based on my understanding. onchange event callback is from Javascript and is an Asynchronous from which Angular is unaware of so in your callback, wrap the code inside the function in the $timeout(function() {}) method call which will tell Angular that something has changed.
For example:
<input type="text" onchange="valueChanged()" />
In your controller:
$scope.valueChanged = function() {
$timeout(function() {
/// your code
});
};
Another best option which is recommended to use ng-change directive of Angular, in this way you don't have to wrap your code inside the $timeout service.
Also, there are various libraries out there which provide your autocomplete code. https://angular-ui.github.io/bootstrap/#/typeahead is one of them if you are using BootStrap.
search something about debouncing this is technique to wait some time before doing some job, or super easy way to check string length
I am building a cross platform app using Onsen UI, Monaca and AngularJS.
I have 2 screens. The first is where a user have to enter a Vehicle ID which calls an API. Based on the input Vehicle ID, I want to populate the API URL with this value and on the next screen display a list of all options that are returned as a JSON object based on the Vehicle ID.
E.g the API call looks as follows: mymadeupdomain/api/vehicle.php?id=109
Where 109 would be the ID the user enters to determine what will be displayed on the detailed vehicle screen.
I have hard-coded the values and I can read back the JSON object that is returned, but I cant seem to send the values for when the user enters them.
vehicle-id.html form that gets the vehicle id from the user
<form class="login-form" style="text-align: center" name="myForm">
<section style="padding: 8px">
<input type="text"
class="text-input--underbar"
required
minlength="3"
maxlength="10"
ng-model-options="{ debounce : 800 }"
placeholder="Vehicle ID / Fleet Number"
ng-model="fleetIDSearch" >
</section>
</form>
app.js is the controller for handling the form check
angular.module("myApp", ['onsen']).controller("vehicleController", function($scope, $http)
{
// Watch for changes on the vehicle ID screen
$scope.$watch('fleetIDSearch', function()
{
fetchFleetDetails();
});
$scope.fleetIDSearch = "";
function fetchFleetDetails()
{
$http.get("http://mymadeupdomain/api/vehicle.php?id=" + $scope.fleetIDSearch).success(function(data)
{
$scope.fleetIDs = data;
});
}
// Returns a list of all Vehivle Checks associated with Vehicle ID
$http.get("http://mymadeupdomain/api/getfleetchecks.php?fleetid=" + $scope.fleetIDSearch).success(function(data) // NOT WORKING HERE
{
$scope.checkItemDescriptions = data;
});
});
How do I get values entered in $scope.fleetIDSearch = ""; in the first screen and pass them to the API URL in the second screen?
I want to display a list of all checks associated with the ID as per below - working when API URL is hard coded
vehicleCheck.html
<ul class="list">
<li class="list__item" ng-repeat="checkItemDescription in checkItemDescriptions">
{{checkItemDescription.checkitemdesc}}
<label class="switch switch--list-item">
<input type="checkbox"
class="switch__input"
checked >
<div class="switch__toggle"></div>
</label>
</li>
</ul>
You have multiple choice in your case, at multiple levels.
If you want to simply share data between controllers, you can use Factories
Another solution is to:
Manage a ng-model on your first page
When the form is submitted, redirect to an url looking like /content/:id
Manage your request (fetching remote data) inside of the router resolver
Take the result directly in the other controller of the other page
Can I use ng-model to build up an object over several views?
For instance, say in view1 I have
<input ng-model='myObject.firstName'>
And in view2 I have
<input ng-model='myObject.lastName'>
And in view3 I have
<input ng-model='myObject.email'>
The idea being you could hit a submit button in the last view, and return the object somewhere.
My initial approach to this is to have a service which declares an empty object, then have functions in the service which allow the controllers using the service to add their view input to that object, then return the object.
However I feel like this is quite a roundabout way of doing it!
If anyone could point me in the right direction I would really appreciate it.
You can use a service for that. Here an example with 3 controllers sharing the same object using 3 directives ng-model. Each controller modify the tested.value property, but you can use differents properties of course.
angular.module('test', []).factory('tested', function() {
return {
value : '123'
};
}).controller('ctrl1', function($scope, tested) {
$scope.tested = tested;
}).controller('ctrl2', function($scope, tested) {
$scope.tested = tested;
}).controller('ctrl3', function($scope, tested) {
$scope.tested = tested;
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test">
<div ng-controller="ctrl1">
<input type="text" ng-model="tested.value" />
{{ tested.value }}
</div>
<div ng-controller="ctrl2">
<input type="text" ng-model="tested.value" />
{{ tested.value }}
</div>
<div ng-controller="ctrl3">
<input type="text" ng-model="tested.value" />
{{ tested.value }}
</div>
</div>
Since each view has its controller, the only way to share data is with a service of type "provider", "service" or "factory".
You could then modify your object from each controller with the methods you talk about.
In the end, to notify each view something changed, the service methods could raise an event from the service :
$rootScope.$broadcast('somethingChanged', myObject);
And each controller could listen with:
$scope.$on('somethingChanged', function(data) {
});
I have a multiple page (view) sign up form and on view 3 I have an input with an ng-pattern to evaualte a postal/zip code. The problem is when I switch views and then revisit the view with the ng-pattern the input becomes invalid.
<input type="text" class="form-control" name="postal" ng-model="user.postal" ng-minlength="1" ng-maxlength="8" maxlength="8" ng-pattern="/(^[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJKLMNPRSTVWXYZ]( )?\d[ABCEGHJKLMNPRSTVWXYZ]\d$)|(^\d{5}(-\d{4})?$)/gmi" required>
Any ideas on why this becomes invalid?
Here's a snippet of working code that maintains a valid zip code, and user name, when switching between views. I changed your pattern string to make it work for me, but yours might be fine. I just wanted something simpler to test with. The pattern matches xxxxx | xxxxx-xxxx .
The user object is displayed on each view.
The Zip code only shows when it's valid.
If you're using ngView, I found it easiest to nest inside a controller, and remove the controller option from the route configuration. When ngview is updated, it looks like a new controller is instantiated, and you get a new scope. Your previous input would be lost, and well, not be valid :)
var testApp = angular.module("testApp", ['ngRoute']);
testApp.config(['$routeProvider',
function($routeProvider) {
//Every View here shares the same controller. ng-view will give you a new controller if you pass it in.
$routeProvider.when("/view1", {
//Use TemplateURL instead to reference an extrnal page
template: '<h2> view 1 </h2><br/>Zip: <input type="text" class="form-control" name="postal" ng-model="user.postal" ng-pattern="/^\\d{5}$|^\\d{5}-\\d{4}$/" ng-required="!user.postal" /><br/>{{user}}'
//controller: testCtrl * Maybe this is your issue. *
})
.when("/view2", {
//Use TemplateURL instead to reference an extrnal page
template: '<h2> view 2 </h2>Name: <input type="text" class="form-control" name="uname" ng-model="user.name" required /> <br /> {{user}}'
});
}
]);
testApp.controller("testCtrl", function($scope, $http) {
$scope.user = {};
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.11/angular.min.js"></script>
<script src="http://code.angularjs.org/1.2.1/angular-route.js">
</script>
<div ng-app="testApp">
<div ng-controller="testCtrl">
View 1
<br/>
View 2
<ng-view></ng-view>
</div>
</div>
I currently have a multistep form that lets the user filter which kind of devices he wants back. In order to store his selections, in the current controller I have injected a Factory that can store his selections:
// Factory
App.factory('DeviceSelection',function() {
var states=[{selection:{}},{selection:{}},{selection:{}},{selection:{}}];
return states;
});
So, the form that is rendered to the user looks like this:
<form class="form-horizontal">
<ul class="nav nav-tabs">
<li ng-repeat="step in steps" ng-class="{active: $index==getCurrentStepIndex()}">
{{step}}
</li>
</ul>
<div ng-switch on="selection">
<!-- First Step -->
<div ng-switch-when="How much do you talk?">
<input type="radio" name="device_hours" id="device_hours_long" value="Yes" ng-model='states[0].selection.hours'>
<input type="radio" name="device_hours" id="device_hours_short" value="No", ng-model='states[0].selection.hours'>
</div>
<!-- Second Step -->
<div ng-switch-when="Operating System">
<input type="checkbox" value="ios" id="selection_os_iOS" ng-model="states[1].selection.ios"> iOS
<input type="checkbox" value="bb" id="selection_os_bb" ng-model="states[1].selection.bb"> Black Berry
<input type="checkbox" value="dunno" id="selection_os_dunno" ng-model="states[1].selection.dunno"> I don't care
</div>
</div>
<div class="pull-right" ng-show="!hasNextStep()"><button style="margin:20px 0;" class="btn btn-success">Show me the devices!</button></div>
My question is:
Once the user clicks submit, I would like to show his results. I am assuming that I want to use another Controller that will be responsible to look for the data that the User expects (according to the selections he has done in the form). However, I fail to see how would I pass that data that I have in the current $scope to another controller (which will fetch an external API). Where/how should I place the code that will let me pass the control to another Controller and give that controller the selections of the User?
$scope is not the model, its a reference to a model, glue in between the data & the view. If $scopes in two, or more, controllers need to share data use a singleton object by registering a angular factory. That one service/factory can be injected into as many controllers as you like, and then everything can work off that one source of truth.
Here is a demo of a factory passing UI user clicks data between controllers. http://plnkr.co/edit/P2UudS?p=preview
app.factory('uiFieldState', function () {
return {uiObject: {data: null}}
});
app.controller('NavbarCtrl', ['$scope', 'uiFieldState', '$stateParams', '$state',
function($scope, uiFieldState, $stateParams, $state) {
$scope.selected = uiFieldState.uiObject;
}
]);
app.controller('LeftTabACtrl', ['$scope', 'uiFieldState', '$stateParams', '$state',
function($scope, uiFieldState, $stateParams, $state) {
$scope.selected2 = uiFieldState.uiObject;
}
]);
The factory object {uiObject: {data: null}} is injected into the controller with uiFieldState & then its simply $scope.selected = uiFieldState.uiObject; for connecting the factory to the scope ng-model="selected.data" .
Use angular to intercept the form, post the data with $http, then on success set window.location to the proper destination, which should be defined in your router