AngularJS needed 2 clicks to do search action - angularjs

Im AngularJs newbie:).
But I have 3 components: controller C1 (for entering search input and clicking submit button),controller C2 (for div which will receive results from SearchService and display it) and service searchService for sending some hardocoded results.
THe idea is that after submitting search text, I have to submit it twice. With one submit click, view of ui-router is updated to partial page without results loaded. After second click the results are displayed.
Then next searches are done on search view page, all is ok, one click gives needed data.
How to fix it: I want to change ui-router view and get results from service with one click?
C1 method code (started after submiting searchText)
$scope.searchText = function searchText(text) {
$log.log("Method searchText with text: " + text);
//var parametersObject = {searchText: text, results: assetResults}
$state.go('search');
$log.log("SearchInputBoxController: rootScope Broadcasting message: " + text);
$rootScope.$broadcast('DoSearch', text);
$log.log("SearchInputBoxController finished");
};
C2 method: event handler for event 'DoSearch'
$scope.$on('DoSearch', function (event, data) {
$scope.asset = searchService.search(data);
$scope.searchText = data;
});
searchService code:
Application.Services.factory('SearchService', ['SearchServiceResource', '$q',
function (SearchServiceResource, $q) {
return {
search : function (searchText)
{
var result = [
{id: 'aaaaa', sn:"1234-1234-1234-1235", name:"DCU_name1", type: "DCU", tags: ["tag1", "tag2"]},
{id: 'aaaaa', sn:"1234-1234-1234-1235", name:"DCU_name2", type: "DCU", tags: ["tag1", "tag2"]},
{id: 'aaaaa', sn:"1234-1234-1234-1235", name:"NODE_name1", type: "DCU_Node", tags: ["tag1", "tag2"]}
];
return result;
}
};
}]);

I believe the clicking twice is because the first click is triggering the state change, then the broadcast occurs, and then the state change occurs. The second click, the state doesn't need to change, and the current now unchanged controller gets the broadcast. There are a couple different approaches I can think of (and I would def shy away from rootscope broadcasting):
in your shared service, maintain a search string variable and on your
controller, put watch on that service's variable.
change your route/state settings to include a parameter like /search/:searchTerm and in the controller, look for the state established searchTerm variable and on the controller load see if searchTerm is provided and fire the search. Some more info here
Let me now if you need any help or more specifics on the ideas listed.

Related

If user 'touches' form inputs on profile & forgets to save: show `POP UP` when they click SideMenu icon

I've tried to come up with some sort of "error checker/validation" for my users IF they forget to Save the edits they made on their profiles.
The user enters the Profile.html state. They start to update some of their info (i.e name, phone number, etc.). INSTEAD of pressing the SAVE CHANGES button they navigate away from the Profile state by clicking the SideMenu icon at the top left of their mobile screen.
Since the form is technically now consider to be "$dirty". I've tried to use this angular property at first but I couldn't really get the results I wanted so I tried my luck with $watch..
ProfileController.js
$rootScope.isFormDirty = false;//global variable 'isFormDirty'->inject in controller.js (toggleLeftSideMenu())
$scope.$watch('updateDriverProfileInfo', function(newValue, oldValue) {//new & oldValue = ng-model when form is 1st 'viewed' is dirty
//http://tutorials.jenkov.com/angularjs/watch-digest-apply.html
if (newValue !== oldValue) {
// console.log("updatingg")
$rootScope.isFormDirty = true;
}
}, true);
Angular docs on $watch
Maybe I should of made a factory or Service for this now that I think about it but at the time I used $rootScope so that I can set a global variable isFormDirty on this controller and use it on the General Controller that holds the Side Menu's logic in this Ionic app.
controller.js (this is where the Controller for the SideMenu is)
$scope.sidemenuIsOpen = false;
$scope.toggleLeftSideMenu = function() {//ng-click from menu.html
$scope.sidemenuIsOpen = !$scope.sidemenuIsOpen;
if ($scope.sidemenuIsOpen && $rootScope.isFormDirty) {
var confirmPopup = $ionicPopup.confirm({
title: 'Changes were not saved',
template: 'Do you want to save your changes?',
});
confirmPopup.then(function(res) {
if (res) {
console.log('Run updateDriverProfile()');
} else {
console.log('Allow user to continue w/o changes');
}
});
}
};
That's basically the gist of my code. It actually "works" but I have identified a pattern and this is where I need your assistance to either suggest a whole different method to accomplish this or perhaps some refactoring tips for this current code.
The Pop up does show when the user clicks on the Side Menu button BUT I don't think it really matters if the form is $dirty or not..
The bigger issue is that the Pop up starts showing regardless if you are trying to leave the profile.html view or any other view for that matter.
When I wrote this code I was under the impression that the Pop up and toggleLeftSideMenu functions would ONLY work on the Profile view since I am "watching" the updateDriverProfileInfo object and I also created that global variable to use between the Menu Controller and Profile Controller.
you need to have a good understanding on ionic Lifecycle, try with any of the below events
$scope.$on('$ionicView.leave', function(){
// Anything you can think of
});
$scope.$on('$ionicView.beforeLeave', function(){
// Anything you can think of
});
$scope.$on('$ionicView.unloaded', function(){
// Anything you can think of
});
find more information here http://www.gajotres.net/understanding-ionic-view-lifecycle/

