How to use aurelia-validate with a object properties to validate? - decorator

I'm using aurelia-validate and my validation works fine if I use variables, but I need it to validate properties of an object rather than a variable:
Here's what works:
import {Validation} from 'aurelia-validation';
import {ensure} from 'aurelia-validation';
import {ItemService} from './service';
export class EditItem {
static inject() {
return [Validation, ItemService];
}
#ensure(function(it){
it.isNotEmpty()
.hasLengthBetween(3,10);
})
name = '';
#ensure(function(it){
it.isNotEmpty()
.hasMinLength(10)
.matches(/^https?:\/\/.{3,}$/) //looks like a url
.matches(/^\S*$/); //no spaces
})
url = '';
constructor(validation, service) {
this.validation = validation.on(this);
this.service = service;
}
activate(params){
return this.service.getItem(params.id).then(res => {
console.log(res);
this.name = res.content.name; //populate
this.url = res.content.url;
});
}
update() {
this.validation.validate().then(
() => {
var data = {
name: this.name,
url: this.url
};
this.service.updateItem(data).then(res => {
this.message = "Thank you!";
})
}
);
}
}
Here's what I'm trying to do (but doesn't work)...also I'm not sure if it's better to keep the properties on the class or have a property called this.item which contains the properties (this is the typical angular way):
import {Validation} from 'aurelia-validation';
import {ensure} from 'aurelia-validation';
import {ItemService} from './service';
export class EditItem {
static inject() {
return [Validation, ItemService];
}
#ensure(function(it){
it.isNotEmpty()
.hasLengthBetween(3,10);
})
this.item.name; //no assignment here should happen
#ensure(function(it){
it.isNotEmpty()
.hasMinLength(10)
.matches(/^https?:\/\/.{3,}$/) //looks like a url
.matches(/^\S*$/); //no spaces
})
this.item.url; //no assignment?
constructor(validation, service) {
this.validation = validation.on(this);
this.service = service;
this.item = null;
}
activate(params){
return this.service.getItem(params.id).then(res => {
console.log(res);
this.item = res.content; //populate with object from api call
});
}
update() {
this.validation.validate().then(
() => {
var data = {
name: this.item.name,
url: this.item.url
};
this.service.updateItem(data).then(res => {
this.message = "Thank you!";
})
}
);
}
}
Can someone give me some guidance here on how to use a validator against an existing object (for an edit page)?

The validation works in all kinds of situations, but using the #ensure decorator can only be used to declare your rules on simple properties (like you found out).
Hence...
Option a: replace the ensure decorator with the fluent API 'ensure' method, this supports 'nested' or 'complex' binding paths such as:
import {Validation} from 'aurelia-validation';
import {ItemService} from './service';
export class EditItem {
static inject() {
return [Validation, ItemService];
}
constructor(validation, service) {
this.validation = validation.on(this)
.ensure('item.url')
.isNotEmpty()
.hasMinLength(10)
.matches(/^https?:\/\/.{3,}$/) //looks like a url
.matches(/^\S*$/)
.ensure('item.name')
.isNotEmpty()
.hasLengthBetween(3,10);
this.service = service;
this.item = null;
}
activate(params){
return this.service.getItem(params.id).then(res => {
console.log(res);
this.item = res.content; //populate with object from api call
});
}
update() {
this.validation.validate().then(
() => {
var data = {
name: this.item.name,
url: this.item.url
};
this.service.updateItem(data).then(res => {
this.message = "Thank you!";
})
}
);
}
}
Note: you can set up your validation even before item is set. Cool, no?
Option b: Since the validation rules are specific to the item, you could move your validation rules inside your item class using the #ensure decorator inside that class instead.
You can then set up validation in your VM after you've retrieved the item: this.validation = validation.on(this.item); or, your service can set up the validation when it returns your item to your VM and make it an intrinsic part of the model: item.validation = validation.on(item);
Option a is easiest and seems to match your experience. Option b is more maintainable, as the validation rules for your model will live on the model, not on the view-model. However if you go with option b, you might have to adjust your HTML a bit to make sure validation hints appear.

Use the .on method of the validator to apply your rules to object properties.
The example below is called after I retrieve an object named stock, it validates that the quantity is not empty and is numeric only. Hope this helps...
let stock = {
name: 'some name'
minimumQuantity: '1'
};
applyRules() {
ValidationRules
.ensure((m: EditStock) => m.minimumQuantity)
.displayName("Minimum Quantity")
.required()
.withMessage(`\${$displayName} cannot be blank.`)
.matches( /^[0-9]*$/)
.withMessage(`\${$displayName} must be numeric only.`)
.on(this.stock);
}

