Filtering for Parameters - angularjs

When using the $urlMatcherFactory provider to create new parameter types, is there a way to filter types while encoding/decoding or validating them? I need some "date" parameters, and it seems easiest to just create a custom type that can encode/decode them from the URL, as the built-in "date" type does not use the same date formats I want. I need to do this in a config block, as I need to do it before actually defining the states. This means I can only use providers (as far as I know, please correct me if I am wrong).
I found a way to use the date parser service from Angular Bootstrap for decoding the date, by injecting the uibDateParserProvider provider then retrieving the service with $get, but this does not seem like the best way to go. Also, it seems like the $filter provider does not work the same way, as $filterProvider.$get returns an array with an injection function or something like that as the 2nd element. This injection function is not the $filter service. I have not delved deeply enough into the angular source to be able to figure out what it actually is, but would be very interested to find out. I know I could parse the dates out myself, but why do that when there are so many helpful angular services to do it for me?
Here is an example of what I am trying to do (uses ES6):
angular.module('myModule').config(myStates);
function myStates($filterProvider, $urlMatcherFactoryProvider, $stateProvider, uibDateParserProvider) {
const decodeDateFormat = 'yyyy-MM-dd'; // the decoder date format
const encodeDateFormat = 'MMM dd, yyyy'; // the encoder format, which is different from the decoder format
$urlMatcherFactoryProvider.type('mydate', {
// encode when switching states, e.g. `$state.go()`
encode: item => {
const $filter = $filterProvider.$get(); // returns an array like: ['$filter', filterService] (not sure what the 2nd element is)
$filter('date')(item, decodeDateFormat); // this is what I ultimately want to do
},
// decode from the URL so we can use it as a date
// this uses a different format from the encoder date format
decode: item => {
const uibDateParser = uibDateParserProvider.$get(); // returns the uibDateParser service
return uibDateParser.parse(item, encodeDateFormat);
},
// validate
is: item => {
return item == null || angular.isDate(item); // just need to know if it is a date
},
});
$stateProvider.state('myState', {
url: 'my-path?{someParam:mydate}',
someParam: null,
});
}
Is there any way to use services when creating parameter types for the Angular UI Router? Otherwise, how do I get the $filter service from its provider, so I can do the filtering?

