NativeScript: Difference between a class and a service? - javascript

I'm trying to get into Nativescript + Angular2, and I read the following in the tutorial:
We’ll build this functionality as an Angular service, which is Angular’s mechanism for reusable classes that operate on data.
What they then do is to create a simple class, like this:
import { Injectable } from "#angular/core";
import { User } from "./user";
#Injectable()
export class UserService {
register(user: User) {
alert("About to register: " + user.email);
}
}
Now, I can't really see the difference between a normal class and a service - this is a very normal class definition.
So, why is it called an "Angular service"?
This creates a basic Angular service with a single method that takes an instance of the User object you created in the previous section.
Also, when using this "service" in the tutorial, it isn't clear to me when this class is instantiated - when is the construction executed? Is the object saved in memory for later use? The only call to the "userservice" in the tutorial is like this:
import { Page } from "ui/page";
import { Component, ElementRef, OnInit, ViewChild } from "#angular/core";
import { User } from "../../shared/user/user";
import { UserService } from "../../shared/user/user.service";
import { Router } from "#angular/router";
import { Color } from "color";
import { View } from "ui/core/view";
#Component({
selector: "my-app",
providers: [UserService],
templateUrl: "./pages/login/login.html",
styleUrls: ["./pages/login/login-common.css", "./pages/login/login.css"]
})
export class LoginComponent implements OnInit {
user: User;
isLoggingIn = true;
#ViewChild("container") container: ElementRef;
constructor(private router: Router, private userService: UserService, private page: Page) {
this.user = new User();
this.user.email = "bla#bla.com";
this.user.password = "1234";
}
//.... methods and stuff...
}

A class, in that context, is a regular class as in any other OO language: a "prototype" of objects which you can create instances simply using:
let myInstance = new MyClass(<arguments...>);
So, actually, an Angular service is also a class.
But consider services a special kind of class. The main difference between regular classes and service classes is their lifecycle, specially who creates their instance (who calls new).
Instances of a service are created - and managed (disposed) - by the Angular "container" (angular injector, actually).
You can also "inject" instances of service classes in constructors of other service classes (or other managed components).
A good resource in the capabilites of services is Angular's Dependency Injection Guide.
When is the construction executed?
The injector executes the construction. When? See below.
Is the object saved in memory for later use?
It could be. Depends on where you registered the service.
Before anything, know that Angular DI is a hierarchical injection system.
If you register the service with an Angular Module, the service will be created by the application's root injector. So everyone below it (aka everyone in that module) will receive the same instance of the service. In other words, Angular (will call the injector only once and) will create only one instance of the service class and pass that same instance to whoever asks for that service. And that instance will live as long as that module lives.
Now, if you register the service with a component, then the service will be registered with that component's injector. So when such component requests an instance of the service, angular will call the injector and create an instance. If any child of that component asks for an instance of such service, angular will provide the same instance. No one else, only children of the component, will receive that same instance. When that component dies, the service instance dies as well.
How does a "regular class" differ? It lacks the Injector?
The difference is not only the lack of an injector.
Angular aside, just JavaScript: you create an instance of a "regular class" by calling let instance = new MyRegularClass() somewhere in your code, right?
This instance has no "magical effects", it does nothing more than any class would (just regular JavaScript methods and properties). Example: if you need instances of other classes as arguments in the constructor, no one will "magically" create you those instances and pass them. You will have to create them manually, when calling new (e.g. new MyClass(new SomeOtherClassIDependOn(), ...)). If you want to instantiate SomeOtherClassIDependOn only once and reuse the same instance everywhere it is needed, you will have to save that instance and pass it wherever it is neeed yourself.
As services, though, angular can take some of that burden off your shoulders.
Now, before anything: since every service, deep down, is a class, someone has to call new MyServiceClass(). The difference is that someone is not you anymore. There is no new UserService() in your code. So, who is it? This someone is the Injector.
When Angular notices someone asks for a service, it calls for the injector to instantiate that service. The injector then calls let serviceInstance = new MyServiceClass(<dependencies>) and adds some "magic" to it (e.g. it can pass - inject - instances of other services to the constructor of a service), and make it available (save it) for anyone that requests that service in the scope you registered it.
Note: You can call new UserService(...) yourself, as it UserService is a class. But this instance is a regular object, not managed by angular, there is no magic (no constructor arguments will be injected, no instance is saved and reused).

Related

Factory Design Pattern with Dependency Injection in Angular