How to enable auto watch angularJs?

I'm beginner with angularJs and have, as i think, simple problem with whatching changes in my $scope.
I have an object $scope.tasks which i received from server. It looks like:
{
2: {id: 2, name: 'name1', user: 3},
3: {id: 3, name: 'name2', user: 1},
4: {id: 4, name: 'name3', user: 4},
}
also i have a function which update user in task:
$scope.managePlannedIterationTask = function(taskId, userId) {
var data = {
'taskId' : taskId,
'userId' : userId
};
$http.post("url", data).success(function(data, status) {
$scope.tasks[taskId].user = userId; //#1
});
};
also i have custom function $scope.test();
this function must to be executed after every change of user in tasks, for example after update query and updating at //#1
I tried to use for this goal $watch and $watchCollection:
$scope.$watchCollection('tasks', function () {
$scope.test();
})
But this doesn't help. And now i must to insert $scope.test() in all places when i'm update my $scope.tasks. It's very sad.
How can i do it automatically?
Using watch isn't be good practice to have it in code. I'd say that have an call to test method when you update call gets succeeded.
And inside a success callback of post call do make an another call to get updated tasks list to make your view data properly in sync with server data. Currently you are updating data on client side itself for particular record, that doesn't make sense in data-centric application which is widely used by multiple users. If suppose you have 1000 users which are accessing this application, and while you are updating any record, other users also has updated/added any record on the same page. So that will add inconsistent behavivor inside application. So that's why I'm suggesting you to make one more ajax again to fetch data & to keep your application to look more consistent and accurate. Making an one more ajax call wouldn't take 100ms, which is less cost paid for good reason.
Code
$scope.managePlannedIterationTask = function(taskId, userId) {
var data = {
'taskId' : taskId,
'userId' : userId
};
$http.post("url", data).then(function(response) {
var data = response.data;
$scope.getTasks();
$scope.test();
});
};
$scope.getTasks = function(){
$http.get('getTasksUrl').then(function(response){
$scope.tasks = response.data;
});
}
You can write a directive with watcher and put on models which you want to be watched. But if I understood you correctly you need to update data on your page after successful post request, you can achieve this by firing get request, after successful post, or at worse case scenario you can use route or state reload depending on which router you use.

Title: "Angular Js Data is not formatted properly"

