I am following the Meteor - Angular2 tutorial and things work fine.
The only point not working is the automatic binding with Angular2 UI for the 'details view'. For instance, if I navigate to the details view of Party1 the data of Party1 is correctly loaded and made visible on the Angular2 'details view'. If, afterwards, the data of Party1 is changed (e.g. via Mongo shell) such change is sent to the browser (via WebSockets) where 'details view' is displayed, but the new data is not shown on the view.
Here is the code of the PartyDetailsComponent class.
export class PartyDetailsComponent extends MeteorComponent implements OnInit, CanActivate {
partyId: string;
party: Party;
constructor(private route: ActivatedRoute, private ngZone: NgZone) {
super();
}
ngOnInit() {
this.route.params
.map(params => params['partyId'])
.subscribe(partyId => {
this.partyId = partyId;
this.subscribe('party', this.partyId, () => {
this.party = Parties.findOne(this.partyId);
}, true);
});
}
saveParty() {
Parties.update(this.party._id, {
$set: {
name: this.party.name,
description: this.party.description,
location: this.party.location
}
});
}
canActivate() {
const party = Parties.findOne(this.partyId);
console.log(party);
return (party && party.owner == Meteor.userId());
}
}
Here is the template of of PartyDetailsComponent
<form *ngIf="party" (submit)="saveParty()">
<label>Name</label>
<input type="text" [(ngModel)]="party.name" name="name">
<label>Description</label>
<input type="text" [(ngModel)]="party.description" name="description">
<label>Location</label>
<input type="text" [(ngModel)]="party.location" name="location">
<button type="submit">Save</button>
<a [routerLink]="['/']">Cancel</a>
</form>
Thanks in advance for any help
I actually found the answer to my questions just reading more of the Tutorial.
I can get automatic update ofthe UI once the underlying Mongo doc changes just adding Meteo autorun() method appropriately in the subscription code.
Here is the code that works
ngOnInit() {
this.route.params
.map(params => params['partyId'])
.subscribe(partyId => {
this.partyId = partyId;
this.subscribe('party', this.partyId, () => {
this.autorun(() => {
this.party = Parties.findOne(this.partyId);
}, true);
}, true);
});
}
What is not totally clear to me is why if you use directly the Meteo Mongo cursors (e.g. via *ngFor in the template) autorun is not needed.
Related
So I have created a website where you can upload images.
Here's the problem:
I'm using hidden field of input type=file
And I have a designed button which trigger this input.
Now, I have a div that displays those images..
Problem is that I get only the last selected images from the user at my controller.
What happens if the user wants to upload from different directories?
I couldn't find answer after searching, Also I can't show the code right now,
I'm using really simple code tho,
Input of file (multiple)
Button that triggers it
Div that shows the pictures (appended with jquery)
This is a simplified example:
Html:
<button id="inputMask">Upload Image</button>
<div id="ImageHolder">
</div>
<form id="holder" method="post" enctype="multipart/form-data">
<h1>Form</h1>
<input type="submit" />
</form>
JS:
$(document).ready(() => {
let inputs = [];
//Model binding name
let name = "file";
let loadImagesFromInputs = () => {
$("#ImageHolder").empty();
//Lets load those images
if (!FileReader) {
console.log("Cant load files in the navigator");
return;
}
//For each input
inputs.forEach(i => {
//for each file in each input
Array.from(i.files).forEach(f => {
//prepare the file reader
let fr = new FileReader();
//this will run when conversion is finished
fr.onload = function () {
$("#ImageHolder").append($("<img>", {
src: fr.result
}));
}
//convert file to url
fr.readAsDataURL(f);
});
});
}
$("#inputMask").click(() => {
//Create file input
let newInput = $("<input>", {
type: "file",
name: name,
id: name,
multiple: true,
accept: "image/x-png,image/gif,image/jpeg"
}).css({ width: "1", position: "absolute", opacity: "0" });
//update the list of images on change
newInput.change((e) => { loadImagesFromInputs() });
//Add input to list of inputs
inputs.push(newInput[0]);
//Add input to form
$("#holder").append(newInput);
//Click input
newInput.click();
});
$("#holder").submit((e) => {
e.preventDefault();
e.target.submit();
})
});
.Net Core 2 Controller:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace MultipleFileDirectoriesDemo
{
[Route("[controller]")]
public class FileController : Controller
{
// GET: /<controller>/
[HttpGet]
public IActionResult Index()
{
return View();
}
[HttpPost]
public IActionResult Index(List<IFormFile> file)
{
//Do something with the list of files
return View();
}
}
}
The built in multi-file up-loaders on most browsers are pretty bad.
Consider looking at something like jQuery.Fileuploader
This will have the added benefit of being relatively consistent across devices and browsers.
I have a Vue component that will display data depending on what the user previously clicked on in a previous component. When they originally click, I set the 'current' index. Then when they get to the next page, I have a getter that will look in the data array and return the 'current' data.
The component they are redirected to is a form that they can edit. I would like to be able to have the 'current' data be pre-populated. NOT as a placeholder but as the actual value so that they can edit it directly.
The issue is I don't know how to set the values returned from the getter to the data function values so that they can be bound with v-model.
HTML
<input type="text" class="form-control" id="nickname1" v-model="name" name="name">
<input type="text" class="form-control" id="address1" placeholder="" v-model="address" name="address" >
<input type="text" class="form-control" id="city1" placeholder="" v-model="city" name="city" >
VUE
data: function() {
return {
name: this.currentArea.title,
address: this.currentArea.address,
city: this.currentArea.city,
state: this.currentArea.state
}
},
computed: {
currentArea() { return this.$store.getters.currentArea }
}
*this.currentArea.title and currentArea.title do not work.
*if I bind the placeholder, the data displays correctly, so the getter function is returning currentArea correctly
The data method is only fired once during initialization, before the computed properties are set up. So, referencing currentArea from within the data method won't work as it will be undefined at the time of execution.
If this.$store.getters.currentArea isn't expected to change in the lifespan of this component, it would be simplest to just set the data properties once in the mounted hook:
data() {
return {
name: '',
address: '',
city: '',
state: ''
}
},
mounted() {
let area = this.$store.getters.currentArea;
this.name = area.name;
this.address = area.address;
this.city = area.city;
this.state = area.state;
}
Or, if you know that the data properties for this component will always be the same as the currentArea, you could simply return this.$store.getters.currentArea in the data method directly:
data() {
return this.$store.getters.currentArea;
}
#thanksd: thank you for your answer.
I am working on a scenario where the state is stored in vuex, temporarily sent incomplete to the component and then updated through a fetch.
And it should be editable in a form.
My solution was to export part of the state with a getter in vuex:
getters: {
getItemDetail: (state, getters) => (id) => {
let item = state.openedItems.items.find(i => i.id === id);
return item ? item.data : null;
}
}
using it in the component by combining data, computed and watch properties (and deep cloning the object with the help of lodash):
data () {
return {
itemDetail: null
};
},
computed: {
itemData () {
return this.$store.getters.getItemDetail(this.item.id);
}
},
watch: {
itemData (n, o) {
this.itemDetail = _.cloneDeep(n);
}
}
Finally I bind the input to "itemDetail" (using an elemen switch, in my example):
<el-switch v-model="itemDetail.property"></el-switch>
To me (but I am quite new to Vue), it seems a good and working compromise.
I'm having some trouble displaying my data in the browser. To explain my problem I'm using some dummy code. I have some nested objects that are causing my problem. Here I'll display one nested object to showcase my problem.
First of all, I only make http calls for the Car-object. So saveCar acts like updating the car as well, depending on what the user does in the app. All the methods in the service works as they should.
So my service looks something like this:
#Injectable()
export class Service {
constructor(private http: Http) { }
saveCar(car: Car) {
return this.http.post ....
}
getCars(){
return this.http.get...
}
getById(id: string) {
return this.http.get...
}
}
Then I have a Car-class, where the nested object "Brand" comes in to play, Brand then has it's own class, but I'll leave it out.
export class Car {
private brands: Array<Brand>;
constructor(public id: string, public name: string) {
this.brands = new Array<Brand>();
}
public getBrands(): Array<Brand> {
return this.brands;
}
public addBrand(value: Brand): void {
this.brands.push(value);
}
//some other methods.
}
Then I have a list-component that lists all cars, this works as it should!
#Component({
selector: 'car-list',
template: `
<h1>Add Car</h1>
<form (submit)="saveCar()">
<input required [(ngModel)]="name" placeholder="Add car">
</form>
<br>
<table>
<tr *ngFor="let car of cars" >
<td>{{car.name}}</td>
<td><button (click)="goToDetail(car)">Detail</button></td>
</tr>
</table>
`,
})
export class ListComponent implements OnActivate {
id: string
name: string;
cars: Array<Car>
constructor(public _service: Service, public _router: Router) { }
routerOnActivate(): void {
this._service.getCars()
.subscribe(cars => this.cars = cars);
}
saveCar() {
let car = new Car(this.id, this.name)
this._service.saveCar(Car)
.subscribe(car => this.cars.push(car));
this._service.getCars()//
.subscribe(cars => this.cars = cars);
}
goToDetail(car:Car) {
this._router.navigate(['/cardetail', car.id]);
}
}
The problem I have is in the detail-component, where the user gets navigated after clicking a specific car. The routing and retrieving the Car from the db works as it should. That I know, because if I remove all the template except <h1>Car: {{car?.name}}</h1> the name gets printed out fine with the elvis operator.
But my detail-component looks something like this:
#Component({
selector: 'car-detail',
template: `
<h1>Car: {{car?.name}}</h1>
<hr>
<button (click)="addBrand()">Add Brand</button>
<div *ngFor="let brand of car.getBrands(); let i=index">
<h2>Brand {{i+1}}</h2>
</div>
`,
})
export class DetailComponent implements OnActivate {
#Input() car: Car;
constructor(public _service: Service, public _router: Router) { }
routerOnActivate(curr: RouteSegment): void {
let id = curr.getParam('id');
this._service.getById(id)
.subscribe(car => {
this.car = car;
});
}
addBrand() {
this.car.getBrands().push(new Brand());
}
//some other methods
}
So in my detail component I call all methods like: car.someMethod() and further on the nested Brand object like: brand.someMethod() in the template. So the error comes at the call of the method e.g in the template 'cannot get getBrands of undefined' I've tried putting the elvis operator like this: car?.getBrands() It doesn't work. I've tried to wrap the whole thing in a div, both with elvis operator and a <div *ngIf = "car"></div>, doesn't work. Even tried with <template *ngIf="car"></template>, well that doesn't work either....
Edit: my mess-up, wrapping like below, it does "kind of" work, meaning, it gives a new error....
Template:
#Component({
selector: 'car-detail',
template: `
<h1>Car: {{car?.name}}</h1>
<hr>
<button (click)="addBrand()">Add Brand</button>
<div *ngIf="car">
<div *ngFor="let brand of car.getBrands(); let i=index">
<h2>Brand {{i+1}}</h2>
</div>
</div>
You mention <h1>Car: {{car?.name}}</h1> with ? but the full code example has <td>{{car.name}}</td> without ? which will cause an error.
<div *ngFor="let brand of car.getBrands(); let i=index">
also needs a ? to avoid errors when Angular tries to render the view and car is not yet set
<div *ngFor="let brand of car?.getBrands(); let i=index">
I am developing an Angular2 app, and I faced a problem:
I have a set of different objects that can be selected using UI. Each of this objects has a set of options (different for different objects) that could be edited using UI. Now, I am using DynamicComponentLoader to insert a specific component for currently selected object, so it can handle its options correctly.
The problem is that I don't know how to bind data of currently selected object to a dynamically inserted options component.
#Component({
selector: 'dynamic',
template: `<div>Options:</div>
<div>Property1: <input type="number" /></div>
<div>Property2: <input type="text" /></div>`
// template: `<div>Options:</div>
// <div>Property1: <input type="number" [(ng-model)]="currentSelection.property1" /></div>
// <div>Property2: <input type="text" [(ng-model)]="currentSelection.property1" /></div>`
})
class DynamicComponent {
}
#Component({
selector: 'my-app',
template: `
<div>
<h2>Selected: {{currentSelection.name}}!</h2>
<div #container></div>
</div>
`
})
class App {
currentSelection = {name: 'Selection1', property1: 10, property2: 'test'};
constructor(private loader: DynamicComponentLoader, private elementRef: ElementRef) {
loader.loadIntoLocation(DynamicComponent, elementRef, 'container');
}
}
Here is a plunker to help you understand my question:
With angular2 and Rxjs, "Observables" are almost always the answer.
If i understood your problem correctly, you need to make your DynamicComponent an "Observer" and your container "an Observable or even better a Subject (In case your container needs to subscribe to another observable to receive selections from)". Then, after loading your dynamic component, subscribe it to your container.
Whenever the selection changes on your container, you push the new selection to your subscribers. This way, you can load multiple dynamic components and all will receive your pushes.
The Container:
class App {
currentSelection = {};
selections = [
{name: 'Selection1', property1: 10, property2: 'test'},
{name: 'Selection2', property1: 20, property2: 'test2'}
];
subject:Subject<any> = new Subject();
constructor(private loader: DynamicComponentLoader, private elementRef: ElementRef) {
}
ngOnInit(){
this.loader.loadIntoLocation(DynamicComponent, this.elementRef, 'container', this.injector)
.then(compRef =>this.subject.subscribe(compRef.instance));
// subscribe after loading the dynamicComponent
}
// set the new selection and push it to subscribers
changeSelection(newSelection){
this.currentSelection = newSelection;
this.subject.next(this.currentSelection);
}
}
The Observer:
class DynamicComponent implements Observer{
public currentSelection = {};
next(newSelection){
this.currentSelection = newSelection;
}
}
Here is your plunker working after my edits, "provided I changed the imports to the newest angular beta.6"
I know this is a quite old question. But hopefully someone will benefit from this answer.
Here is what you can do, move your code from constructor to ngOnInit and use promises for assigning dynamic value.
ngOnInit(){
this.dynamicComponentLoader.loadIntoLocation(DynamicComponent, this.elementRef,'container').then((component)=>{
component.instance.currentSelection = currentSelection;
});
}
A very basic and standard component which should call the change handler when the input changes.
It works in my pen: http://codepen.io/sdbondi/pen/MaGovq
It doesn't in my meteor app - in fact any handler (onClick, etc) doesn't work if rendered after the initial page load - { (this.state.cond) ? <Element onChange={..}/> : ''} will also render but not fire the change.
Interestingly changes fire if I set the entries in the initial state, but with setTimeout they render but no onChange.
I've stepped through react to try and understand how events are bound (I actually got to addEventListener eventually) but it'll take a while to understand what is happening enough to debug.
export default React.createClass({
displayName: 'VotePage',
getInitialState() {
return {
entries: []
};
},
handleChange(e) {
console.log(e);
},
componentDidMount() {
// this will eventually be replaced by meteor data (ReactMeteor.createClass etc)
window.setTimeout(() => {
this.setState({'entries': [{_id: '123', name: 'guyuy'}, {_id:234, name: 'sadfsd'}]});
}, 1000);
},
render() {
var voteEntries;
if (this.state.entries && this.state.entries.length) {
voteEntries = this.state.entries.map((entry) =>
<input key={entry._id} name="entry" type="text" onChange={this.handleChange} defaultValue={entry.name} />
);
} else {
voteEntries = 'loading...';
}
return (
<div>
<h2>Vote</h2>
<div className="island_-small">
{voteEntries}
</div>
</div>
);
}
});
React: v0.13.0 (tried 0.13.3 too)
------------> versions excerpt
react#0.1.13
react-meteor-data#0.1.9
react-runtime#0.13.3_7
react-runtime-dev#0.13.3_7
react-runtime-prod#0.13.3_6
reactive-dict#1.1.3
reactive-var#1.0.6
reactjs:react#0.2.4
kadira:flow-router#2.7.0
kadira:react-layout#1.4.1
----------> packages full file
meteor-base # Packages every Meteor app needs to have
mobile-experience # Packages for a great mobile UX
mongo # The database Meteor supports right now
blaze-html-templates # Compile .html files into Meteor Blaze views
session # Client-side reactive dictionary for your app
tracker # Meteor's client-side reactive programming library
standard-minifiers # JS/CSS minifiers run for production mode
es5-shim # ECMAScript 5 compatibility for older browsers.
dburles:collection-helpers
aldeed:collection2
accounts-base
accounts-password
alanning:roles
# twbs:bootstrap
# fortawesome:fontawesome
wylio:mandrill
# kadira:blaze-layout
# sach:flow-db-admin
check
stevezhu:lodash
accounts-facebook
service-configuration
kadira:flow-router
universe:modules-npm
ecmascript
fixate:app-deps
universe:modules
yasaricli:slugify
maxharris9:classnames
reactjs:react
kadira:react-layout
jquery
react-meteor-data
meteorhacks:subs-manager
Was something in reactjs:react. See https://github.com/reactjs/react-meteor/issues/105
I made this mixin based on froatsnooks response on github:
export const MeteorReactSubscriber = {
componentWillMount() {
this._subsMap = {};
this.subscriptions();
this._startMeteorTracker();
},
_startMeteorTracker() {
this._subsHandle = Tracker.autorun(() => {
this.subscriptionReady && this.subscriptionReady();
if (this.meteorSubsReady()) {
this.meteorDataReady && this.meteorDataReady();
}
});
},
meteorSubsReady() {
return _.all(this._subsMap, (s, _) => s.ready());
},
meteorSubscribe(name, selector=undefined) {
this._subsMap[name] = Meteor.subscribe(name, selector);
},
componentWillUnmount() {
this._subsHandle.stop();
this._subsMap = null;
}
};
Usage
var SweetComponent = React.createClass({
mixins: [MeteorReactSubscriber],
subscriptions() {
this.meteorSubscribe('entries');
},
meteorDataReady() {
var currentEntry = Entry.findOne(this.props.entryId);
this.setState({entry});
},
render() { ... }
});