Related

How to load Appcomponent class before routing takes place

I have set routing and display the page according to user roles. For this i am using guard on route. I am extracting userRole from service in Appcomponent class and using set and get method in main-service file. Now problem is that before i get role, routing takes place and it navigate to wrong url as it doesn't have role by then. Tough from next call, it works properly. Let me share the code:-
1.Here is guard class:-
export class HomeGuard implements CanActivate {
constructor(private _router: Router,private mainService: MainService) {
}
canActivate(): boolean {
let userRoles:any;
alert('HomeGuard');
userRoles = this.mainService.getSavedUserRole();
//userRoles = ['Profile Manager','Operations','Shipper'];
alert('userRoles are here'+userRoles);
console.log('here in homeguard');
if(userRoles) {
if(userRoles.some(x => x === 'Shipper') || userRoles.some(x => x === 'Admin'))
return true;
}
this._router.navigate(['/notfound']);
return false;
}
}
Here is AppComponent where i am extracting userRole from service:-
export class AppComponent {
savedUserRoles:any;
constructor(private translate: TranslateService,private mainService: MainService) {
console.log('Environment config', Config);
// this language will be used as a fallback when a translation isn't found in the current language
translate.setDefaultLang(AppSettings.LNG_TYPE);
// the lang to use, if the lang isn't available, it will use the current loader to get them
translate.use(AppSettings.LNG_TYPE);
this.mainService.getCurrentUser().subscribe(result => {
this.savedUserRoles = JSON.parse(JSON.parse(result._body).Data).Roles;
console.log('sdfghj'+this.savedUserRoles);
this.mainService.setSavedUserRole(this.savedUserRoles);
});
}
}
Here is main-service where i have defined set and get method:-
setSavedUserRole(name: any) {
console.log('main'+name);
this._userRoles = name;
}
getSavedUserRole() {
return this._userRoles;
}

Problems with onQueryEvent observable failing upon routing in Nativescript with Angular