When a user clicks through to their User Profile Page they are supposed to see friendly dates. Instead they see the raw Javascript date. I am using a service to transform the data. I think the problem is related to ng-click and href that is being used. I do this throughout my Single Page Appllication using the MEAN Stack and the only pages that have problems are the ones that use ng-click to open.
The link which navigates the user to their User Profile is the following
<a ng-show="authenticated" href="#/usrProfile" ng-click="showUser(current_user)">{{current_user}} </a>
Clicking on the above html link opens their User Profile page. The correct date time is there but it is not rendered properly. It shows, for example "2015-12-01T01:19:22.947Z", instead of "Nov 30, 2015" which is adjusted for their timezone and more human readable. If the user clicks the back button of the browser, then clicks the forward button it will show the same date properly rendered.
The date is rendered by Angular outside the controller using a service. The service renders the date with the following code
vUsrProfile.usrCreatedDate = $filter('date')(vOriginalUsrCreatedDate,"MMM d, y", $rootScope.current_offset);
The data in the service is passed to the controller using the following code
var usrSingle = usrTracker.getProperty();
$scope.usrSingle = usrSingle;
The code to display that date is this code
<h5 title="Date {{usrSingle.username}} joined">{{usrSingle.usrCreatedDate}}</h5>
I am missing something. It feels like the data is being displayed before the service has transformed it.
I hope this makes sense. It seems like a hard problem to describe.
MORE DETAILS:
Controller code. Passing data from one controller to another.
// Controller for link
app.controller('mainController', function ($scope, usrTracker, usrServiceById) {
// removed non-relevant code ...
// get single user data
$scope.showUser = function (vUsername) {
usrTracker.setProperty(usrServiceById.get({ "username": vUsername }));
};
// removed non-relevant code ...
});
// Controller User Profile Page
app.controller('usrPageController', function ($scope, usrTracker) {
var usrSingle = usrTracker.getProperty();
$scope.usrSingle = usrSingle;
// removed non-relevant code ...
});
// factory for a single user
app.factory('usrServiceById', function ($resource) {
return $resource('/api/usrProfile/:id', { id: '#_id' });
});
// factory to hold user data between controllers
app.factory('usrTracker', function ($filter,$q,$rootScope) {
var vUsrProfile = {
username: 'username',
usrEmail: 'usrEmail'
, usrFirst: 'usrFirst'
, usrLast: 'usrLast'
, usrTimezone: 'usrTimezone'
, usrGender: 'usrGender'
, usrBirth: 'usrGender'
, usrPrefHours: 'usrPrefHours'
, usrSocAuth: 'usrSocAuth'
, usrCreatedDate: 'usrCreatedDate'
, usrAccessToken: 'usrAccessToken'
, usrRefreshToken: 'refreshToken'
, usrOffset: '+0000'
, usrDesc: 'usrDesc'
, userSocId: 'userSocId'
};
return {
getProperty: function () {
var vOriginalUsrCreatedDate = vUsrProfile.usrCreatedDate;
vUsrProfile.usrCreatedDate = $filter('date')(vOriginalUsrCreatedDate,"MMM d, y", $rootScope.current_offset);
return vUsrProfile;
},
setProperty: function (value) {
vUsrProfile = value;
}
};
});

Access to nested object in JSON ,select the right value in a dropdownlist (Angularjs)

I am a beginner in AngularJS. I used the $resource service to get a specific object of type person based on its id from the server in order to update this object.
Resource service:
moduleName.factory('Person', function ($resource) {
return $resource('/application/person/:id', {}, {
show: { method: 'GET' , params: {id: '#id'}},
});
});
The object that I received and it displayed in my browser using the {{}} is:
{
"id": "560cf96ee85532035928889e",
"firstName": "Christiane",
"gender": "Male",
"flight": {
"id": null,
"arrivalDate": "2015-01-05 11:30",
"arrivalAirport": {
"name": "Frankfurt"
},
"stopovers": []
}
}
In this form I have a dropdownlist that contains the Airport list, I expect that the value of the arrivalAirport will be selected in this dropdown list, but no value was selected, I try to set the selected value of this dropdown list in the AngularJs controller of this view.Before setting the value of this object I try to access to their value by this code inside the controller.
moduleName.controller('PersonDetailController',function ($scope, $routeParams, Person,$location) {
$scope.person = Person.show({id: $routeParams.id});
console.log($scope.person.flight.arrivalAirport);
});
when I want to get the value of the object flight.arrivalAirport nested in person object I got this error.
TypeError: Cannot read property 'arrivalAirport' of undefined
How can I display the right value selected in the dropdown list and how can I access to the other object like stopovers array in order to update their values?
Thank you.
When you call console.log Person's data hasn't been retrieved yet. You should do something like this:
$scope.person = Person.show({id: $routeParams.id}, function(person) {
console.log(person.flight.arrivalAirport);
});
In general, any code working on data received asynchronously can't be put right after the call because it's very unlikely that data has been retrieved at that point, you should use callbacks and promises.
Your view should update automatically, and you said it does, at least for the part showing the json. In order to make the dropdown update you should bind its values to a property inside $scope.person
If the dropdown doesn't update then there must be some other error and you might need to post the dropdown's code too.
If you want to manually update the dropdown's list instead then you need to put the relevant code inside the callback (the function i added to "person.show" in the code above) in order to make it work

