I´m quite new to Angular 2 and I have a little Problem:
I load data from a service asynrochnus.
The data arrives in my component and i can handle with it.
Here i save the data in a local variable.
Here´s the code from the component:
export class ConfigurationComponent implements OnInit {
private tab: string = 'general';
private config = [];
constructor(private router: Router, private variables: Variables, private apiService: ApiService) {
if (!this.variables.getIsLoggedIn()) {
this.router.navigate(['login']);
}
}
ngOnInit() {
this.getConfigService();
}
getConfigService() {
this.apiService.getConfigService().
subscribe(
data => {
this.config = data.settings.section;
},
//error => this.error = <any>error,
error => this.variables.setFailure(error)
//() => console.log('done:' + this.status)
);
}
And here´s the code in html:
<th>{{config[5].entry.name}}</th>
<th>{{config[5].entry.text}}</th>
The problem is, that on the time when the view loads, the local variable 'config' is just '[]'. So the 'config[5]' fails.
But if i do not define the local variable in the component, it fails, too.
Can someone help me here?
Thanks!
You should push each of elements from data.settings.section into your config and check it does exist in view;
//in component
data.settings.section.forEach(i => this.config.push(i));
//in html
<div *ngIf="config[5]">
{{config[5].entry.name}}
</div>
I hope it helps!
Related
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>
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 }">
I'm just wondering if someone has already been able to change the color of a row, in React Griddle, by clicking on it (just once).
I'm experimenting things with JQuery, and even with Griddle Metadata, but it may be done in a cleaner way ?
Edit : I'm using React 15, Griddle inside MantraJS/Meteor, getting the data in my react Component using a Mantra container.
I can get the data by using onClick event, but not able to switch the background color in the onClick event, or playing with Metadatas.
Thanks !
EDIT : I use another view to display the content of the table, so for now I don't need to change the background of my tables cells, but if I found a solution I'll complete this post
You can use react-griddle props rowMetadata and onRowClick to do this:
class ComponentWithGriddle extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedRowId: 0,
};
}
onRowClick(row) {
this.setState({ selectedRowId: row.props.data.id });
}
render() {
const rowMetadata = {
bodyCssClassName: rowData => (rowData.id === this.state.selectedRowId ? 'selected' : ''),
};
return (
<Griddle
...
rowMetadata={rowMetadata}
onRowClick={this.onRowClick.bind(this)}
/>
);
}
}
Now this adds a selected class to the selected <tr> elements, so you can use custom styles to add colors or whatever styles you want to apply to the selected row.
Note that a more convenient API for selecting rows has been called for in the Griddle Github issues.
For whatever reason, I couldn't get Waiski's answer to work for me at all. I'm assuming that something must have changed in Griddle over the past two years. It looks like the current prevailing advice on the Web is to "implement row selection as a plugin", but I couldn't find any examples of that either. After a long hard look at the code for the Position plugin’s TableEnhancer on GitHub and a bunch of trial and error I eventually managed to cobble together the following row selection plugin for Griddle in TypeScript:
import * as React from "react";
import * as Redux from "redux";
import Griddle, { connect, GriddlePlugin, components } from "griddle-react";
export type RowId = string | number;
export type RowClickHandler = (event: React.MouseEvent<Element>, rowId: RowId) => void;
export type RowIdGetter<TData> = (rowData: TData) => RowId;
export interface IRowEnhancerProps {
rowClickHandler: RowClickHandler;
rowId: RowId;
isSelected: boolean;
}
export class RowSelector<TData> {
private _rowClickHandler: RowClickHandler = null;
private _rowIdGetter: RowIdGetter<TData>;
constructor(rowClickHandler: RowClickHandler, rowIdGetter: (rowData: TData) => RowId) {
this._rowClickHandler = rowClickHandler;
this._rowIdGetter = rowIdGetter;
}
public rowIdToSelect: RowId;
public plugin: GriddlePlugin = {
components: {
RowEnhancer: (OriginalComponent: React.ComponentClass<components.RowProps>) =>
this.rowSelectionEnhancer(OriginalComponent)
}
}
private rowSelectionEnhancer(
OriginalComponent: React.ComponentClass<components.RowProps>
): React.ComponentClass<components.RowProps> {
const rowDataSelector = (state, { griddleKey }) => {
return state
.get('data')
.find(rowMap => rowMap.get('griddleKey') === griddleKey)
.toJSON();
};
return Redux.compose(
connect((state, props) => {
const rowData: TData = rowDataSelector(state, props as { griddleKey });
const rowId: RowId = this._rowIdGetter(rowData);
return {
...props,
rowClickHandler: this._rowClickHandler,
rowId: rowId,
isSelected: rowId.toString() === this.rowIdToSelect.toString()
};
})
)(class extends React.Component<IRowEnhancerProps, any>{
public render() {
return (
<OriginalComponent
{...this.props}
onClick={(event) => this.props.rowClickHandler(event, this.props.rowId)}
className={this.props.isSelected ? "selected" : ""}
/>
);
}
});
}
}
Here's a rough outline of how it's used by a component. (Note that I had to selectively extract this example from a much larger and more complicated component, so there might be some errors/inconsistencies; sorry about that. It should still give a good overall idea of the approach.)
import * as React from "react";
import Griddle, { RowDefinition, plugins, GriddlePlugin} from "griddle-react";
import * as MyGriddlePlugins from "../GriddlePlugins";
export interface IPartInfo {
serialNumber: number,
name: string,
location: string
}
export interface IPartListProps{
parts: IPartInfo[],
selectedSerialNumber: number
}
export class PartList extends React.Component<IPartListProps, void > {
private rowSelector: MyGriddlePlugins.RowSelector<IPartInfo>;
private rowIdGetter: MyGriddlePlugins.RowIdGetter<IPartInfo>;
constructor(props?: IPartListProps, context?: any) {
super(props, context);
this._rowClickHandler = this._rowClickHandler.bind(this);
this.rowSelector = new MyGriddlePlugins.RowSelector(
this._rowClickHandler,
this._rowIdGetter);
}
private _rowClickHandler: MyGriddlePlugins.RowClickHandler =
(event: React.MouseEvent<Element>, selectedSerialNumber: MyGriddlePlugins.RowId) => {
if (selectedSerialNumber !== this.props.selectedSerialNumber) {
/*
Set state, dispatch an action, do whatever. The main point is that you
now have the actual event from the click on the row and the id value from
your data in a function on your component. If you can trigger another
render pass from here and set a fresh value for this.rowSelector.rowIdToSelect
then the "selected" CSS class will be applied to whatever row this click
event just came form so you can style it however you like.
*/
}
}
private _rowIdGetter: (rowData: IPartInfo) => MyGriddlePlugins.RowId =
(rowData: IPartInfo) => rowData.serialNumber;
public render(): JSX.Element {
this.rowSelector.rowIdToSelect = this.props.selectedSerialNumber;
return (
<div>
<Griddle
data={this.props.parts}
plugins={[plugins.LocalPlugin, this.rowSelector.plugin]}
>
<RowDefinition>
<ColumnDefinition id="name" title="Part Name" />
<ColumnDefinition id="location" title="Installed Location" />
<ColumnDefinition id="serailNumber" title="Serial Number" />
</RowDefinition>
</Griddle>
</div>
);
}
}
So, what's actually going on here? The component creates an instance of the plugin class at instantiation time, passing in an event handler to capture the click on the row and an accessor function to retrieve your ID value (not an inscrutable internal ID) from a row of your data. Just before the component returns its rendering, a value is set on the component's instance of the plugin, that way, when Griddle renders the plugin has the data to figure out when it's on a selected row and then adjust the CSS accordingly. The handler function from your component is then assigned to the row's onClick handler so your component can get the data from the click and do whatever it needs to do.
This passes the "It works for me" test (on React 15.6) which in my case is a straightforward master/detail view driven by a traditional table implemented through Griddle. I have no idea how well it would work with some of Griddle's more advanced features.
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
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);
}