I'm able to use the $filter service by getting it from the $injectorProvider it self, e.g., $injectorProvider.$get().get('$filter');.
The following snippet is an example of usage in the config phase.
angular.module('myApp', [])
.config(function($injectorProvider) {
var $filter = $injectorProvider.$get().get('$filter');
console.log($filter('date')(new Date(), 'yyyy-MM-dd'));
});
angular.element(function() {
angular.bootstrap(document, ['myApp']);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.0/angular.js"></script>

Related

Passing a Complex Parameter into a Query in AngularJs

I have seen 100 examples of passing an ID into $resource.get() in order to query information out of a back-end in Angular. What I have not been able to find is how to pass a complex object.
If I have a table of objects to return, and I wish to run a search against them using multiple items of filter, I need to pass those items as parameters or as one complex parameter. For example, say I have a table of people's names and their cities, states, etc. I want to be able to say something like this:
var myResource = $resource(url);
myResource.get({name : "Mike", state : "Texas"});
The return may be a single row or multiple rows. But the point is how do I get the parameters off to the API call?
The way I have other methods set up that are simpler is by creating a repository in which I return like so:
return resource('/api/broker/productionInfo/');
Then in my API I do this (after the [RoutePrefix("api/broker")] setup:
[HttpGet]
[Route("productionInfo")]
public IHttpActionResult GetProductions()
{}
That's all awesome but I want to be able to add the search criteria in the repository call and then in the API method (i.e. extract from a querystring or however it is to be passed).
If I understand what you are asking correctly, you just want to pass additional parameters into an angular resource get request. It is as simple as what you have already suggested:
resource.get({id: 1, custom_param_1: "param1", custom_param_2: "param2"});
This would result in an http request that looks like this:
/resource/1?custom_param_1=param1&custom_param_2=param2
You can then extract these parameters on the API side of things.
Something to note is that get requests have a maximum length, and if you are attaching lots of different parameters to the request, it may be better to use a post or put instead.
The only thing I'm seeing that you're missing is a [FromUri] decorate attribute, in your GetProduction API method. Since Get supports only params binding through a query string (no body binding).
Your params:
options: {
StartDate: _startDate
EndDate: _endDate
TextSearch: "some search query....",
Page: 1,
PageSize: 25,
et...
}
Then, calling your repository from your controller:
repository.get(options).$promise.then(function (data) {
// data = response payload from backend
});
reposiroty
....
return resource('/api/broker/productionInfo/');
....
API
[HttpGet]
[Route("productionInfo")]
public IHttpActionResult GetProductions([FromUri] SearchCriteriaModel criteria) {
....
}
Hope that helps.

Integrating helper functions in AngularJS

So, I've just began learning Angular and my question is what ways (or the best practices) are there for injecting helper functions into AngularJS? I often need to include functions to assist my controller, but I have read online that the controller should hold as little logic as possible, which means they should be injected into the controller and declared in the module (fat module, skinny controller).
As such, I have been primarily injecting functions like this:
$provide.value
$provide.value('MySQLtoJS', function(datetimeString) {
var t = datetimeString.split(/[- :]/);
var d = new Date(t[0], t[1]-1, t[2], t[3], t[4], t[5]);
return d;
});
This uses the $provide service to create a value that can be injected in my controller. However, for more elaborate functions, such as those that require an injectable, I have been using this:
Factory provider
.factory('convertMySQLToJS', ['moment', function(moment) {
return function(arrayInput) {
if (Array.isArray(arrayInput)) {
for (var i = 0; i < arrayInput.length; i++) {
var t = arrayInput[i].begin_datetime.split(/[- :]/);
var start = new Date(t[0], t[1]-1, t[2], t[3], t[4], t[5]);
arrayInput[i].begin_datetime = start;
var t = arrayInput[i].end_datetime.split(/[- :]/);
var end = new Date(t[0], t[1]-1, t[2], t[3], t[4], t[5]);
arrayInput[i].end_datetime = end;
//Also create the moment message
if (arrayInput[i].begin_datetime >= new Date()) {
arrayInput[i].message = 'Begins at ' + moment(arrayInput[i].begin_datetime).format('MMMM Do YYYY, h:mm a') + ' and likely ends at ' + moment(arrayInput[i].end_datetime).format('MMMM Do YYYY, h:mm a');
}
else {
arrayInput[i].message = 'Began at ' + moment(arrayInput[i].begin_datetime).format('MMMM Do YYYY, h:mm a') + ' and likely ended at ' + moment(arrayInput[i].end_datetime).format('MMMM Do YYYY, h:mm a');
}
}
return arrayInput;
}
}
}])
However, factories are often used for their service and properties (like $http), or so I've been told. So I've been recently suggested by some people that I should be including them in the run configuration block (which seems a bit weird to me) by using $rootScope and giving it that property for the function I need. Since I'm new to Angular and I've found documentation rather lacking, I'm wondering how am I supposed to inject helper functions correctly into Angular, if there is a correct way?
The correct way in Angular is to wrap helpers into services and inject them when needed. It is a good idea to join several similarly themed methods into single helper service (think of it as of utility class).
It can be either factory, or value, or constant. The latter is preferable for such things because it can be also used within config blocks. They are interchangeable in other respects, as long as the factory function consists of return statement and doesn't use other dependencies. Since this one
app.factory('mysqlHelper', function (moment) {
return {
MySQLtoJS: function(datetimeString) { ... },
convertMySQLToJS: return function(arrayInput) { ... }
};
});
uses moment dependency, factory is the case for it.
Using globals (either on global JS scope or $rootScope) is considered bad practice:
Of course, global state sucks and you should use $rootScope sparingly,
like you would (hopefully) use with global variables in any language.
In particular, don't use it for code, only data. If you're tempted to
put a function on $rootScope, it's almost always better to put it in a
service that can be injected where it's needed, and more easily
tested.
And thus it provides a reasoning for that: testability. Services are testable. They can be unit-tested, they can be mocked. That is where Angular dependency injection shines.
Factories/services are certainly a way to do something like this. If it is always related to service activities such as sanitizing your data after retrieval, placing it in a service (or base service) works just fine. However, I find that always injecting services is a bit heavy when I have one-off helper functions that I need to pass around my app. I've ended up placing a special object on angular that holds my helper functions.
app.run([function() {
angular.UTIL = angular.UTIL || {};
var util = {
coolFunction: function(fieldName) {
return fieldName;
}
}
angular.extend(angular.UTIL, util);
}]);
This can then be called throughout your app:
var getField = angular.UTIL.coolFunction("fieldName");
As far as best practice goes with helpers such as these, it is best if you follow a few rules:
The helper functions are global and can easily be used throughout the codebase in a variety of applications and patterns
They cannot be easily translated into a directive and do not directly manipulate the DOM
They are helpers, not functionality or business logic
They replace utility functions that have been copied into multiple files
They are not bound to a specific scope

angular: programmatically format a number using the $locale information

I have included the corresponding locale file and it works fine. In any template I can do things like:
{{ value | number: 2}}
and it correctly formats the number according to the locale info.
Now I need to use the same locale info from javascript code in a controller to build a string.
I'm using a javascript component (a d3 graph to be precise) and I want to build strings to attache to it, so the template system is useless for this, but I'd like to take the locale configuration of numbers and dates from it.
So I'd nee something like this pseudocode:
var formattedValue = $local.format(value, { 'number': 2 });
Or something like that
Anyone knows how can I achieve that?
Try this :
var formattedValue = $filter('number')(value,2);
Working : http://plnkr.co/edit/aC4p95y52YZyoUEdQVzo?p=preview
We can achieve this by implementing a filter.
var app = angular.module('app', []);
app.filter('yourFilter', function(){
return function(string){
// build the string whatever you are trying to achieve
return newString; // return the string you achieved
}
});
for reference, http://blog.trifork.com/2014/04/10/internationalization-with-angularjs/
I could inject the filter like this:
presuApp.run(function ($rootScope, numberFilter) {
var formattedValue = numberFilter(value, 2);
[...]
It's just the name of the filter followed by th 'Filter' suffix.

Is it good practice to combine CREATE and EDIT controllers in AngularJS?

There are many duplicated code among CREATE and EDIT controllers.
These controllers could be combined into one for minimizing repetitive code.
The problem: I need to distinguish which method to use on form submitting - create() or edit() for example.
The solution: I could add $scope.mode for example and set $scope.mode='edit' if user clicked 'EDIT' button or set $scope.mode='add' if user clicked 'ADD' button.
I could use services for minimizing repetitive code, but there still will be duplicated code. For example in both controllers I have cancel() method which clears the form and hide it. I could store clearForm() and hideForm() in the service, but this code will be duplicated in both controllers:
$scope.cancel = function() {
Service.clearForm();
Service.hideForm();
};
Questions:
Is it good practice to combine CREATE and EDIT controllers in AngularJS?
Is there any good practices to minimize repetitive code?
Yes. Use 1 controller.
Here is the reason why use 1 controller
The job of the controller is to support the View. Your create view and the edit view is exactly same - just that one has data pre-populated (edit) and another does not (create).
Moreover the "purpose" of this View is to have the user change or enter new values in the form. Your only difference should be something like reset(). But even there you could start with an empty model object e.g. $scope.entity = {} in case of CREATE and you will start with $scope.entity = $http.get().
Repetition Problem with 2 Controllers
With 2 different controllers and services you are going to incur at least the following duplication:
$scope.cancel = function() {
Service.cancel();
};
$scope.validate = function() {
ValidtionSvc.validate();
}
.
.
.//other stuff similar
but the problem is why even this duplication like you stated.
(UDATED here onwards since above was the answer to the 1st question)
How to use 1 controller with repetition ?
Is there any good practices to minimize repetitive code?
Question redefined: Is there a good practice of eliminating repetitive code in CREATE and EDIT forms ?
No formal 'best practice' exist to my knowledge to avoid repetitive code in this specific situation. However I am advising against mode=edit/create. The reason being for controllers in this situation there should be almost no difference since their job is to purely to fetch/update the model as the user interacts.
Here are the difference you will encounter in this situation and how you can avoid if/then/else with mode=create/edit:
1) Populating the form with existing values vs. empty form for Create.
To fetch a existing entities you need some key/query data. If such key data is present you could do
var masterEntity = {};
if(keyData) {
masterEntity = MyEntityResourceFactory.getEntity(keyData);
}
$scope.entity = masterEntity;//for Create this would be {}
2) reset() form
should be simply
$scope.reset = function() {
$scope.entity = masterEntity;
}
3) Update/Create
$http.post()//should not be different in today's world since we are treating PUT as POST
4) Validation - this is a perfect reuse - there should be no differences.
5) Initial / Default Values
You can use masterEntity = Defaults instead of {}.
Is it good practice to combine CREATE and EDIT controllers in
AngularJS?
In my experience, yes it is a good idea for 99.9% of the time. I typically inject a formType variable into my controller via the $routeProvider resolve feature. So I would have something like the following:
$routeProvider
.when('/item/create', {
templateUrl: '/app/item/itemForm.html',
controller: 'itemFormController',
resolve: {
item: ['$route', 'itemRepository', function ($route, itemRepository) {
return itemRepository.getNew();
}],
formType: function () { return Enums.FormType.CREATE; }
},
})
.when('/item/edit/:itemId', {
templateUrl: '/app/item/itemForm.html',
controller: 'itemFormController',
resolve: {
item: ['$route', 'itemRepository', function ($route, itemRepository) {
return itemRepository.get($route.current.params.itemId);
}],
formType: function () { return Enums.FormType.EDIT; },
},
});
That way you get your entity and type of form action injected into the controller. I also share the same templates, so saving a form I can either rely on my repository/service to determine what REST endpoint to call, or I can do a simple check inside the controller depending on what formType was injected.
Is there any good practices to minimize repetitive code?
Some of the things I'm using to keep things DRY:
If you keep a common convention on your server API you can go a very long way with a base factory/repository/class (whatever you want to call it) for data access. For instance:
GET -> /{resource}?listQueryString // Return resource list
GET -> /{resource}/{id} // Return single resource
GET -> /{resource}/{id}/{resource}view // Return display representation of resource
PUT -> /{resource}/{id} // Update existing resource
POST -> /{resource}/ // Create new resource
etc.
We then use a AngularJs factory that returns a base repository class, lets call it abstractRepository. Then for each resource I create a concrete repository for that specific resource that prototypically inherits from abstractRepository, so I inherit all the shared/base features from abstractRepository and define any resource specific features to the concrete repository. This way the vast majority of data access code can be defined in the abstractRepository. Here's an example using Restangular:
abstractRepository
app.factory('abstractRepository', [function () {
function abstractRepository(restangular, route) {
this.restangular = restangular;
this.route = route;
}
abstractRepository.prototype = {
getList: function (params) {
return this.restangular.all(this.route).getList(params);
},
get: function (id) {
return this.restangular.one(this.route, id).get();
},
getView: function (id) {
return this.restangular.one(this.route, id).one(this.route + 'view').get();
},
update: function (updatedResource) {
return updatedResource.put();
},
create: function (newResource) {
return this.restangular.all(this.route).post(newResource);
}
// etc.
};
abstractRepository.extend = function (repository) {
repository.prototype = Object.create(abstractRepository.prototype);
repository.prototype.constructor = repository;
};
return abstractRepository;
}]);
Concrete repository, let's use customer as an example:
app.factory('customerRepository', ['Restangular', 'abstractRepository', function (restangular, abstractRepository) {
function customerRepository() {
abstractRepository.call(this, restangular, 'customers');
}
abstractRepository.extend(customerRepository);
return new customerRepository();
}]);
What you'll find if you use this base repository pattern is that most of your CRUD controllers will also share a lot of common code, so I typically create a base CRUD controller that my controllers inherit from. Some people dont like the idea of a base controller, but in our case it has served as well.
The answer to your first question probably depends on the specific circumstances.
If the two controllers share a substantial amount of operations, and the behavior of just one or two functions needs to be altered - why not! Maybe not the most elegant solution but hey, whatever works.
If the behavior of many or all controller operations is going to depend on '$scope.mode'...I'd say be careful. That seems like a dangerous path.
Angular services have always served me well when it comes to minimizing code replication between controllers. If there is a "good practice to minimizing repetitive code," I would say it would be services. They are global to your app and can be injected into multiple controllers without issue.
I hope that helps!