I'm trying to use the factory design pattern in Angular, but I think I'm doing something wrong, or at least wondering if there's a better way. I have a factory which returns a car class depending on the type the user specifies (e.g., "mazda" returns a Mazda class, and "ford" returns a Ford class). Each car class uses multiples services.
constructor(
private userService: UserService,
private logService: LogService
) {}
create(
info
):
| Mazda
| Tesla
| Ford
switch (info.type) {
case 'mazda':
return new Mazda(info, this.userService, this.logService);
case 'tesla':
return new Tesla(info, this.userService, this.logService);
case 'ford':
return new Ford(info, this.userService, this.logService);
}
}
}
My issue is when I'm creating the factory in a component, I need to inject the dependencies.
this.carFactory = new CarFactory(this.userService, this.logService);
It seems weird that my component would need to know about which dependencies my factory needs. Is there a way to create a factory in Angular where you don't need to inject the dependencies into the factory? Something like
this.carFactory = new CarFactory();
you can solve this as following.
Declare above class as provider (Mazda,Tesla,Ford)
In factory class inject 1 dependancy "Injector".
In create function return as following
return this.injector.get(Tesla)
by this way you can resolve your dependancy dynamically.
You could define the CarFactory as a dependency:
#Injectable(providedIn: 'root') // or provided wherever you need it
export class CarFactory {...}
and then just inject it, where you want to use it:
constructor(private carFactory: CarFactory) {
const car = carFactory.create({type: 'tesla'});
}
Since the CarFactory is a dependency in the dependency injection hierarchy, it can inject, whatever is provided in this part of the Angular app.

How to create new instance of angular service?

So, I'm looking solution for the problem... Ok, let me share my thoughts and possible you could help me or say that I'm wrong.
Introduction:
When you are creating angular apps usually you create the service which calls like AuthService which contain methods login, logout, refreshToken and etc.
But, you also need to store somewhere your current users, it will be user who was logged in your application. Usually you will create something like UserModel with fields like firstname, lastname, balance, group, friends and etc.
And so, you are store instance of this model in AuthService.
After that for getting active user you need to inject your Auth service in your #component like this:
constructor(private _authService: AuthService) {
this.user = this._authservice.user;
}
Yep?
The problem:
So, I do not want inject authService for getting current user. I want to have CurrentUser service which will be allowed across all modules and will contain everything about logged user like UserModel.
I want something like this:
constructor(public user: UserService) {
}
BUT when user do logout I need to cleanup UserService and re-initialize every field to default value. So, this step it is a real problem.
Yes, sure, I can do for ... in cycle by object fields, but it's sucks!
I want to create new instance of UserService and make re-assign in global scope.
If you still do not understand - UserService was declared as provider in my root AppModule like this:
#NgModule({
imports: [
...
],
declarations: [
...
],
providers: [
UserService
]
})
export class AppModule {}
Question:
How can I create new instance for UserService and replace it in global?
When user logout I want to do this and all my components will get new, clean instance of UserService without filled fields and etc. And when new user will be logged in I'll use method of UserService class like user.updateUser(data) for filling this instance.
This can be achieved by declaring the service UserService to the highest level component your user is directed to after logging in (say HomeComponent). Continue to declare your authService as a provider in your root app.module.ts so it remains a singleton for the entire application life time.
By doing this the UserService will be created each time a User logs in and it will be available to all the children of the home component (which is why you will want to be sure to provide it at the highest level component), and it will be destroyed each time a user logs out. Creating a singleton for each user session.
#Component({
selector: 'home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css'],
providers: [UserService]
})
export class HomeComponent {...}
I want to stress the highest level component. Essentially from an Architecture perspective what you would want to do is (in this example) be sure to make the HomeComponent essentially a container component for all routes/components a logged in user would need to access thereafter. Otherwise, this technique will not succeed for say sibling components, as the sharing of the UserService is dependent upon the hierarchical component structure.
I think the real question is why would you want this behaviour?
If it's regarding state then you should use a state management tool for this like ngrx or ng2redux so that the state is abstracted away from your components and services.
Then you can cleanly logout and login with different events and handle what should happen with the states upon each action.
try to make use of BehaviorSubjects. this should go in your service class:
public userData: new BehaviorSubject({});
once you get the logged in user data, store in the behavior subject like this
in the component:
this.userService.userData.next(response);
and when you need to fetch it across other components, just use this:
this.userService.userData.value();
N.B.: when the user refreshes, be it a service or a behavior subject, all will refresh. so, try using it in seasion storage or in Node cache.

Angular 4 data binding concept

