I am building a site what lets you build a time table (for school subjects) I wrote a component that fetches data from database and then calculates corect placement in time table
<div class="timetable col-lg-8">
<div class="rowT">
<div style="width : 16%">Day</div>
<div *ngFor="let head of timeTableHeader"
[style.width]="headerWidth">
{{head}}
</div>
</div>
<div *ngFor="let sublists of allData" class="rowT">
<div *ngFor="let sublist of sublists"
id="{{sublist[0]}}"
class="timetable-cell"
[style]="getMyStyles(sublist[1])"
(click)="showDetails($event,sublist[3])">
{{sublist[0]}}
</div>
</div>
now I wrote a form which allows somebody to edit particular subject(e.g. time changed or class room) it works fine it saves the changes in DB adn now I want to show these changes in my view I thought I just call the function that calculates subject placements in time table but that results in rendering the time table again and leaving the old one there.
#Component({
selector: 'time-table',
templateUrl: './timetable.component.html',
styleUrls: ['./timetable.component.css']
})
export class TimeTableComponent {
allSubjects: Array<Subject>;
headerWidth: string;
timeTableHeader: Array<string> = new Array();
allData: Array<Array<Array<string>>> = new Array<Array<Array<string>>>();
constructor(private http: Http) {
this.fetchAndMake(); //function that fetches data and calls other function
//to make calculations... too long and not sure if relevant
// so I wont post it here
}
fetchAndMake(){
this.allSubjects = new Array<Subject>();
let params : URLSearchParams = new URLSearchParams();
params.set('userName', this.authService.currentUser.userName);
let reqOption = new RequestOptions();
reqOption.params = params;
this.http.get(this.configurations.baseUrl + "/SubjectModel/TimeTable", reqOption).subscribe(result => {
this.makeSubjects(result.json());
});
}
updateSubject(subj){
let subject = subj as SubjectData;
this.http.post(this.configurations.baseUrl + "/SubjectModel/UpdateSubject",helper)
.subscribe();
this.editSubjectView = false;
this.fetchAndMake();
}
}
Thanks in advance for the help.
First you should not be fetching the data directly from the component but rather from a data service. When you use a data service and inject it into components that use it the data service is a singleton. Not sure if that solves your problem but in any case it is a design issue you should look into before you go any further down this road.
Also, you are calling the primary function in the constructor. The only thing you should be doing in the constructor is to inject the data service. You need to implement the OnInit interface and call your function that fetches the data from the data service you injected in the constructor in the ngOnInit() method.
Check out some of the many Angular 4 tutorials and look for examples of creating a data service, making it a provider in app.module.ts. Then in your component
import { Component, OnInit } from '#angular/core';
import { MyDataService } from '../shared/my-data.service';
....
export class TimeTableComponent Implements OnInit {
...
constructor(private _mydata: MyDataService) { }
ngOnInit(){
// call method in data service that fetches data and do whatever you need to
// with the returned result. Now everything will be available to your view
}
There is a chance that
this.fetchAndMake()
gets called before
this.http.post(this.configurations.baseUrl +
"/SubjectModel/UpdateSubject",helper)
.subscribe();
is complete in updateSubject function. Subscribe just initiates the call , if you want to ensure that the new data is updated only after the post is complete, edit the updateSubject() function as follows :-
updateSubject(subj){
let subject = subj as SubjectData;
this.http.post(this.configurations.baseUrl + "/SubjectModel/UpdateSubject",helper)
.subscribe(result =>{
this.allData = new Array<Array<Array<string>>>();
this.fetchAndMake();
});
this.editSubjectView = false;
}
Related
I'm trying to have an observable that is based on a Subject, so that it gets updated every time the SUbject emits.
For some reason, when trying to fetch the value of the observable, first value is always null, and rest contain values. I shall expect to retrieve the latest emitted value of the observable. I've tried with all kind of SUbjects. What is what I'm missing?
...
activeBookings$: Subject<Booking[]> = new BehaviorSubject<Booking[]>(null);
activeBooking$: Observable<Booking> = this.activeBookings$.pipe(
filter((bookings) => bookings?.length > 0),
mergeMap((bookings) => this.getBookingById(bookings?.[0]?.id)),
share()
);
i = 0;
constructor(
public http: HttpClient,
public store: Store,
public asyncPipe: AsyncPipe,
public bookingStoreService: BookingStoreService
) {
this.getBookings().subscribe((bookings) => {
this.bookings$.next(bookings);
});
this.getActiveBookings().subscribe((bookings) => {
this.activeBookings$.next(bookings);
});
this.getPreviousBookings().subscribe((bookings) => {
this.previousBookings$.next(bookings);
});
}
...
Here is where I try to obtain the latest value:
...
const booking: Booking = this.asyncPipe.transform(
this.bookingService.activeBooking$
);
booking.status = status; //error: booking is null
...
You are using a BehaviorSubject here with an initial value of null:
activeBookings$: Subject<Booking[]> = new BehaviorSubject<Booking[]>(null);
You could initialise it with an empty array instead for exemple:
activeBookings$: Subject<Booking[]> = new BehaviorSubject<Booking[]>([]);
It would be more consistent type wise else your real type is:
activeBookings$: Subject<Booking[] | null> = new BehaviorSubject<Booking[] | null>(null);
Why are you getting null ? A BehaviorSubject needs to be initialised with a value and that is probably why the first value you get is null. A plain Subject doesn't need to contain an initial value and you would probably not get null
activeBookings$: Subject<Booking[]> = new Subject<Booking[]>()
A cleaner way to obtain the latest value without subscribing in your other service would be to leverage the features of the BehaviorSubject and the fact that calling activeBooking$.value makes you retrieve the latest value of the BehaviorSubject. Transform your activeBooking$ field so that it is a BehaviorSubject and when subscribing to the result of getActiveBookings(), you could do the computing that is currently in the pipe of the activeBooking$ definition there and call next on activeBooking$ with the result and expose a getter for your other service:
get activeBooking(): Booking {
return this.activeBooking$.value
}
I am trying to create a search bar. With every input change, a fetch API is triggered, which calls function in my Controller:
app.js
document.querySelector('#search')
.addEventListener('input', event => {
if(!event.target.value) return;
fetch('/search' + '/' + event.target.value);
});
Controller
<?php
namespace App\Http\Controllers;
use MeiliSearch\Client;
class SearchController extends Controller
{
protected $client;
public function __construct()
{
$this->client = new Client('http://127.0.0.1:7700');
}
public function search($searchFor)
{
$indexes = $this->client->getAllIndexes();
$searchResult = ['test' => 'test'];
foreach($indexes as $index) {
$searchResult[$index->getUid()] = $index->search($searchFor)->getHits();
}
return response($searchResult);
}
}
Next, the Controller returns records for the given parameters and I want to catch them in JS and send them into blade component. Returning view() with results data in it is not an option since view won't rerender itself every time and that's why I am trying to do this through JS.
The reason I need that data inside blade is because I want to loop through it and display results under the search bar
Return json_encoded value from your controller and get the result in fetch callback function then access any element from within the page and render the data.
*I am assuming the app.js file is included in your blade file.
I am wanting to use ion-slides with ion-img to produce a gallery since I am thinking there may be a fair no of images. ion-img in ionic 4 uses lazy-loading to fetch the images which I think is what I want.
I am fetching the images from an Azure storage account which needs to have a dynamic (expiring) key added to it when the url is fetched.
I have managed to make this work pretty well as follows, heres the html:
<ion-slides #photoslider [options]="sliderOptions"
(ionSlideDidChange)="checkKeyandFixText()"
(ionSlidesDidLoad)="checkKeyandFixText()">
<ion-slide *ngFor="let i of userService.user.Images">
<ion-img cache="true" width="50%" [src]="i.ImageUrl + validKey"></ion-img>
</ion-slide>
</ion-slides>
which checks the key (and fixes up an index for text use with each load or image change).
The check key function is this:
checkKeyandFixText() {
console.log(this.validKey);
this.userService.getStorageURLKey('').then(() => { // trigger key update
console.log(this.validKey);
});
.... more irrelevant stuff
}
the 'validKey' used in the html is updated by the 'getStorageURLKey function, because that function fires the service that gets the key and updates an observable, which is set up in the ngOnInit - part of which includes:
this.azureKey$ = this.userService.azurekey$.subscribe((res) => {
this.validKey = res.Key;
});
But this is unreliable, depending on the state of the key and all manner of async timing possibilities.
Its been a bit of a road to here, stating with the idea that writing the 'getStorageURLKey' function would work, by providing the url, it checking if the current key is valid or not then if not getting one, and adding it back.
That function by necessity returns a Promise which the ion-img seems unable to cope with. (Here is the function for completeness:
async getStorageURLKey(url: string): Promise<string> {
const nowplus5 = addMinutes(Date.now(), 5); // 5 mins to give some leeway
console.log(nowplus5);
console.log(url);
console.log(this.azurekey);
if (!this.azurekey || isBefore( this.azurekey.Expires, nowplus5 )) {
console.log('Getting new Key');
const keyObj = await this.getAzureKeyServer().toPromise();
await this.saveAzureKeyStore(keyObj);
this.azurekey = keyObj;
console.log(this.azurekey);
return url + keyObj.Key; // Return the url with the new Key or just the key if url is blank
} else {
console.log('Key is valid till ' + this.azurekey.Expires);
console.log(this.azurekey.Key);
const rval = Promise.resolve(url + this.azurekey.Key);
return rval ; // Key is in time so return it with url
}
}
If I just call the function from inside the html:
<ion-img cache="true" width="50%" [src]="userService.getStorageURLKey(i.ImageURL) "></ion-img>
it just loops forever due to angulars retry and event loops. If I pipe the result to async:
<ion-img cache="true" width="50%" [src]="userService.getStorageURLKey(i.ImageURL) | async "></ion-img>
Then it loops over the images the requisite no of times but doesn't do anything else... sometimes, then sometimes it seems to just make no difference and loops forever.
I have also tried adding my own custom pipe instead of the async but the inbound value always comes in as 'undefined'.
Changing the html just to reference "i.ImageURl | urkKey", here's the pipe I tried:
#Pipe({
name: 'urlKey'
})
export class URLKeyPipe implements PipeTransform {
userService: UserService;
transform(value: string): any {
console.log('At Pipe');
console.log(value);
this.userService.getStorageURLKey(value).then((key) => {
console.log(key);
return key;
});
}
}
I have tried a lot of things...:-s
Fundamentally I need to add an an async key to an async url and seem to be looped up in angular. Am I approching this wrongly, or should I just go try using plain old img instead?
Any ideas? Thanks in advance.
I have a component in which i subscribe to an Observable. In the callback i set member variables in that component. Those variables’ values will then be displayed on the component page.
But, when the app goes out of scope (e.g. by manually doing so or by the Android OS-Popup asking for location permission) and comes back in, the view is not updated anymore (though the callback still receives new values as the console log proves).
A working example would a blank starter app with the following content of the homepage class (requires the cordova geolocation plugin)
export class HomePage implements OnInit {
lat = 0;
long = 0;
private subscription: Subscription;
constructor(private geolocation: Geolocation,
private zone: NgZone)
{
}
ngOnInit()
{
console.log('constructor() Subscribing');
this.renewSubscription();
}
renewSubscription()
{
if(this.subscription)
{
this.subscription.unsubscribe();
this.subscription = null;
}
this.subscription = this.geolocation
.watchPosition({ enableHighAccuracy : true})
.subscribe(this.onLocationChange);
}
private onLocationChange = (location: Geoposition) =>
{
if(location.coords)
{
console.log(location.coords.latitude + ':' + location.coords.longitude);
this.lat = location.coords.latitude;
this.long = location.coords.longitude;
}
else
{
console.dir('no coordinates');
console.dir(location);
}
}
}
and the following as a replacement for the Home page html content
{{ lat }}:{{ long }}.
As a bit of searching, i suspect that after the app resumption many of the the code does not live in the angular zone anymore or something like this, because when setting lat and long inside the zone explicitly it works again
this.zone.run(() => {
this.lat = location.coords.latitude;
this.long = location.coords.longitude;
});
The thing is that this is a very over-simplified version of the app i'm working on - which has a lot of subscriptions and indirections, so i was not able to find the code i have to put inside the angular zone manually.
Is there a way to run the app's code within the angular zone after app resumption just like it would after a normal app start?
EDIT
Putting the code into platform.resume won't work either.
I am building a site using Angular 2. I have a detail page where I access it by passing the id in the url (http://localhost:3000/#/projectdetail/1). I try to access a service to get the correct project by the id that I pass through. This project is saved in a project variable, but the variable is actually undefined all the time.
This are the errors I get when I go to the detail page:
This are the code pieces that I use (if you need more code just ask):
The projectdetail.page.html:
<div class="container-fluid">
<h1>{{project.name}}</h1>
<div>{{project.description}}</div>
</div>
The projectdetail.page.ts:
public project: Project;
constructor(private projectsService: ProjectsService, private route: ActivatedRoute) {
for (let i = 0; i < 4; i++) {
this.addSlide();
}
}
ngOnInit() {
this.route.params.map(params => params['id']).subscribe((id) => {
this.projectsService.getProject(id).then(project => this.project = project);
});
}
The projects.service.ts:
getProject(id: number): Promise<Project> {
return this.http.get(this.url).toPromise().then(x => x.json().data.filter(project => project.id === id)[0]).catch(this.handleError);
}
The error you're getting is from the template.
At the time of template render, project is not available yet.
Try to wrap it in an *ngIf to avoid this.
<div class="container-fluid" *ngIf="project">
<h1>{{project.name}}</h1>
<div>{{project.description}}</div>
</div>
Template rendering is happening before you are getting data,
what you need is to create an empty object for project property,
option 1:
public project: Project = new Project (); // Provided its a class.
option 2:
If you may not instantiate Project (is an interface), you may use nullable properties
{{project.name}} -> {{project?.name}}
Hope this helps!!