How to extend or override existing filters in angularjs?

Is it possible to extend existing "standard" filters (date, number, lowercase etc)?
In my case I need to parse date from YYYYMMDDhhmmss format so I'd like to extend (or override) date filter instead of writing my own.
I prefer to implement the decorator pattern, which is very easy in AngularJS.
If we take #pkozlowski.opensource example, we can change it to something like:
myApp.config(['$provide', function($provide) {
$provide.decorator('dateFilter', ['$delegate', function($delegate) {
var srcFilter = $delegate;
var extendsFilter = function() {
var res = srcFilter.apply(this, arguments);
return arguments[2] ? res + arguments[2] : res;
}
return extendsFilter;
}])
}])
And then in your views, you can use both.. the standard output and the extended behavior. with the same filter
<p>Standard output : {{ now | date:'yyyyMMddhhmmss' }}</p>
<p>External behavior : {{ now | date:'yyyyMMddhhmmss': ' My suffix' }}</p>
Here is a working fiddle illustrating both techniques:
http://jsfiddle.net/ar8m/9dg0hLho/
I'm not sure if I understand your question correctly, but if you would like to extend functionality of existing filters you could create a new filter that decorates an existing one. Example:
myApp.filter('customDate', function($filter) {
var standardDateFilterFn = $filter('date');
return function(dateToFormat) {
return 'prefix ' + standardDateFilterFn(dateToFormat, 'yyyyMMddhhmmss');
};
});
and then, in your template:
{{now | customDate}}
Having said the above, if you simply want to format a date according to a given format this can be done with the existing date filter:
{{now | date:'yyyyMMddhhmmss'}}
Here is the working jsFiddle illustrating both techniques: http://jsfiddle.net/pkozlowski_opensource/zVdJd/2/
Please note that if a format is not specified AngularJS will assume that this is 'medium' format (the exact format depends on a locale). Check http://docs.angularjs.org/api/ng.filter:date for more.
The last remark: I'm a bit confused about the 'parse from' part of your question. The thing is that filters are used to parse an object (date in this case) to string and not vice verse. If you are after parsing strings (from an input) representing dates you would have to look into NgModelController#$parsers (check the "Custom Validation" part in http://docs.angularjs.org/guide/forms).

Resources