In AngularJS i used$ rootScope to pass user data, for example:
$ rootScope.user = {
id: '4',
username: 'user'
...
};
$ rootScope.user.authenticated = false;
the data in $ rootScope was filled in every time a page was opened or updated using a query toSQL.
In Angular 9 i did not find the use of$ rootScope.
Tell me, where can such data be stored in Angular 9 and with what help can this functionality be implemented?
In angular, if you need anything like that, you create a service, provide it in root and inject it wherever you want it. For example:
The service:
// Create the service (providedIn: 'root') makes it available globally
#Injectable({providedIn: 'root'})
export class UserService {
user: any ={
id: '4',
username: 'user'
...
};
}
Using the service in a component:
#Component({...})
export class MyComponent implements OnInit {
_isAuthenticated: boolean;
// Inject the service
constructor(private _userService: UserService) {}
ngOnInit() {
// Using the service
this._isAuthenticated = _useService.user?.authenticated ?? false;
}
}
PS: The code above uses two interesting typescript features (which are new as I write this answer): optional chaining and Nullish Coalescing. You can always use a regular ternary operator instead of that:
this._isAuthenticated = _useService.user ? _useService.user?.authenticated : false
When my team and I migrated one of the projects from AngularJS to Angular, I took a look at when $rootScope was being used in the old app and it turns out it was used for identity/authentication 95% of the time. A few other use cases were regarding the spinner, browser related settings and edge cases.
It seems like your use case is similar to ours as well. I just folded that $rootScope.user into an existing AngularJS service called identity (or it could be auth, or whatever). So in each component that referred to that $rootScope.user, I replaced it with the following. The constructor is just dependency injection, allows you to use the variables within the identity service anywhere.
whatever.component.ts
currentUser = this.identity.currentUser
constructor(private identity: IdentityService) {}
The identity service looks something like the below. There's a getter function for the current user, and when it's not available, you look into the cookie, or otherwise, it's blank object (unauthenticated).
identity.service.ts
private _currentUser; //should only obtain currentUser via get currentuser()
constructor(private cookieService: CookieService) {}
get currentUser() {
if (!this._currentUser) {
this._currentUser = this.getUserFromCookie() || {}; //get from cookie
}
return this._currentUser;
}
getUserFromCookie() {
return this.cookieService.get('currentUser') ? JSON.parse(this.cookieService.get('currentUser')) : {};
}
Hopefully this gets you started and helps others as well.
Related
I have an events app which has an all-events component which lists all the created events. When the all-events component is loaded, the events are retrieved from my database.
This is the relevant code from the all-events Typescript file:
eventParams: EventParams;
getAllEventsObs: Observable<PaginatedResult<BeachCleanEvent[]>>;
ngOnInit() {
this.eventParams = new EventParams();
}
getEvents() {
this.getAllEventsObs = this.eventService.getAllEvents(this.eventParams);
console.log("getting events");
}
pageChanged(event: any) {
this.eventParams.pageNumber = event.page;
this.eventService.setEventParams(this.eventParams);
this.getEvents();
}
Here is the html from the all-events component:
<div class="background-img">
</div>
<div class="container">
<div class="events-container" *ngIf="getAllEventsObs | async as getAllEventsObs">
<div class="event-item" *ngFor="let event of getAllEventsObs.result">
<app-event-card [existingEvent]="event"></app-event-card>
</div>
<pagination *ngIf="getAllEventsObs.pagination as eventPagination"
[boundaryLinks]="true"
[totalItems]="eventPagination.totalItems"
[itemsPerPage]="eventPagination.itemsPerPage"
[(ngModel)]="eventPagination.currentPage"
(pageChanged)="pageChanged($event)">
</pagination>
</div>
</div>
The getAllEvents method is just a simple http request. I won't show this code as I know it works correctly.
This is the PaginatedResult class:
export interface Pagination {
currentPage: number;
itemsPerPage: number;
totalItems: number;
totalPages: number;
}
export class PaginatedResult<T> {
result: T;
pagination: Pagination;
}
This is the EventParams class:
export class EventParams {
pageNumber = 1;
pageSize = 4;
}
When the page initially loads, it loads correctly and displays the first four events:
The issue I am having is that when clicking on the next page, it gets stuck and won't load the next four events. No error is displayed on the console but the "getting events" console.log I created in the getEvents method above just keeps firing:
I suspect this is something to do with the way I am consuming the observable in the html code (using the async pipe). How would I go about resolving this? Is this where I should be using the switchMap RXJS operator? If so, how would I use it in this scenario?
You are on the right track... and yes, you should use switchMap :-)
Instead of re-assigning your source observable inside getEvents(), you could simply define it to depend on your params, and just push new params when they change. switchMap will execute the service method each time the params change.
But if the EventService is keeping track of the params anyway, it's probably the simplest to have it expose the events$ based on those params.
If you aren't already, define the params as a BehaviorSubject with default value and add a method for consumers to modify them. Then expose a single events$ observable that represents the events based on the specific params:
service:
private params$ = new BehaviorSubject(new EventParams());
public setEventParams(params) {
this.params$.next(params);
}
public events$ = this.params$.pipe(
switchMap(params => this.getAllEvents(params))
);
component:
events$ = this.eventService.events$;
pageChanged(params) {
// build proper params object if necessary
this.eventService.setEventParams(params);
}
Your component code becomes a lot simpler since it doesn't need to be concerned with managing the observable and keeping track of params.
I am getting this error trying to bind my control to its data. Here is some relevant code.
Template.
<tree-control [nodes]="getData"></tree-control>
Component.
public getData(): Observable<Array<any>> {
const assets: any = this.service.get('url', headers);
return assets;
}
Anything I have found so far is not helping. Any idea what's wrong with my code?
Thanks
First of all, you assign a function (getData) to the nodes property. I assume you want to assign the data from getData to it instead.
Secondly, the call to this.service.get is probably not being executed. Reason for that is that you do not subscribe to, what I assume, is a http-call that returns an Observable.
To fix this, you can do the following:
export class Foo {
nodeData: Observable<any>;
constructor(
private readonly service: YourService,
) {
this.nodeData = this._getData();
}
private _getData() {
return this.service.get(...);
}
}
Inside your template you can then subscribe and unsubscribe to the data automatically by using the async pipe.
<tree-control [nodes]="nodeData | async"></tree-control>
For all that to work I assume your service.get method returns an Observable.
Task.ts:
export class Task {
name: string;
dueDate: Date;
}
tasks.service.ts:
#Injectable()
export class TasksService {
constructor(private http: HttpClient) { }
getTasks(): Observable<Task[]> {
return this.http.get<Task[]>(`${WEBAPI_URL}/Tasks`);
}
}
The Task objects I get back from getTasks() have their dueDate field assigned but the value is of type string instead of Date like I would expect.
Some searching lead me to this issue on the Angular github which made clear to me that HttpClient has no intent of properly parsing my object. Unfortunately the issue didn't give helpful guidance about what I should actually be doing to get my Date object. What do I do?
You have several options here.
1) You can deal with the date as a string in the UI. So change the definition of Task to retain the date as a string and work with it that way, converting it to a date for calculations as needed.
2) You can map each object coming back from the Http request to a Task object. Something like this:
getTasks(): Observable<Task[]> {
return this.http.get<Task[]>(`${WEBAPI_URL}/Tasks`)
.pipe(
map(items => {
const tasks: Task[] = [];
return items.map(
item => {
item.dueDate = new Date(item.dueDate);
return Object.assign(new Task(), item);
});
}),
tap(data => console.log(JSON.stringify(data))),
catchError(this.handleError)
);
}
This also have the benefit of having actual task objects in your array, meaning that if you ever add Task methods or getters/setters they will be correctly associated with your tasks array.
EDIT:
It may be better to build a utility class that handled the serialization/deserialization of your objects. Then the above code would look more like this:
getTasks(): Observable<Task[]> {
return this.http.get<Task[]>(this.url)
.pipe(
map(TaskSerializer.serialize),
catchError(this.handleError)
);
}
declare it as a date in the component like this:
example.component.ts
constructor(private taskService: TaskService) {
}
ngOnInit() {
this.taskService.getTaksks().subscribe(response => {
tempValue = response.body;
tempValue.dueDate = new Date(tempValue.dueDate.format('MM-DD-YYYY');
});
}
OR save it as an instant
Task.ts
export class Task {
name: string;
dueDate: Instant;
}
I would suggest doing the first way. I would also suggest looking at the moment.js library
EDIT: I would declare it as a Date object and let it store it as a string on the db. That is how I have seen most use cases for dealing with dates and it is how everyone in my company has dealt with dates. 99% of the time you just want the month/day/year so it makes sense that you will store only that on the db, but it is a little cumbersome to format it to a date object on the ui side.
I found a not so heavy interceptor, allowing to have directly correct Date in objects when using HTTP calls in Angular. You can find it here: https://dev.to/imben1109/date-handling-in-angular-application-part-2-angular-http-client-and-ngx-datepicker-3fna
Please note that the only thing I had to change was the regex, in order to make the Z at the end optional and I use the DateTimeFormatter.ISO_DATE_TIME format on server side.
There is a set of components in my Angular 4 app in which each component
requires a certain set of properties from a different JSON file which it shows on its template.
I have created a common JSON file containing all the properties and I load it when the app-component is called using a service that holds those properties array.
I then inject that same service into different components and fetch the populated array. The values show in the HTML all fine.
However, this approach seems to be a bit time consuming especially when the constants grow in size. Loading thousands of constants all at once and injecting them into different components where few of them are required is not a good approach.
I was willing to work on an approach where I create specific contansts JSON file for each component and somehow load it when the component is actually initialized. This way I can save the burden of a heavy JSON object and only those properties would be loaded that are required by that component.
The load() method in my constants service looks something like this:
#Injectable()
export class ConstantsService {
constructor(private http: HttpClient) {
console.log('ConstantsService created');
}
constants = {};
load() {
var constants = {};
var cons = 'constants';
var constantsResourceUrl =
'path' + cons + '.json';
this.http.get(constantsResourceUrl)
.subscribe(result => {
this.constants = result;
},
error => this.log.error(constantsResource + ' could not be loaded')
);
}
}
And my Components look like this to get the value of the constants:
#Component({
selector: 'xyz',
templateUrl: './xyz.html',
styleUrl: './xyz.css'
})
export class MyComponent {
consts = {};
constructor(private constantsService: ConstantsService) {
consts = this.constantsService.constants;
}
}
Any help would be appreciated.
If did have similar dilema. To use db or json files for settings/parametars behavior. Dynamic content end up in db, and at the end I found that is little stupid to fetch and use http request to get static json content since that could be bundled in source. I did split it and use in multiple exported constant in ts. file like:
export const dummyLookupConst = `{
"queryNo": 0,
"id": 250,
...}
and then import it into components by need.
I am creating an HTTP request to the back end thru this angular implementation.
I prepared the app.module by first importing the {HttpClientModule} from '#angular/common/http' into the app.module and then adding it to the imports array making it available to the rest of the app.
then, I use the get method on HttpClient to access the data in my server
That's the game plan.
The question is about determining the data type to be returned by the server.
The component has this code... I cut the not-needed stuff for brevity.
#Component(...)
export class MyComponent implements OnInit {
// results: string[]; // I am not sure about this
constructor(private http: HttpClient) {}
ngOnInit(): void {
url = 'http://example.com/authentication/get-username.php';
http
.get<MyJsonData>(url)
.subscribe...
}
My question is..
What should I type-cast where it says ? in the above code?
Little bit more backgrond:
the URL returns something like this:
{"wordpress_username":"admin_joe","user_role":"admin"}
or if no user logged in, this
{"wordpress_username":"","user_role":""}
now, here, we got a a format like {"x":"y", "j":"k"}
this is a json string. but, it is also an object
And this is where my confusion starts.
Would it be better to build an interface for this?
If so, what would be the interface look like?
Using Typescript is preferred for its strong typing also on the level of the Typescript. It is better to have a type for that.
In both cases you have the same shape of your object with two properties wordpress_username and user_role. Your interface can look like this
export interface MyJsonData {
wordpress_username: string,
user_role: string
}
Also if you can have a response with only one property, you can make them optional appending ? at the end of the property name
export interface MyJsonData {
wordpress_username?: string,
user_role?: string
}