I'm using angular2-jwt to authorize the requests. I've got a get request which retrieves multiple documents from the API.
A document can have multiple images which also need to be fetched using an authorized request.
So obviously calling them directly with doesn't work.
I followed this example: https://stackoverflow.com/a/40862839/909723
I've got two questions:
Without the async i get : GET http://localhost:4200/[object%20Object] 404 (Not Found)
And with the async i get : Invalid argument '[object Object]' for pipe 'AsyncPipe' I tried it with the 'data:image/jpg;' and without
Part of the template
<md-card *ngFor="let document of documents">
<md-toolbar color="accent" *ngIf="getDocName(document)">
<span class="nowrap">{{getDocName(document)}}</span>
<span class="country-full-width"></span>
</md-toolbar>
<md-card-content>
<div *ngFor="let image of getImages(document)">
<img class="image" [src]="getImageSrc(image.image_id) | async" />
</div>
</md-card-content>
</md-card>
I've got a service which uses angular2-jwt - AuthHttp
#Injectable()
export class ImageService {
constructor(public authHttp: AuthHttp, public http: Http, public sanitizer: DomSanitizer) {}
getImageSrc(id, type){
let url = Config.apiUrl + "/images/" + id + "/thumb.jpg"
let headers = new Headers();
headers.append('Content-Type', 'image/jpg');
return this.authHttp.get(url, {
headers: headers,
responseType: ResponseContentType.Blob
})
.map(res => {
return new Blob([res["_body]"]], {
type: res.headers.get("Content-Type")
});
})
.map(blob => {
var urlCreator = window.URL;
return this.sanitizer.bypassSecurityTrustUrl(urlCreator.createObjectURL(blob));
})
}
}
This is the function called in the template
getImageSrc(id)
{
return this.imageService.getImageSrc(id)
//.subscribe (
// data => data,
// err => console.log(err)
//);
}
Hope someone can help
I have faced the same problem and this solution helped: http://blog.jsgoupil.com/request-image-files-with-angular-2-and-an-bearer-access-token
Although you may need to change the way in which options are added to http request (in UrlHelperService) according to your angular version 2/4.
Also you need to change:
Observable
to
Observable<any>
all ower the place.
And
private _result: BehaviorSubject = new BehaviorSubject(null);
to
private _result: BehaviorSubject<any> = new BehaviorSubject('');
Related
I'm using Angular 9 in my web app. I'm using a lot of services to connect to a web service. Sometimes a lot of identical requests are sent to a service. Maybe when a user click on a button repeatedly.
I want to cancel previews incomplete requests for all of my services. What's the best solution? (Maybe using RXJS)
This is one of my service functions:
constructor(private http: HttpClient) {}
getList(options?: ApiListOptions) {
return this.http.post<ApiResponse<UserGet[]>>(
environment.apiRoot + 'user/list',
options,
{ headers: this.headers() }
);
}
Thanks
Please try below with takeUntil rxjs operator you will be able to do the same.
export class YourComponent {
protected ngUnsubscribe: Subject<void> = new Subject<void>();
[...]
public httpGet(): void {
this.http.get()
.takeUntil(this.ngUnsubscribe)
.subscribe( (data) => { ... })
}
public ngOnDestroy(): void {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}
}
For your specific case, like frequent button clicks, you can use switchMap() combine with debounceTime() to limit the frequency of action and http calls. You can use Subject for action trigger instead of component method
<button click="getList.next()">click</button>
getList=new Subject()
getList.pipe(debounceTime(2000),switchMap(()=>
this.http.post<ApiResponse<UserGet[]>>(
environment.apiRoot + 'user/list',
options,
{ headers: this.headers() }
);
)).subscribe()
In my Angular 8 application, I have a basic caching interceptor:
export class CacheInterceptor implements HttpInterceptor {
constructor(private cache: CacheService) {}
public intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
if (req.method !== 'GET') {
return next.handle(req);
}
const cachedResponse = this.cache.get(req);
if (cachedResponse) {
console.log(cachedResponse);
return of(cachedResponse);
}
return next.handle(req).pipe(
filter(event => event instanceof HttpResponse),
map((response: HttpResponse<any>) => {
this.cache.addToCache(req, response);
return response;
})
);
}
}
I also have a service which retrieves data from an external API:
public getCases(options: ModuleArguments): Observable<CaseResponse> {
return this.http
.get<CaseResponse>(this.URL_BASE, {
params: options as HttpParams
})
.pipe(map(this.cleanData, this));
}
The 'cleanData' method just loops through the received data and amends some of the values to make them more human friendly (e.g. turns 'support_request' to 'Support Request').
What appears to be happening is the response is being added to the cache by the CacheInterceptor after the data has been 'cleaned' within the service. Therefore, when the same request is made again, and received from the cache, the service is attempting to clean data which has already been cleaned.
How do I ensure that the the HTTP Response has been intercepted and added to the cache before it has been amended by the service?
How about you approach this by moving the pipe(map(this.cleanData, this)) operation to the point when the Observable has completed and returned your CaseResponse. Likely, this will mean that the HttpInterceptor has been applied first.
i.e. In the place where you invoke getCases you could try something like this:
service.getCases(options).subscribe(resolvedData => {
// assuming cleanData(data: CaseResponse) signature
const cleanedData = this.cleanData(resolvedData);
// .. do something with cleanedData
});
Also, from a design perspective, you wouldn't want getCases to do more than exactly what it's supposed to - It's a service method that performs an HTTP request and returns the cases in the format they are sent to you. The reformatting of the data could be ideally done at the consumer of that service function - as it's very likely the consumer that needs it cleaned/reshaped.
In the component I upload the file and send it to the server.
export class SignalsComponent {
selectedFile: File = null;
addFileStatus = false;
progressBar: any;
constructor(private http: HttpService) {}
onFileSelected(event) {
this.selectedFile = <File>event.target.files[0];
this.addFileStatus = true;
}
sendCsvFile() {
const formData = new FormData();
formData.append('csv-file', this.selectedFile, this.selectedFile.name);
this.http.postData(formData);
this.http.progressBar.subscribe((data) => {
this.progressBar = data;
});
}
}
The post method is rendered in a separate service. The file goes to the server and immediately returns in json. In the service, I subscribe to the file using Subject.
postData(data: any) {
return this.http.post('/upload_csv/', data, {
reportProgress: true,
observe: 'events'
})
.subscribe(data => {
if(data.type === HttpEventType.UploadProgress) {
this.progress.next('Progress upload: ' + Math.round(data.loaded / data.total * 100) + '%');
} else if (data.type === HttpEventType.Response) {
this._dataChanged.next(data.body);
}
});
}
And bring out json in other components in the tables. One of the tables is on one page with the component in which I send the file to the server. The data is output in this table good. It`s component.ts of my table:
constructor(private http: HttpService) { }
signals: SignalList[] = [];
ngOnInit() {
this.http.onDataChanged.subscribe((data) => {
this.signals = data;
console.log(this.signals);
});
}
It`s html of my table:
<tbody>
<tr *ngFor="let signal of signals | paginate: { itemsPerPage:6, currentPage: p }">
<th><small class="font-weight-bold">{{signal?.exchange}}</small></th>
<td><small class="font-weight-bold">{{signal?.coin}}</small></td>
<td><small class="font-weight-bold">{{signal?.base}}</small></td>
<td><small class="font-weight-bold">{{signal?.tstamp | formatDate}}</small></td>
<td><small>{{signal?.highest}}</small></td>
<td><small>{{signal?.lowest}}</small></td>
<td><small>{{signal?.m30}}</small></td>
<td><small>{{signal?.h1}}</small></td>
<td><small>{{signal?.h3}}</small></td>
<td><small>{{signal?.h6}}</small></td>
<td><small>{{signal?.h12}}</small></td>
<td><small>{{signal?.h24}}</small></td>
<td><small></small></td>
<td><small>{{signal?.buy_price}}</small></td>
<td><small></small></td>
<td><small></small></td>
<td><small></small></td>
<td><small></small></td>
<td><small></small></td>
<td><small></small></td>
</tr>
</tbody>
</table>
<pagination-controls (pageChange)="p=$event"></pagination-controls>
Other tables are on other pages. As soon as I follow to the link to another page all the data in the tables is cleared. Because the component through which I uploaded the file to the server is reset. How do I make the component not be reset?
P.S. I`ve solved this problem. Instead of the Subject() should use a ReplaySubject().
After returning json-data It must save to data-base.
Your tables can read data (about files) from DB.
Your service needs method GET for getting data after updating page or routing.
For reading data you must save it to anywhere. Usually this place is DB.
I`ve solved this problem. Instead of the Subject() should use a ReplaySubject().
I'm trying to do a ToDoList in service with localstorage.
add.component.ts
export class AddComponent implements OnInit {
item: Item[];
constructor(
private router: Router,
private itemService: ItemService) {
}
addTodo() {
this.itemService.save();
this.router.navigate(['./list']);
}
ngOnInit() {}
}
item.service.ts
#Injectable()
export class ItemService {
private itemsUrl = 'items';
private headers = new Headers({'Content-Type': 'application/json'});
private todos: any;
private currentItem: any;
constructor(
private http: Http,
private item: Item) {
this.currentItem = (localStorage.getItem('currentItem')!==null) ? JSON.parse(localStorage.getItem('currentItem')) : [ ];
this.todos = this.currentItem;
}
save(): Promise<Item> {
return this.http
.post(this.itemsUrl, {headers: this.headers})
.toPromise()
.then((res: Response) => {
this.todos.push({
id: this.item.id,
title: this.item.title,
message: this.item.message,
done: false
});
this.todos.title = '';
this.todos.message = '';
localStorage.setItem('currentItem', JSON.stringify(this.todos))
return this.todos;
})
.catch(this.handleError);
}
private handleError(error: any): Promise<any> {
console.log('An error occured', error);
return Promise.reject(error.message || error);
}
}
item.ts
export class Item {
id: number;
title: string;
message: string;
}
add.component.ts
<div class="container">
<form (submit)="addTodo()">
<div class="form-group">
<label>Id:</label>
<input [(ngModel)]="id" class="textfield form-control" name="id">
</div>
<div class="form-group">
<label>Titulo:</label>
<input [(ngModel)]="title" class="textfield form-control" name="title">
</div>
<div class="form-group">
<label>Mensagem:</label>
<input [(ngModel)]="message" class="textfield form-control" name="message">
</div>
<button type="submit" class="btn btn-success">Save</button>
</form>
</div>
If I add localstorage in add.component.ts it works! But if a try to do it as a service I have an error : EXCEPTION: Uncaught (in promise): Error: DI Error Error: DI Error
I don't know what to do. I search how to do it but I don't found an answer that would help me.
Does anyone know how to fix it?
Localstorage is not an angular service. It's just a native JS object controlling the browser's localstorage. Therefor you can not inject it. If you want to have service functionality, you can wrap the native localstorage in an angular LocalStorageService
You don't want to inject Item into ItemService. You really want to have a get function that accepts an Item as an argument and retrieves it from localStorage.
As PierreDuc said, local storage is feature of the user browser. According to this it should work with all major browsers.
Because it browser feature and not part of angular it doesn't need to be injected.
More of how to use localStorage you can read here.
If you still want to use it as an angular service I recommend you to use this library.
I have service defined in Angular 2 like this:
import { Inject } from 'angular2/angular2';
import { Http ,Headers , HTTP_PROVIDERS } from 'angular2/http';
export interface CourseInterface {
courseId: number,
coursePrice: number,
authorName: string
}
export class CourseDetailsService {
http: Http;
constructor(#Inject(Http) Http) {
console.log(Http)
this.http = Http;
}
load() {
console.log("came here in service")
var headers = new Headers();
headers.append('Authorization', <my username password>);
this.http.get('https://some.api',{
headers : headers
}).map(res => console.log("Response came!!!"))
console.log("done . . .")
}
}
and in another component, I use this service like this:
import {CourseInterface, CourseDetailsService} from '../services/course';
#Component({
selector: 'dashboard',
viewBindings: [CourseDetailsService]
})
#View({
template: `
<h1>Dashboard page laoded</h1>
`
})
export class Dashboard {
constructor(service: CourseDetailsService) {
service.load();
}
}
and while running the application, I can see my Dashboard component gets displayed on the screen. But however from the CourseDetailsService, no http calls are getting fired.
But in the console I could able to see the following printed:
came here in service
done . . . .
But in my chrome networks tab, I couldn't able to see any request fired to the specified url. Where I am making mistake?
I'm using Angular 2 Alpha 47
Basically the part that triggers the request itself it's the subscribe, so to make it work you have to add it.
// Service
load() {
var headers = new Headers();
headers.append('Authorization', <my username password>);
return this.http.get('https://some.api',{
headers : headers
}).map(res => console.log("Response came!!!"))
}
// Component
// 'subscribe' triggers the request!
service.load().subscribe((result) => /* do something with result */);