This is a newbie question about Angular 1.4.
My Main Question
I have the following search view:
'use strict';
var module = angular.module('myapp.search', ['ngRoute', 'ngResource']);
module.config(function ($routeProvider) {
$routeProvider.when('/search', {
templateUrl: 'search/search.html',
controller: 'SearchCtrl'
});
});
module.factory('Search', function ($resource) {
return $resource('http://www.myapp.com/api/search', {}, {
query: {
method: 'GET',
params: {},
isArray: false
}
});
});
module.controller('SearchCtrl', function ($scope, Search) {
$scope.results = [];
$scope.query = '';
$scope.doSearch = function (query) {
Search.query({q: query}).$promise.then(function (result) {
$scope.results = result.results;
});
};
});
And the partial HTML (search.html):
#search.html
<input placeholder="search" ng-model="query" />
<button ng-click="doSearch(query)" />
<h1>Results</h1>
<ol>
<li ng-repeat="result in results">{{ result }}</li>
</ol>
...
This works perfectly, the problem is when I want to put the search input in a toolbar (defined in index.html):
#index.html
<div ng-controller="SearchCtrl">
<div class="toolbar">
<input placeholder="search" ng-model="query" />
<button ng-click="doSearch(query)">
</div>
...
In this case, by pushing the search button the query is executed in the back-end but the results are not updated in $scope. what's wrong? I've tried calling $scope.$apply() with no luck. I'm lost with this.
Bonus Question
Given that the search functionality is in the toolbar (always visible) users can execute queries in any place of the website. How can I redirect the response to http://www.myapp.com/search?
Thanks in advance.
You have two distinct instances of SearchCtrl, each with its own distinct scope: one in the navbar (dur to ng-controller="SearchCtrl"), and one in the main view (due to controller: 'SearchCtrl').
So the search in the navbar modifies the scope of the navbar controller, and the scope of the main view doesn't know anything about it.
If you want to got to the search main view when something is searched in the navbar, simply use
$location.url('/search?query=' + theEncodedQueryTypedByTheUser);
And in the search route's resolve function, or in its controller, get the route param named query, send the HTTP request, and update the scope with the results.
Related
I cannot get some data from my factory:
.factory("userDataFactory", function(){
var user;
var user_notes;
var interfaz = {
getUsuario: function(){
return user;
},
getNotes: function(){
return user_notes;
},
nuevoUsuario: function(userId, email){
user = [userId, email];
},
addNotes: function(notes){
user_notes = notes;
}
}
return interfaz;
})
What I do first is to populate the factory with data from my Ruby Api using this code:
$http({
method : "POST",
url: "http://localhost:3000/api/v1/checkuser",
data: json
}).then ( function mySucces(response){
userDataFactory.nuevoUsuario(response.data.user[0].id, response.data.user[0].email);
userDataFactory.addNotes(response.data.notes);
$state.go('thirdday');
And later on Im calling my factory in a new controller:
.controller('ListNotes', function($scope, userDataFactory, $state){
$scope.notes = userDataFactory.getNotes();
console.log($scope.notes);
//console.log($scope.notes[0].note1);
})
and finally, trying to show it in my html (listnotes.html)
<div class="list">
<a class="item" href="#" ng-repeat="x in notes">
{{ x.note2 }}
</a>
</div>
my routes config just in case:
.state('list',{
url: '/listnotes',
templateUrl: 'templates/listnotes.html',
controller: 'ListNotes'
})
so, what am I missing? why is not showing all the notes I got from the API?
Thanks in advance
EDIT
After I get the data from my API I go to 'thirdday.html' view and I just press a button to go to the list view.
.state('thirdday',{
url: '/thirdday',
templateUrl: 'templates/thirdday.html',
controller: 'Notes'
})
In your factory 'user_notes' contains object with key 'note1' (according to your log statement console.log($scope.notes[0].note1)). and in ng-repeat you are using x.note2. Make it x.note1.
try to add notes {{notes}} in html. see if it has value in there.
may be you just not add ng-app or ng-controller or someting in the mark up
I have defined a controller like this :
app.controller("home", function ($scope, $http, $common) {
$http({
method: "GET",
url: '/posts/loadData'
}).then(function (response) {
//console.clear()
if (typeof response.data.posts != 'undefined') {
console.log(response.data.posts);
$scope.posts = $common.arrangePosts(response.data.posts);
}
});
})
and a service to arrange data :
app.service('$common', function ($timeout, $sce, $httpParamSerializerJQLike) {
var that = this;
this.arrangePosts = function (rawPosts) {
var posts = [];
$.each(rawPosts, function (key, value) {
posts.push({
postId: value.postId,
postLink: '/post/' + that.cleanString(value.title) + '/' + value.postId,
title: value.title,
summary: $sce.trustAsHtml(value.summary)
});
});
return posts;
}
});
using values in html like this :
<div class="widget fullwidth post-single">
<h4 class="widget-title">Latest</h4>
<div class="widget-content">
<ul>
<li ng-repeat="post in posts">
<h4 class="list-title">{{post.title}}</h4>
{{post.summary}}
</li>
</ul>
</div>
</div>
Data coming from server in JSON form :
Object { postId="4", title="asdf", summary="<p>asdf</p>"}
but all the html tags are printing on my page as it is (like a text) in summary.
In many SO posts people suggested to use $sce.trustAsHtml but its not working for me. Please suggest anyway to solve my problem.
Any help will be appreciated..!!
have you tried this?
<div ng-bind-html='post.summary'></div>
You could solve this over a directive. Did you know, that you can use JQuery Lite inside AngularJS to manipulate the DOM?
Here a quick example:
angular.module("PostsDirective",[])
.directive("posts", function($sce){
return {
link: function($scope, $element, $attrs){
//the HTML you want to show
var post = "<div>hello world</div>";
var posts = [post,post,post,post];
//iterating through the list (_.each is a function from underscore.js)
_.each(posts, function(element){
//if you want to save the trusted html string in a var you can do this with getTrustedHtml
//see the docs
var safeHtml = $sce.getTrustedHtml($sce.trustAsHtml(element));
//and here you can use JQuery Lite. It appends the html string to the DOM
//$element refers to the directive element in the DOM
$element.append(safeHtml);
});
}
};
});
And the html
<posts></posts>
This also pretty nice for the readability for your HTML code. And you can use it everywhere on your page.
BTW:
As i can see, you get the HTML elements directly from a REST-Service. Why don't you get just the data and insert it into the ng-repeat? If you transfer all the HTML you get a pretty high overhead if you have loads of data.
I have 3 tabs: customers, billing, and method. On the first tab I have the Bootstrap Typeahead, that is connected to a json file of customers. The second tab is a detailed view of the customer account with outstanding charges. Finally, the third tab is to actually charge the customer with a credit card. Additionally, I am using UI Router to navigate through a complex hierarchy. the tabs are named views of a page called make-payments, which is a child of a parent state called payments. I am trying to use the typeahead in the first tab (customers) to select and view a customer detail in the second tab (billing). I am hoping this happens when the user selects the person from the dropdown, they should immediately navigate to the next tab, and the next tab should show that persons details. I am having some difficulty really understanding how to get the data from the typeahead to the next two tabs, and if I need a service to do this or not.
make-payment.html(My tabs):
<h3 class="text-center">Make a One-Time Payment</h3>
<uib-tabset type="pills nav-pills-centered">
<uib-tab heading="1">
<div ui-view=customers></div>
</uib-tab>
<uib-tab heading="2">
<div ui-view=billing></div>
</uib-tab>
<uib-tab heading="3">
<div ui-view=method></div>
</uib-tab>
</uib-tabset>
customers.html & the typeahead.js ctrl:
angular
.module('myApp')
.controller('TypeAheadCtrl', function($scope, $http) {
$http.get('customers.json').success(function(data) {
$scope.customers = data;
});
var self = this;
self.submit1 = function() {
console.log('First form submit with', self.customer)
};
});
<div ng-controller="TypeAheadCtrl as ctrl">
<form ng-submit="ctrl.submit1()" name="customerForm">
<div class="form-group">
<label for="account">Account Number</label>
<input type="text" class="form-control" placeholder="Search or enter an account number" ng-model="ctrl.customer.account" uib-typeahead="customer as customer.index for customer in customers | filter:$viewValue | limitTo:6" typeahead-template-url="templates/customer-tpl.html"
typeahead-no-results="noResults">
</div>
</form>
</div>
app.js (where I have ui-router stuff)
.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/payments/make-payment');
$stateProvider
// HOME STATES AND NESTED VIEWS ========================================
.state('payments', {
url: '/payments',
templateUrl: 'views/payments.html'
})
.state('activity', {
url: '/activity',
templateUrl: 'views/activity.html'
})
// nested lists
.state('payments.make-payment', {
url: '/make-payment',
views: {
'': {
templateUrl: 'views/make-payment.html'
},
'customers#payments.make-payment': {
templateUrl: 'views/customers.html'
},
'billing#payments.make-payment': {
templateUrl: 'views/billing.html'
},
'method#payments.make-payment': {
templateUrl: 'views/method.html'
});
});
billing.html
<p>{{ctrl.customer.account.name}}</p>
<p>{{ctrl.customer.account.address}}</p>
<p>{{ctrl.customer.account.email}}</p>
<p>{{ctrl.customer.account.phone}}</p>
you can send the selected customer data to a service and pull from the service in the two tabs.
so I guess when you actually select a user from the dropdown or whatever you can call myFactory.setCustomer(data).
and in your other two states you can watch the getCustomer() function in myFactory
.factory('myFactory', myFactory);
function myFactory() {
var customer;
function setCustomer(data) {
customer = data;
}
function getCustomer() {
return customer;
}
//public API
return {
setCustomer: setCustomer,
getCustomer: getCustomer
}
}
I'm writing a small test application where I can retrieve my customers from a parse.com database.
I have the following form in html
...
<body ng-app="myApp">
<div ng-controller="CustomerController">
<button ng-click="getCustomers()">Get customers</button>
<ul>
<li ng-repeat="customer in customers">{{ customer.name }}</li>
</ul>
</div>
</body>
...
My angular app is the following:
Module
var app = angular
.module('myApp', ['ngResource'])
.constant('myConfig', {
'api_url': 'https://api.parse.com/1/classes/',
'parse_application_id': 'xxxxxxxxxxxxx',
'parse_rest_api_key': 'xxxxxxxxxxxxx'
});
Factory
app.factory('CustomersService', function($resource, myConfig) {
return $resource(myConfig.api_url + 'Customer', {}, {
query: {
method: 'GET',
isArray: false,
headers: {
'X-Parse-Application-Id': myConfig.parse_application_id,
'X-Parse-REST-API-Key': myConfig.parse_rest_api_key
}
},
create: {
method: 'POST',
headers: {
'X-Parse-Application-Id': myConfig.parse_application_id,
'X-Parse-REST-API-Key': myConfig.parse_rest_api_key
}
}
})
});
Controller:
app.controller('CustomerController', function($scope, CustomersService, CustomerService) {
$scope.getCustomers = function() {
CustomersService.query().$promise.then(function(result) {
$scope.customers = result.results;
});
};
});
So when I click my button, everything works like it should.
But I also want to add a filter by name when I want to retrieve customers from the database.
When I execute the following in Postman
https://api.parse.com/1/classes/Customer?where={"name":"aaaaa"}
this works and only gets the customer with the name "aaaaa". So I know that the syntax is OK.
So I will add a textbox where the user can enter a customername and after that I want to click on the search button.
But how can I manage the ?where={"name":"aaaaa"} into the angular stuff when I click the button? I also want to expand the filter with other columns from that customer.
Something like this should work (assuming everything goes in the where object)
Add some search fields that bind to a scoped object's properties. We'll call it search
<label for="search_name">Name</label>
<input type="text" ng-model="search.name" name="name" id="search_name">
<label for="search_city">City</label>
<input type="text" ng-model="search.city" name="city" id="search_city">
Then you can execute the query action with
CustomersService.query({where: $scope.search}).$promise...
That should create a query param like
?where=%7B%22name%22%3A%22aaaaa%22%2C%22city%22%3A%22London%22%7D
which is the URI encoded value
?where={"name":"aaaaa","city":"London"}
Let's say, I have two divs like this:
<div ng-show="!items.length>
<p>No items found</p>
</div>
<div ng-show="items.length>
<li ng-repeat="item in items">
...
...
</div>
Now $scope.items is populated using an AJAX call. Now whenever I load this page, it initially shows me that No items found. But after a second or so (when the AJAX call gets completed), it displays me the other <div> and hides the No items found. I was wondering if there is a better alternative technique for solving this problem ?
UPDATE: Controller looks like this (simplified for the question):
myApp.controller('Controller', function($scope, $http) {
$http.get(fetchURL).
success(function(data, status, headers, config) {
$scope.items =data
});
});
dont create $scope.items before the ajax is complete, so create $scope.items only after ajax request is complete and data is ready.
for ex:
$http.get(fetchURL).success(function(data) {
$scope.items = data; // create $scope.items here, and don't create before this.
});
then
<div ng-show="items && (items.length ==0)">
<p>No items found</p>
</div>
<div ng-if="items">
<div ng-show="!items.length">No items found.</div>
...
</div>
This will only render the content after the items are actually created.
You can use the resolve parameter of your routeProvider#when. See in the docs that this parameter is:
An optional map of dependencies which should be injected into the controller. If any of these dependencies are promises, the router will wait for them all to be resolved or one to be rejected before the controller is instantiated
So, in your case, you can do something like this:
$routeProvider.when('/url', {
templateUrl: 'partials/yourhtml.html',
controller: 'yourController',
resolve: {
'items' : ['$http', function($http) {
return $http.get("items");
}]
}
});
And in controller you inject the 'items' as a parameter:
myApp.controller('Controller', function($scope, items) {
$scope.items = items.data;
}]);