I am using Nativescript with Angular and have code written that succesfully calls an onQueryEvent from the nativescript-firebase-plugin for data set upon first building the application. However after following a route to a second component containing the exact same onQueryEvent the data succeeds to retreive a complete list but skips the onQueryEvent.
In all honesty I don't know best practices for queries in any situation let alone this one, so I hope it is just a matter of manipulating how I call the onQueryEvent.
I believe the problem to be in the firebase.query inside the getMyTransactionList() function of the firebase.service.ts file.
the overview.component.html page has a transaction | async RadListView that successfully filters upon running tns run android. Then clicking any link directing to the deal-summary.component.html page where the function is re-iterated refuses to query by the static storage variable set in the firebase.service
Here is my code:
firebase.service.ts
export class FirebaseService {
private _allItems: Array<any> = [];
items: BehaviorSubject<Array<any>> = new BehaviorSubject([]);
public storage: any = '-KomUSGcX-j6qQmY4Wrh'; // set statically to test different routes
constructor(
private ngZone: NgZone,
){}
// fetch data
getMyDealList(): Observable<any> {
return new Observable((observer: any) => {
let path = `deals/${BackendService.token}`;
let onValueEvent = (snapshot: any) => {
this.ngZone.run(() => {
let results = this.handleSnapshot(snapshot.value);
observer.next(results);
});
};
firebase.addValueEventListener(onValueEvent, `/${path}`);
}).share();
}
getMyTransactionList(): Observable<any> {
return new Observable((observer: any) => {
let path = `transactions/${BackendService.token}`;
// this is a merge of jen loopers giftler code combined with nativescrip-firebase-plugins standard onQueryEvent. It works on the first load but routing to a second instance of the same function retrieves all the data without queryEvent
let onQueryEvent = (snapshot: any) => {
this.ngZone.run(() => {
let results = this.handleSnapshot(snapshot.value);
observer.next(results);
});
};
firebase.query(
onQueryEvent,
`/transactions/${BackendService.token}`,
{
singleEvent: true,
orderBy: {
type: firebase.QueryOrderByType.CHILD,
value: 'dealId' // mandatory when type is 'child'
},
range:
{
type: firebase.QueryRangeType.EQUAL_TO,
value: `${this.storage}` // this calls a static variable for testing consistency
}
,
}
);
firebase.addValueEventListener(onQueryEvent, `/${path}`);
console.log("transaction Listener added");
}).share();
}
handleSnapshot(data: any) {
//empty array, then refill and filter
this._allItems = [];
if (data) {
for (let id in data) {
let result = (<any>Object).assign({id: id}, data[id]);
this._allItems.push(result);
}
this.publishUpdates();
}
return this._allItems;
}
publishUpdates() {
// here, we sort must emit a *new* value (immutability!)
this._allItems.sort(function(a, b){
if(a.date < b.date) return -1;
if(a.date > b.date) return 1;
return 0;
})
this.items.next([...this._allItems]);
}
}
app.component.ts
<page-router-outlet></page-router-outlet>
overview.component.ts
export class OverviewComponent implements OnInit {
public deals: Observable<any>;
public transactions: Observable<any>;
constructor(private router: Router,
private firebaseS: FirebaseService,
){ }
ngOnInit() {
this.deals = <any>this.firebaseS.getMyDealList();
this.transactions = <any>this.firebaseS.getMyTransactionList();
}
viewDealSumm(id){
this.router.navigate(['dashboard/deal-summary', id]);
}
}
overview.component.html
<RadListView [items]="deals | async ">
<ng-template tkListItemTemplate let-item="item">
<StackLayout (tap)="viewDealSumm(item.id)">
<Label [text]="item.dealName"></Label>
</StackLayout>
</ng-template>
</ListViewGridLayout>
</RadListView>
<RadListView [items]="transactions | async " >
<ng-template tkListItemTemplate let-item="item">
<GridLayout>
<Label [text]="item.transName"></Label>
</GridLayout>
</ng-template>
</RadListView>
deal-summary.component.ts
export class DealSummaryComponent implements OnInit {
public transactions: Observable<any>;
constructor(
private firebaseS: FirebaseService,
){ }
ngOnInit() {
this.transactions = <any>this.firebaseS.getMyTransactionList();
}
deal-summary.component.html
<RadListView [items]="transactions | async " >
<ng-template tkListItemTemplate let-item="item">
<GridLayout >
<Label col="1" [text]="item.transName"></Label>
</GridLayout>
</ng-template>
</RadListView>

Aurelia: How to handle a async request in a view?

I have a dotnet core api that returns a FileContentResult..
return new FileContentResult(bytes, contentType)
{
FileDownloadName = Path.GetFileName(request.Filename)
};
Via postman I can read out the image perfectly fine. Now I want to read the image, via the aurelia fetch client, and show it in my html view. This is my function to retrieve the image from the api.
public image(filename: string) {
return this.http.fetch(AppConfiguration.base_url + 'assets/image',
{
method: 'post',
body: json({
filename: filename
})
});
}
I've tried to convert the blob in the response with this value converter. But I can't get that to work
Converter:
export class BlobToUrlValueConverter {
public toView(blob) {
return URL.createObjectURL(blob);
}
}
Viewmodel:
export class Dashboard {
public blob: any;
constructor(
public assets_service: AssetsService
) { }
async attached() {
let response = await this.assets_service.image('test.png');
this.blob = response.blob();
}
}
View
<div if.bind="blob">
${ blob | blobToUrl }
</div>
I'm not sure this is the right approach. Also not sure how handle the async request part of it either. What is the best way to get that image response to show in the html view? Lets say via a img tag?
I was close. Here is how I got the image to show.
Viewmodel:
export class Dashboard {
public url: string;
constructor(
public assets_service: AssetsService
) { }
async attached() {
let blob = await this.assets_service.image('test.png')
.then(response => response.blob());
this.url = URL.createObjectURL(blob);
}
}
View:
<div if.bind="url">
<img src.bind="url">
</div>
EDIT:
Found a better solution using parts written above:
The exported function that does the call (for reusability on both ts and html sides):
export function image_request(filename: string): Promise<Response> {
let http = new Http();
return http.fetch(<your-url-that-fetches-the-image>,
{
method: 'post',
body: json({
filename: filename
})
});
}
Value converter that uses above function
import { image_request } from './AssetsRequests';
export class ImageRequestValueConverter {
public toView(filename: string) {
return image_request(filename);
}
}
The important and most awesome part of the solution. Many thanks to http://www.sobell.net/aurelia-async-bindings/
for getting me on my way. You can override the binding behaviour. You can use this override to process async
Promise in a view in combination with a value converter.
export class AsyncImageBindingBehavior {
public bind(binding, source): void {
binding.originalupdateTarget = binding.updateTarget;
binding.updateTarget = (target) => {
// When we have a promise
if (typeof target.then === 'function') {
// Set temp value to loading so we know its loading
binding.originalupdateTarget('Loading...');
// Process the promise
target
.then(response => response.blob())
.then(blob => binding.originalupdateTarget(
URL.createObjectURL(blob)
));
}
else {
binding.originalupdateTarget(target);
}
};
}
unbind(binding) {
binding.updateTarget = binding.originalupdateTarget;
binding.originalupdateTarget = null;
}
}
Finally the view is very simple
<img src="${ 'test.png' | imageRequest & asyncImage }">

render() method is not correctly called after props update

I'm doing a very simple react+redux application where I've a reducer called goals and a container called GoalsContainer.
From App Container I call the action goal for load the initial goals from a local db(indexedDB)
dispatch(loadGoals(currentDate));
This call the loadGoals from the goals actions:
export function loadGoals(currentDate = new Date()){
return dispatch => {
var goals = getGoalsFromDB(normalizeDate(currentDate)); // with this I get an array from the db
dispatch(setLoadGoals(goals));
}
}
function setLoadGoals(goals) {
return {
type: types.LOAD_GOALS,
goals
};
}
And then in my reducer I've this:
export default function goals(state = [], action) {
switch(action.type) {
case types.LOAD_GOALS:
return action.goals; // here I set the state of the goal reducer with the array passed via action
default:
console.log('Im here');
return state;
}
}
and this is my GoalsContainer(read the comments in code):
class GoalsContainer extends React.Component {
render() {
if (this.props.goals != undefined) {
console.log('ok called the render'); // in chrome console shows it
console.log(this.props.goals); // in chrome console shows correctly the goals loaded
console.log(this.props.goals.length); // it say 2
if (this.props.goals.length > 0) { // here fails...
console.log('good');
console.log(this.props.goals);
var goalsView = <div>There are goals</div>
}
else {
console.log('why go here?'); // go here
console.log(this.props.goals);
var goalsView = <div>No goals</div>
}
} else {
var goalsView = <div>Undefined</div>
}
return (
<div id="goals-main">
{goalsView}
</div>
);
}
}
GoalsContainer.propTypes = propTypes;
function mapStateToProps(state) {
const { goals, environment } = state;
const { currentDate } = environment;
return {
goals,
currentDate
}
}
export default connect(mapStateToProps)(GoalsContainer);
The problem is that when it does the if check, it fails(like if there are 0 goals), but in chrome console show correctly the goals array...
Then if I force with some workaround the render(), all works correctly.
What I've done wrong ?
You didn't mention if you use https://github.com/gaearon/redux-thunk or not. To use reducer returning function you should definitely install it.
It's hard to follow all of the parts of your code from random gists. What happens if you change your GoalsContainer to be;
class GoalsContainer extends React.Component {
render() {
console.log(this.props.goals);
return (
<div id="goals-main">
{(this.props.goals.length >= 1)?<div>There are goals</div>:<div>Nope!</div>}
</div>
);
}
}
What gets logged to the console?

Data in one component wont bind to array in injectable

I have following issue:
I have one component, in which I am calling:
this.users = UsersInj.getUsersCollection()
In UsersInj, I have:
#Injectable()
export class UsersInj{
public users:any = [];
constructor(private _http:Http){
this.getUsers().subscribe(
success=>{
this.users = success.json();
},
error =>{
console.log('error')
}
)
}
getUsers(){
return this._http.get('/api/user');
}
getUsersCollection(){
console.log('GET USERS COLLECTION :',this.users);
return this.users;
}
}
However, this.users.length in my component is always 0. Any ideas?
UPDATE
It works when I pack this.users in UsersInj in an object.
PLNKR
In the plunker you copy the values (references) once when TheContent is created.
export class TheContent {
name: any;
constructor(public nameService: NameService) {
console.log("content started");
this.info = nameService.info
this.names = nameService.names;
}
changeMyName() {
this.nameService.change();
}
}
In NameService you assign a new array to this.names.
this.names = success.json();
this.names in TheContent and this.names in NameService are now not connected anymore.
What you probably want to do is
change(){
this.info.name = "Jane";
this.http.get('https://api.github.com/repos/vmg/redcarpet/issues?state=closed').subscribe(success=>{
while(this.names.length > 0) {
this.names.pop();
}
this.names.push.apply(this.names, success.json());
console.log('names: ' + this.names);
});
}
or alternatively copy the new array to TheContent again.
In Angular using an Observable that allows interested parties to subscribe to changes and pass the new value with the notification is the preferred way. See also https://stackoverflow.com/a/35568924/217408

Resources