How to implement path aliases in ui-router

I'm trying to find a way to implement route aliases in Angular.js using ui-router.
Let's say I have a state with the url article/:articleId and I want to have /articles/some-article-title redirected (internally) to /article/76554 without changing the browser location.
Could this be done with ui-router?
I. Doubled state mapping (reuse of controller, views)
NOTE: This is original answer, showing how to solve the issue with two states. Below is another approach, reacting on the comment by Geert
There is a plunker with working example. Say we have this two objects (on a server)
var articles = [
{ID: 1, Title : 'The cool one', Content : 'The content of the cool one',},
{ID: 2, Title : 'The poor one', Content : 'The content of the poor one',},
];
And we would like to use URL as
// by ID
../article/1
../article/2
// by Title
../article/The-cool-one
../article/The-poor-one
Then we can create this state definitions:
// the detail state with ID
.state('articles.detail', {
url: "/{ID:[0-9]{1,8}}",
templateUrl: 'article.tpl.html',
resolve : {
item : function(ArticleSvc, $stateParams) {
return ArticleSvc.getById($stateParams.ID);
},
},
controller:['$scope','$state','item',
function ( $scope , $state , item){
$scope.article = item;
}],
})
// the title state, expecting the Title to be passed
.state('articles.title', {
url: "/{Title:[0-9a-zA-Z\-]*}",
templateUrl: 'article.tpl.html',
resolve : {
item : function(ArticleSvc, $stateParams) {
return ArticleSvc.getByTitle($stateParams.Title);
},
},
controller:['$scope','$state','item',
function ( $scope , $state , item){
$scope.article = item;
}],
})
As we can see, the trick is that the Controller and the Template (templateUrl) are the same. We just ask the Service ArticleSvc to getById() or getByTitle(). Once resolved, we can work with the returned item...
The plunker with more details is here
II. Aliasing, based on native UI-Router functionality
Note: This extension reacts on Geert appropriate comment
So, there is a UI-Router built-in/native way for route aliasing. It is called
$urlRouterProvider - .when()
I created working plunker here. Firstly, we will need only one state defintion, but without any restrictions on ID.
.state('articles.detail', {
//url: "/{ID:[0-9]{1,8}}",
url: "/{ID}",
We also have to implement some mapper, converting title to id (the alias mapper). That would be new Article service method:
var getIdByTitle = function(title){
// some how get the ID for a Title
...
}
And now the power of $urlRouterProvider.when()
$urlRouterProvider.when(/article\/[a-zA-Z\-]+/,
function($match, $state, ArticleSvc) {
// get the Title
var title = $match.input.split('article/')[1];
// get some promise resolving that title
// converting it into ID
var promiseId = ArticleSvc.getIdByTitle(title);
promiseId.then(function(id){
// once ID is recieved... we can go to the detail
$state.go('articles.detail', { ID: id}, {location: false});
})
// essential part! this will instruct UI-Router,
// that we did it... no need to resolve state anymore
return true;
}
);
That's it. This simple implementation skips error, wrong title... handling. But that is expected to be implemented anyhow... Check it here in action

Resources