I use WebStorm (great IDE) and I'm working on a custom pet project of mine which will become a game eventually. But I'm developing a bare bones Alpha version to show off to potential employers because I'm looking for work and want to add this project to my resume. Anyway, I'm using the Builder design pattern to create very complex objects. Some of these objects are spread across several different services because it makes more sense to do it that way. The game I'm making is going to be a text-based RPG where players can create characters, go to different locations, gather items, etc. So IDK how to get around needing multiple services which multiple Builder objects, but when I combined them all into a "super object"...I get a circular dependency error.
I tried installing NestJS because Nest has a way around Circular Dependency errors and I believe I did everything right, but I'm still getting the same error.
https://docs.nestjs.com/fundamentals/circular-dependency
As you can see below, it does build a localhost, but of course it doesn't do anything.
Terminal Error Image
Here's a small example from two Service files. The Nest docs say I need the ForwardRef in both Services files, not one which I have here. I've also of course installed the packages #nestjs/common and #nestjs/core. I've also tested a couple other Builder objects that don't depend on another service and they display to the console just fine. So I know the source of my problem are these Circular Dependencies.
decisions.service.ts
import { DecisionBuilder } from '../../../../Shared/builder';
import { DecisionType } from '../../../Gameplay/structs';
import { ChapterOneService } from './chapter-one.service';
import { forwardRef, Inject } from '#nestjs/common';
#Injectable({
providedIn: 'root'
})
export class DecisionsService
{
commonOne = DecisionBuilder.create({
decisionType: DecisionType.Common,
agendaScore: DecisionType.Common.valueOf(),
agendaImpact: 'Moderate',
currentPage: this.chapOne.PageOne,
nextPage: this.chapOne.PageTwo
});
commonTwo = DecisionBuilder.create({
decisionType: DecisionType.Common,
agendaScore: DecisionType.Common.valueOf(),
agendaImpact: 'Idealist',
currentPage: this.chapOne.PageOne,
nextPage: this.chapOne.PageTwo
});
commonThree = DecisionBuilder.create({
decisionType: DecisionType.Common,
agendaScore: DecisionType.Common.valueOf(),
agendaImpact: 'Extremist',
currentPage: this.chapOne.PageOne,
nextPage: this.chapOne.PageTwo
});
constructor(
#Inject( forwardRef(() => ChapterOneService) )
private chapOne: ChapterOneService )
{
}
}
The above Decision Service only depends on one other service and that's the one before. But I use the product of the service as a value for currentPage and nextPage
chapter-one.service.ts
import { AdventurePageBuilder } from '../../../../Shared/builder';
import { LocationsService } from './locations-service';
import { CharacterService } from '../../character.service';
import { DecisionsService } from './decisions.service';
import {forwardRef, Inject} from '#nestjs/common';
#Injectable({
providedIn: 'root'
})
/**
* CHAPTER ONE
* - Chapter Service that contains all Page objects
* - Each Chapter component accesses this one service for all content
*/
export class ChapterOneService
{
PageOne = AdventurePageBuilder.create({
name: this.location.getShip().area,
location: this.location.getShip(),
character: this.character.Krellen,
storyText: this.pageOneStory(),
descriptors: this.pageOneDesc(),
decisionEvents: this.decisions.commonOne
});
PageTwo = AdventurePageBuilder.create({
name: this.location.getShipTwo().area,
location: this.location.getShipTwo(),
character: this.character.Krellen,
storyText: this.pageTwoStory(),
descriptors: this.pageTwoDesc(),
decisionEvents: this.decisions.commonOne
});
constructor(
#Inject( forwardRef(() => LocationsService))
#Inject( forwardRef(() => CharacterService))
#Inject( forwardRef(() => DecisionsService))
private location: LocationsService,
private character: CharacterService,
private decisions: DecisionsService)
{
}
/***************************************/
/****************PAGE ONE**************/
/***************************************/
getPageOne(): any
{
return this.PageOne;
}
pageOneStory(): string
{
return `${this.PageOne.name} was dark was dreary. Much to ${this.PageOne.character.name}'s dismay`;
}
pageOneDesc(): any
{
// See if character carries any items with descriptions. Guns, armor, ect.
}
/***************************************/
/****************PAGE TWO***************/
/***************************************/
getPageTwo(): any
{
return this.PageTwo;
}
pageTwoStory(): string
{
return `${this.PageTwo.name} was dark was dreary. Much to ${this.PageTwo.character.name}'s dismay`;
}
pageTwoDesc(): any
{
// See if character carries any items with descriptions. Guns, armor, ect.
}
displayHolodeckPage()
{
return this.PageOne;
}
}
Some of the code from above can be ignored as I don't think they're directly the problem...I just wanted to show that I use the ForwardRef in this file too as well as the other Services that are used in chapter-one.service.ts
Click on the error image link above to see the error code I get, but any help is welcomed. Whether it's a fix to the circular error problem or a way to refactor the code so I can essentially get the same result by doing something differently.
NestJS has project structure and Dependency Injection system that is somewhat inspired by Angular but they are completely different frameworks. You cannot use NestJS decorators inside of an Angular app and expect it to do anything. You need to fix the problem in your Angular app. Don't install backend framework dependencies into a frontend app
Related
Is there a way to handle when an Angular 2+ app is updated?
Note: NOT WHEN ANGULAR IS UPDATED for example from 4.1.0 to 4.1.2 (this not)
when i say "updated" i mean to:
When code has changed and is published to production.
when i publish an update to the system built in Angular 4, the view of clients just start to have errors because javascript of NG has changed, even have other javascript generated names.
what's the right way to handle this?
Angular has "something" official for say to the Browser when to update code/resources?
or something like that.
thanks.
I don't think there is an "official" way to force a client side reload when you deploy new code. Usually this should not be a problem, because when the client calls the app, it caches the JS and CSS files, so a deploy should not have any effects on the version of the application a client is currently running...
But if this really is a problem, you could provide your application version via an HTTP API, have your angular app check it on every interaction, and reload the page if the version has changed.
version.txt
1.0.1
src/environments/environment.prod.ts
export const environment = {
production: true,
version: '1.0.2'
};
src/app/version.service.ts
import {Injectable} from '#angular/core';
import {HttpClient} from '#angular/common/http';
import {environment} from '../environments/environment';
#Injectable()
export class VersionService {
constructor(private __httpClient: HttpClient) { }
checkVersion() {
this.__httpClient.get('/version.txt').subscribe(data => {
if (data != environment.version) {
alert('Code is outdated, website will reload');
window.reload();
}
}
}
}
Add a constructor to all your components and check the version
src/app/app.component.ts
constructor(private __versionService: VersionService) {
this.__versionService.checkVersion();
}
Note: this code is completely untested :-) You might have to tinker with it... Also, I am not sure if this actually IS the best way to do it, but I couldn't find a better answer anywhere either.
Thank you so much #masterfloda!!
I been working in your aproach, and it worked like a charm, I did some tunning to your code so I hope you don't mind if I publish the updated code to help other people facing the same problem.
version.txt
1.0
I noticed that when it was more than one point betwen numbers (0.0.0 -> 0.0) it fails comparing the values.
I didn't use the src/environments/environment.prod.ts aproach because I wanted a way to update the value of version inside src/environments/environment.prod.ts and was not sure how to do it once in production, so I stored the value in local storage.
src/app/version.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Compiler } from '#angular/core';
import { GlobalVariablesService } from '../services/global.service';
//https://stackoverflow.com/questions/47440576/how-to-handle-angular-2-code-updates
#Injectable()
export class VersionService {
constructor(private __httpClient: HttpClient,
private _compiler: Compiler,
public variablesService: GlobalVariablesService
) { }
checkVersion() {
this.__httpClient.get('https://host.com/version.txt?'+Math.round(Math.random() * 10000),{responseType: 'text'}).subscribe(
response => {
let text_version = response;
let stored_version = this.variablesService.getVersion();
//alert('text_version: '+text_version);
if(stored_version == undefined){
this.variablesService.setVersion(text_version);
} else if (+text_version != +stored_version) {
this.reloadAndStore(text_version);
}
},
error => {
console.log(<any>error);
}
);
}
reloadAndStore(text_version){
//alert('Code is outdated, website will reload');
this._compiler.clearCache();
this.variablesService.setVersion(text_version);
location.reload();
}
}
version.txt?'+Math.round(Math.random() * 10000)
Here you will see I'm using a random param, because if not I noticed when the web app is installed in ios homescreen it catches even that version.text file.
../services/global.service
...
getVersion() {
if(this.deCodeLocal('storedVersion') === null){
return undefined;
} else {
return this.deCodeLocal('storedVersion');
}
}
setVersion(val) {
this.enCodeLocal('storedVersion', val);
}
...
src/app/app.component.ts
constructor(private __versionService: VersionService) {
this.__versionService.checkVersion();
}
I hope it helps somebody, thank so much.
I have added a class like this.
export class Settings{
public data: string = "blopp";
}
When I try to access the data, it seems that the field I'm trying to assign that value to sees the class Settings itself but it doesn't recognize the data thingy.
How do I redesign the class to provide settings for other components?
I've read about #Output decorator but since I won't be binding to the values, it seems not the correct approach. I've made sure that the class is imported and recognized withing the component that's supposed to consume it. I've also tried the corresponding exposure but using a function in the class with settings - the same, failed result.
If you're using angular-cli and going to store in this class environment specific settings - you already have built in support for this.
Put the setting into environment.ts. For example:
export const environment = {
production: false,
someSetting: 'foo',
};
Then it can be consumed from anywhere within the app:
import { environment } from "../environments/environment"; //fix according to your project structure
#Injectable()
export class SampleService {
private foo = environment.someSetting;
}
Here you can find more info on how to add more environments and build you project with specific environment settings.
Your best bet for storing global settings is to use a service. The code for that looks like this:
import { Injectable } from '#angular/core';
#Injectable()
export class DataService {
serviceData: string;
}
I have a blog post about this here: https://blogs.msmvps.com/deborahk/build-a-simple-angular-service-to-share-data/
And a plunker here: https://plnkr.co/edit/KT4JLmpcwGBM2xdZQeI9?p=preview
Also, the #Output decorator is only for communication between a child component and a parent component where the child is nested within the parent.
I'm thinking of separating our MVC-based website's front-end into few components and I was thinking about using Angular for that purpose (e.g. creating cart application that I can include to my view afterwards).
Problem is that I need to pass few variables into that application and I'm wondering how to do that safely and professionally. I was thinking about something like:
I'm going to ng build --prod --aot
I inject all my scripts to my view
I inject variables passed to view to my app
... and "code" representation for my thoughts:
Controller:
public function viewAction()
{
$this->view->addCss('angular/app/styles.css'); // adds styles for cart app
$this->view->addJS('angular/app/scripts.js'); // adds scripts for cart app
$this->view->setJSVariable('idCustomer', 555); // sets global var idCustomer
}
View:
<!-- bunch of HTML for view ... -->
<script>
// CartApp would be an angular app.
CartApp.use([
idCustomer
]);
</script>
So my question is... would it be possible (and would it be a good solution) to get the CartApp as an object and then make some function (like use in above example) that would set/pass the data? (let's say to some globally provided service/component or anything else). Or is there any other way to do this? Like taking the variables from inside the application from the window object? (as they're going to be bound to the window anyways). Thank you.
So I was going to suggest using input bindings... I've done that before in AngularJS but was surprised to find that using input bindings on the root component isn't supported yet. You can have fun reading this giant issue: https://github.com/angular/angular/issues/1858
The best post I saw there was Rob Wormald's which had a couple of suggestions:
to the initial question, if you really want to grab data from the
root, that's possible via
https://plnkr.co/edit/nOQuXE8hMkhakDNCNR9u?p=preview - note that it's not an input (because there's no angular context outside of it to do the input...) - its a simple string attribute
which you'd need to parse yourself.
ideally though, you'd do as
#robtown suggested and actually pass the data in javascript, rather
than passing it as a DOM string and retrieving / parsing it yourself
(which has never really been a supported case in angular, despite the
explicit-warned-against usage of ng-init in angular1 to accomplish
this) - see https://plnkr.co/edit/PoSd07IBvYm1EzeA2yJR?p=preview for a
simple, testable example of how to do this.
So the 2 good options I saw were:
Add normal HTML attributes to the root component:
<app-root appData='important stuff'></app-root>
and use ElementRef to fetch them:
#Component({
selector: 'app-root'
})
export class AppComponent {
constructor(el: ElementRef) {
console.log(el.nativeElement.getAttribute('appData'));
}
}
Would probably work best if you are just dealing with strings or config flags. If you are passing JSON, you will need to manually parse it.
Have the server render the data as JavaScript and import it in your app:
Have the server render something like this to a script tag or JS file that is loaded before Angular is bootstrapped:
window.APP_DATA = { ids: [1, 2, 3] }
Tell your NgModule about it using a provider:
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { AppComponent } from './app.component';
#NgModule({
providers: [{ provide: 'AppData', useValue: (<any> window).APP_DATA }],
bootstrap: [AppComponent]
})
export class AppModule { }
And then use it as a normal Angular service:
import {Component, Inject} from '#angular/core';
#Component({
selector: 'app-root'
})
export class AppComponent {
constructor(#Inject('AppData') appData) {
console.log(appData.ids);
}
}
I have built a shared data service that's designed to hold the users login details which can then be used to display the username on the header, but I cant get it to work.
Here's my (abbreviated) code:
// Shared Service
#Injectable()
export class SharedDataService {
// Observable string source
private dataSource = new Subject<any>();
// Observable string stream
data$ = this.dataSource.asObservable();
// Service message commands
insertData(data: Object) {
this.dataSource.next(data)
}
}
...
// Login component
import { SharedDataService } from 'shared-data.service';
#Component({
providers: [SharedDataService]
})
export class loginComponent {
constructor(private sharedData: SharedDataService) {}
onLoginSubmit() {
// Login stuff
this.authService.login(loginInfo).subscribe(data => {
this.sharedData.insertData({'name':'TEST'});
}
}
}
...
// Header component
import { SharedDataService } from 'shared-data.service';
#Component({
providers: [SharedDataService]
})
export class headerComponent implements OnInit {
greeting: string;
constructor(private sharedData: SharedDataService) {}
ngOnInit() {
this.sharedData.data$.subscribe(data => {
console.log('onInit',data)
this.greeting = data.name
});
}
}
I can add a console log in the service insertData() method which shoes the model being updated, but the OnInit method doesn't reflect the change.
The code I've written is very much inspired by this plunkr which does work, so I am at a loss as to what's wrong.
Before posting here I tried a few other attempts. This one and this one again both work on the demo, but not in my app.
I'm using Angular 2.4.8.
Looking through different tutorials and forum posts all show similar examples of how to get a shared service working, so I guess I am doing something wrong. I'm fairly new to building with Angular 2 coming from an AngularJS background and this is the first thing that has me truly stuck.
Thanks
This seems to be a recurring problem in understanding Angular's dependency injection.
The basic issue is in how you are configuring the providers of your service.
The short version:
Always configure your providers at the NgModule level UNLESS you want a separate instance for a specific component. Only then do you add it to the providers array of the component that you want the separate instance of.
The long version:
Angular's new dependency injection system allows for you to have multiple instances of services if you so which (which is in contrast to AngularJS i.e. Angular 1 which ONLY allowed singletons). If you configure the provider for your service at the NgModule level, you'll get a singleton of your service that is shared by all components/services etc. But, if you configure a component to also have a provider, then that component (and all its subcomponents) will get a different instance of the service that they can all share. This option allows for some powerful options if you so require.
That's the basic model. It, is of course, not quite so simple, but that basic rule of configuring your providers at the NgModule level by default unless you explicitly want a different instance for a specific component will carry you far.
And when you want to dive deeper, check out the official Angular docs
Also note that lazy loading complicates this basic rule as well, so again, check the docs.
EDIT:
So for your specific situation,
#Component({
providers: [SharedDataService] <--- remove this line from both of your components, and add that line to your NgModule configuration instead
})
Add it in #NgModule.providers array of your AppModule:
if you add it in #Component.providers array then you are limiting the scope of SharedDataService instance to that component and its children.
in other words each component has its own injector which means that headerComponentwill make its own instance of SharedDataServiceand loginComponent will make its own instance.
My case is that I forget to configure my imports to add HttpClientModule in #NgModules, it works.
I have written two services in Angular 2. One of those is a basic, customised class of Http with some custom functionality in (it looks basic for now, but it will be expanding):
ServerComms.ts
import {Injectable} from 'angular2/core';
import {Http} from 'angular2/http';
#Injectable()
export class ServerComms {
private url = 'myservice.com/service/';
constructor (public http: Http) {
// do nothing
}
get(options) {
var req = http.get('https://' + options.name + url);
if (options.timout) {
req.timeout(options.timeout);
}
return req;
}
}
Another class, TicketService utilises this class above, and calls one of the methods in the service. This is defined below:
TicketService.ts
import {Injectable} from 'angular2/core';
import {ServerComms} from './ServerComms';
#Injectable()
export class TicketService {
constructor (private serverComms: ServerComms) {
// do nothing
}
getTickets() {
serverComms.get({
name: 'mycompany',
timeout: 15000
})
.subscribe(data => console.log(data));
}
}
However, I receive the following error whenever I try this:
"No provider for ServerComms! (App -> TicketService -> ServerComms)"
I do not understand why? Surely I do not need to inject every service that each other service relies upon? This can grow pretty tedious? This was achievable in Angular 1.x - how do I achieve the same in Angular 2?
Is this the right way to do it?
In short since injectors are defined at component level, the component that initiates the call services must see the corresponding providers. The first one (directly called) but also the other indirectly called (called by the previous service).
Let's take a sample. I have the following application:
Component AppComponent: the main component of my application that is provided when creating the Angular2 application in the bootstrap function
#Component({
selector: 'my-app',
template: `
<child></child>
`,
(...)
directives: [ ChildComponent ]
})
export class AppComponent {
}
Component ChildComponent: a sub component that will be used within the AppComponent component
#Component({
selector: 'child',
template: `
{{data | json}}<br/>
Get data
`,
(...)
})
export class ChildComponent {
constructor(service1:Service1) {
this.service1 = service1;
}
getData() {
this.data = this.service1.getData();
return false;
}
}
Two services, Service1 and Service2: Service1 is used by the ChildComponent and Service2 by Service1
#Injectable()
export class Service1 {
constructor(service2:Service2) {
this.service2 = service2;
}
getData() {
return this.service2.getData();
}
}
#Injectable()
export class Service2 {
getData() {
return [
{ message: 'message1' },
{ message: 'message2' }
];
}
}
Here is an overview of all these elements and there relations:
Application
|
AppComponent
|
ChildComponent
getData() --- Service1 --- Service2
In such application, we have three injectors:
The application injector that can be configured using the second parameter of the bootstrap function
The AppComponent injector that can be configured using the providers attribute of this component. It can "see" elements defined in the application injector. This means if a provider isn't found in this provider, it will be automatically look for into this parent injector. If not found in the latter, a "provider not found" error will be thrown.
The ChildComponent injector that will follow the same rules than the AppComponent one. To inject elements involved in the injection chain executed forr the component, providers will be looked for first in this injector, then in the AppComponent one and finally in the application one.
This means that when trying to inject the Service1 into the ChildComponent constructor, Angular2 will look into the ChildComponent injector, then into the AppComponent one and finally into the application one.
Since Service2 needs to be injected into Service1, the same resolution processing will be done: ChildComponent injector, AppComponent one and application one.
This means that both Service1 and Service2 can be specified at each level according to your needs using the providers attribute for components and the second parameter of the bootstrap function for the application injector.
This allows to share instances of dependencies for a set of elements:
If you define a provider at the application level, the correspoding created instance will be shared by the whole application (all components, all services, ...).
If you define a provider at a component level, the instance will be shared by the component itself, its sub components and all the "services" involved in the dependency chain.
So it's very powerful and you're free to organize as you want and for your needs.
Here is the corresponding plunkr so you can play with it: https://plnkr.co/edit/PsySVcX6OKtD3A9TuAEw?p=preview.
This link from the Angular2 documentation could help you: https://angular.io/docs/ts/latest/guide/hierarchical-dependency-injection.html.
Surely you do.
In Angular2, there are multiple injectors. Injectors are configured using the providers array of components. When a component has a providers array, an injector is created at that point in the tree. When components, directives, and services need to resolve their dependencies, they look up the injector tree to find them. So, we need to configure that tree with providers.
Conceptually, I like to think that there is an injector tree that overlays the component tree, but it is sparser than the component tree.
Again, conceptually, we have to configure this injector tree so that dependencies are "provided" at the appropriate places in the tree. Instead of creating a separate tree, Angular 2 reuses the component tree to do this. So even though it feels like we are configuring dependencies on the component tree, I like to think that I am configuring dependencies on the injector tree (which happens to overlay the component tree, so I have to use the components to configure it).
Clear as mud?
The reason Angular two has an injector tree is to allow for non-singleton services – i.e., the ability to create multiple instances of a particular service at different points in the injector tree. If you want Angular 1-like functionality (only singleton services), provide all of your services in your root component.
Architect your app, then go back and configure the injector tree (using components). That's how I like to think of it. If you reuse components in another project, it is very likely that the providers arrays will need to be changed, because the new project may require a different injector tree.
Well, i guess you should provide both services globally:
bootstrap(App, [service1, service2]);
or provide to component that uses them:
#Component({providers: [service1, service2]})
#Injectable decorator adds necessary metadata to track dependecies, but does not provide them.