I am creating an Angular app and need some help with the data binding.
I have a Dashboard where I have different Widgets. Every Widget has a name and a date.
To configure/change this settings I have created a Sidebar that can be shown.
I am using a router to display the dashboard view. And the sidebar is not a children of the dashboard it is outside of the router.
When I click on the settings button on a Widget the Sidebar should open and should be able to show and change the settings of this Widget and than the Widget should run an update function.
I have already tried to work with a shared service but I dont think that this is the best solution because I have multiple Widgets each with different settings object.
Shared Service is the way to go about the things over here , As there is no parent child Relationship Between Components you cannot use Event Emitters.
If you want to take things up a notch and go for a cleaner Design pattern go Ngrx Suite. It is tailor maid for such scenarios but keep in mind it will reqire some additional dependencies and also bit more code.
Go for Ngrx is the app is Large else stick to shared services.
A shared service is a good call for this scenario, since you won't be able to bind to your click event in your router-outlet tag.
Let's say you have a component (pseudocode):
class MyWidget {
widgetData: Object;
constructor(private myService: MyService) { }
handleClick(event) {
this.myService.sendClick({event, data:this.widgetData})
}
}
Then in your service, you can use a Subject to keep store the data that is needed for your Sidebar component.
Example service (psuedocode):
class MyService {
subject = new Subject();
sendClick(data) {
this.subject.next(data);
}
getClick(data) {
return this.subject.asObservable();
}
}
Finally, your Sidebar will be able to Subscribe to the getClick() method from your service:
class Sidebar {
widgetData: Object;
constructor(private myService: MyService) {
myService.getClick()
.subscribe(v => doSomethingWithWidgetData(v));
}
}
For another resource: this blogpost has a good explanation of this concept.
You cannot use Event Emitters since there is no relationship between two components, so ideal way would be to use is Shared Service.

Angular 2, How to Share Array Data Between components using service?

In Angular2, Share Array data between components using service?
I'm designed this structure like below.
object structure
{
data: 'data'
keys: ['key1', 'key2', ... , 'keyN']
}
* Service has a array of total objects.
totalObject = [object1, object2, ... , objectN]
At first I initialized selectedObject of service like this.
selectedObject = totalObject;
Then I initialized selectedObject of Component B on constructor like this.
constructor(private wordService: WordService) {
this.words = wordService.selectedWords;
}
At the first, Component B displayed All objects correctly!!
But, when the service initialize new array to selectedObject, Component B cannot display selected objects.
// It's not working...
// remove
this.selectedWords.splice(0, this.selectedWords.length);
// add
for(let i in WORDS) {
if(WORDS[i].keys.indexOf(key) >= 0) {
this.selectedWords.push(WORDS[i]);
}
}
You can simply create a service and use it as a "singleton" service.
#Injectable()
export class DataService {
public selectedWords:string[] = [];
}
And you provide it at the top level of your application, this way only one instance will be used across your app:
#NgModule({
imports: [ BrowserModule ],
declarations: [ App, OtherComponent ],
bootstrap: [ App ],
providers: [ DataService ]
})
export class AppModule {}
Plunkr example
If I understand what you're trying to do, you are trying to manipulate an object, by reference, across two components, with a service as sort of a broker, such that changes by component A to Object X will be visible in component B. The service more or less acts as just a place to stash references.
You will achieve a lot more stability, make it easier to debug, and make it a lot more extensible, thinking this way:
Component A makes change to Object X (which it houses itself).
Component A updates model in Service (which as several people here say, acts as a singleton, or more technically correct, a "managed instance"), with a copy of Object X. The model/service now has the data, but that data has no external reference that can accidentally (or otherwise) mutate it.
When necessary, Service dispatches a "dirty data" notification, which Component BCDE...etc. is listening for. This notification contains the new data (this is "push").
Component BCDE...etc. uses that data. It is not reliant on a reference outside of it's control concern and it is not tightly coupled to that service.
Any other component that needs to modify data used by other components, just uses the same mechanism.
Any other component that wants to get the data on demand from the service, can grab a copy of it from a getter on that service (this is "pull").
I have tried to do what you're doing (pretty sure we all have). Over time it's just trouble (especially if you throw more components into the mix). Using notifications/events is more staightforward all around, even if it might seem more complex to initially set up. It's easier to test since you just test with the payload from a notification/event (easily triggered in a test), you don't have to set up the other component and have it modify the service/reference used in the target component.
But yeah, the whole "one reference on a singleton everything is looking at" thing is just trouble.

How to Inject services with Async call on constructor, Angular 2

This is the problem: I have a service that makes an HTTP request in the constructor:
constructor(public http: Http, public geolocation: Geolocation) {
this.http = http;
this.geolocation = geolocation;
//Http request... this will set variable forecast of the class when complete.
this.getForecast(16);
}
then I inject that service in a component like this:
constructor(public connector: ApiConnector) {
this.forecast = connector.forecast;
}
If I try to use the forecast member of the component class on the component decorator as I do here:
#Component({
selector: 'app',
directives: [MainForecast, DailyForecast],
template: `
<main-forecast [main-weather]="forecast"></main-forecast>
<daily-forecast [weather-list]="forecast.list"></daily-forecast>
`,
})
I get an error, "Cannot read property 'list' of undefined in..."
Is there any possibility to work with promises in the component constructor?
Angular2 advise to do heavy work inside ngOnInit NOT inside the constructor.
ngOnInit is a lifecycle hook / event.
You're trying to work around the angular lifecycle.
It seems you assume that your service is initiated during startup. This is not the case. Even if your service is provided on root level it may only be initialized just in time. You may want to call some public init method during your startup logic to ensure its initialization.

Categories

Resources