I am creating an Ionic app. I have 3 providers - database provider, portfolio provider and user provider. All 3 are Injectable. I have created it this way because several other pages need to use their function calls (i.e. they should not share the same data, they all should create new instances)
Both the portfolio and user provider import the database provider, as the need to make the same database calls to retrieve data.
I have 1 page - ViewPortfolio. The ViewPortfolio page imports the user provider (to know who the user is) and portfolio provider (to get the users portfolio data). For some reason, these 2 providers seem to be sharing the same instance for database provider. This is how I use them:
PORTFOLIO PROVIDER
import { DatabaseProvider } from '../providers/database-provider';
#Injectable()
#Component({
providers: [DatabaseProvider]
})
export class PortfolioProvider {
public portfolio_list: any = new BehaviorSubject<Array<string>>([]);
constructor(private dbProvider: DatabaseProvider) {
this.dbProvider.enableDataListener(this.protfolio_path); // set path
this.dbProvider.db_listener.subscribe(value => { // subscribe to data in the path
// do stuff
});
}
}
The user portfolio is the same, the only difference is the path its listening to is different.
However, when I change data in the portfolio path, the subscribe call is also triggered in the user path (and vice versa). Thus, even though I added DatabaseProvider in the components providers, its not creating unique instances. Why is this?
I figured it might be because I am importing them both on the same page but I am not convinced that's why it is not working. How do I make the 2 providers use unique instances on databaseprovider, while calling them both on the same page?
This is what my app.moudle.ts file looks like (please note that DatabaseProvider is not included in my app.module.ts file)
// ... more imports
import { PortfolioProvider } from '../providers/portfolio-provider';
import { UserProvider } from '../providers/user-provider';
#NgModule({
declarations: [
MyApp,
// ... more
],
imports: [
// ... more
IonicModule.forRoot(MyApp, {
backButtonText: '',
tabsPlacement: 'bottom'
}),
IonicStorageModule.forRoot()
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
// ... more
],
providers: [
// ... more
PortfolioProvider,
UserProvider
]
})
export class AppModule {}
Thanks,
Did you remove the provider from app.module.ts (root AppModule)?
From the Angular Documentation:
Scenario: service isolation
While you could provide VillainsService in the root AppModule (that's where you'll find the HeroesService), that would make the VillainsService available everywhere in the application, including the Hero workflows.
If you generated the provider using ionic-cli, it'll automatically add it to the root AppModule.
Related
I'm currently developing a micro-frontend application using React, webpack and module federation. It consists of one container app, and two "children" / remotes. The container app consist of an app bar, empty side drawer and main div, and the remotes it imports expose one independent React app for the actual page content, plus one single component which should render the navigation items for the side drawer, each:
The remote MFEs have their own Redux store each, which works fine for the page content part, as this includes an App.tsx with a Redux store provider. So far, also the navigation component worked fine, since all it did was push routes into the browser history.
Now I've run into a problem: the exposed navigation component of one remote also has to select data from it's Redux store and dispatch actions. This does not work so far, since it's a single exposed component, and when it's rendered in the container app, there is not Redux store provider for the childs Redux store. How could I solve this? I've read a few times that sharing redux state between micro frontends is a bad practice, so I was trying to avoid this so far. The data the navigation needs access to is basically just a boolean, which indicates that the application is in an elevated "service mode", making the remote MFE render a few more items (e.g. a "delete all" button which is usually hidden). So maybe this could also be shared through local storage or similar, what are some best practices here?
Here's my webpack config and some relevant code for better understanding:
// container app webpack config (development)
...
plugins: [
new ModuleFederationPlugin({
name: "container_app",
remotes: {
config_app: "config_app#http://localhost:3001/remoteEntry.js",
commissioning_app: "commissioning_app#http://localhost:3002/remoteEntry.js",
},
shared: {
...
},
}),
],
...
// config_app webpack config (development)
...
plugins: [
new ModuleFederationPlugin({
name: "config_app",
filename: "remoteEntry.js",
exposes: {
"./ConfigApp": "./src/bootstrap",
"./ConfigAppNavigation": "./src/components/UI/Misc/ConfigAppNavigation",
},
shared: {
...
},
}),
],
...
// MiniDrawer.tsx in container_app, which uses ConfigAppNavigation to render the navigation items
// (depending on current route, otherwise navigation items are rendered from other MFE child)
...
const ConfigAppNavigation = lazy(() => import("config_app/ConfigAppNavigation"));
const MiniDrawer: React.FC = () => {
...
<Switch>
...
<Route path="/">
<Suspense fallback={<span>loading ...</span>}>
<ConfigAppNavigation onNavigate={onDrawerClose} />
</Suspense>
</Route>
</Switch>
...
}
...
And as stated, before switching to an MFE design, the component which is now ConfigAppNavigation selected / changed the mentioned boolean value from config_app's Redux store, which now with this setup doesn't work.
Does someone have some extra info about defineRoutes function in remix.config?
I have a this route:
{
"id": "routes/__main/city/$city", "path": "/city/:city",
"file":"routes/__main/city/$city.tsx"
}
and in defineRoutes I made something like this:
routes: async (defineRoutes) => {
return defineRoutes((route) => {
route("/citta/:city", "routes/__main/city/$city.tsx");
});
},
I want that both /citta/test and /city/test will go on the same file located here routes/__main/city/$city.tsx.
But when I run the code only the /citta/test route is active the other one /city/test will throw error.
As I read from the docs here https://remix.run/docs/en/v1/api/conventions#routes, what I want to achive should be possible.
Have I misunderstood the use of defineRoutes?
This can be solved without the use of defineRoutes. Revert your remix.config changes and let Remix handle the routing for you by placing your routes within app/routes.
Move routes/__main/city/$city.tsx in your app directory and add an additional folder structure app/routes/__main/citta/$city.tsx. So you have two folders /city and /citta next to each other. They will share all the nested routing that you introduced with __main.
Export the following from your app/routes/__main/citta/$city.tsx file:
import CityComponent from '~/routes/__main/city/$city';
// re-export loader, action, and all other functionality
export * from '~/routes/__main/city/$city'
// re-use the default export from your other route
export default CityComponent;
This lets you reuse the code from your city/$city.tsx file in citta/$city.tsx.
Note: Make sure to name both files in citta/ and city/ $city.tsx to avoid naming discrepancies. Otherwise your re-exported loader and action won't work as the parameter name differs.
I recently tried to colocate all my code in modules and re-export the page components from app/routes like this:
import LoginPage, from "~/modules/auth/LoginPage";
export * from "~/modules/auth/LoginPage";
export default LoginPage;
but I ran into React 18 hydration issues. The working solution for me was re-exporting this way:
import LoginPage, { action, loader } from "~/modules/auth/LoginPage";
export { action, loader };
export default LoginPage;
If we follow ngrx-data example and look at the Entity DataService, we can fetch the Hero data that we have in-memory (hard-coded) without any configuration. The default will work the same as if we configured:
const defaultDataServiceConfig: DefaultDataServiceConfig = {
root: 'api', // or a running server url, e.g: 'http://localhost:4000/api'
timeout: 3000, // request timeout
}
and in e.g: EntityStoreModule
#NgModule({
providers: [{ provide: DefaultDataServiceConfig, useValue: defaultDataServiceConfig }]
})
Question:
How will we configure our app to fetch data for entity "Heros" from the default source:
root: 'api'
and data for entity "Villans" from a URL:
root: 'http://localhost:4000/villans'
and data for other entities from their (other/various) respective URLs ...?
After reviewing the docs specifically:
Custom EntityDataService and
Replace the HttpUrlGenerator
I came up with this solution. Anyone feel free to comment.
Define/review your data types - entity metadata - entity names;
Create mapping to plurals for non-default plural entity names (default is: name + 's');
For entities with the non-default root URL create a mapping of entity names to specific URL;
File: ../entity-metadata.ts
// Step 1:
const entityMetadata: EntityMetadataMap = {
Hero: {},
Villan: {},
Creature: {},
DataA01: {}
// etc.
}
// Step 2:
const pluralNames = {
Hero: 'heroes',
DataA01: 'data-a01'
}
export const entityConfig = {
entityMetadata,
pluralNames
};
// Step 3:
export const rootUrls = {
// Hero: - not needed here, data comes from default root
Villan: 'http://localhost:4001',
Creature: 'http://localhost:4001',
DataA01: 'http://remoteserver.net:80/publicdata',
}
Replace the HttpUrlGenerator (doc) with your own URL generator (DynamicHttpUrlGenerator)
File: ../http-dyn-url-generator.ts
import { Injectable } from '#angular/core';
import {
DefaultHttpUrlGenerator,
HttpResourceUrls,
normalizeRoot,
Pluralizer,
DefaultPluralizer,
} from '#ngrx/data';
import { rootUrls } from '../data/ngrx-data/db01-entity-metadata';
#Injectable()
export class DynamicHttpUrlGenerator extends DefaultHttpUrlGenerator {
constructor(private aPluralizer: Pluralizer = new DefaultPluralizer(undefined)) {
super(aPluralizer);
}
protected getResourceUrls(entityName: string, root: string): HttpResourceUrls {
let resourceUrls = this.knownHttpResourceUrls[entityName];
if ( ! resourceUrls) {
// rootUrls contains
// mapping of individual ngrx data entities
// to the root URLs of their respective data sources.
// It contains only entities which do not have
// the default root URL.
if (rootUrls.hasOwnProperty(entityName)) {
root = rootUrls[entityName];
}
const nRoot = normalizeRoot(root);
const url = `${nRoot}/${this.aPluralizer.pluralize(entityName)}/`.toLowerCase();
// remove after testing
console.log('-- entityName: ' + entityName + ', URL: ' + url)
resourceUrls = {
entityResourceUrl: url,
collectionResourceUrl: url
};
this.registerHttpResourceUrls({ [entityName]: resourceUrls });
}
return resourceUrls;
}
}
For each of your data entity create a custom EntityDataService
HeroDataService
VillanDataService
CreatureDataService
DataA01DataService
etc.
(doc and code is here) - the code example is under
// store/entity/hero-data-service.ts
Register your DynamicHttpUrlGenerator and your custom EntityDataServices in your app's module, in my case:
File: ../ngrx-data-store.module.ts
(in a simple app, directly in file: app.module.ts)
#NgModule({
imports: [ ... ],
providers: [ { provide: HttpUrlGenerator, useClass: DynamicHttpUrlGenerator },
HeroDataService,
VillanDataService,
CreatureDataService,
DataA01DataService
]
})
Use your custom EntityDataServices in your components for each given entity the same way as all standard or default EntityDataServices to fetch data. The data will be pulled from the respective URLs you set in the const: rootUrls.
Don't forget to get your URLs' data server(s) configured and started.
A few important considerations:
on your server you may need to enable CORS handling. E.g: on nestjs use:
app.enableCors();
if your client app uses: Angular in-memory-web-api you need to enable access to remote server as follows:
File: ../in-mem-data.module.ts (or as you named it)
import { NgModule } from '#angular/core';
import { HttpClientModule } from '#angular/common/http';
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemDataService } from '../../services/data/in-mem-data/in-mem-data.service';
#NgModule({
imports: [
HttpClientModule,
HttpClientInMemoryWebApiModule.forRoot(InMemDataService, {
passThruUnknownUrl: true // <--- IMPORTANT for remote data access
}),
]
})
export class InMemDataModule {}
I'm currently learning to implement angularfire2 in my project. Unfortunately I'm currently stuck. I set up my Angular-Project like described here
https://github.com/angular/angularfire2/blob/master/docs/install-and-setup.md
I also set up a database in firebase with a the rule set
{
"rules": {
".read": "true",
".write": "true"
}
}
But when I try to run the application in my console in the browser gives me the following error
ERROR Error: The Cloud Firestore API is not enabled for the project
Now I found a way to enable the API here
https://console.cloud.google.com/apis/library/firestore.googleapis.com/?project=projectname
leaving me now with the error
ERROR Error: Missing or insufficient permissions
My problem now is I can set up API keys (but also also got a different API-key from my firebase console?) and OAuths, but I have no idea how to implement those is my code. Just simply generating an API key and using that one in the environment.firebase config didn't work. Would be great if someone knew anything. I'll keep on trying and let you know if I get it to work as well.
To let others knows where it is in the firebase console:
Select your project then click on Database and change the dropdown from "Realtime Database" to "Cloud Firestore"
I have had the same problem, and I have fixed it with this:
Go to:
https://console.firebase.google.com/u/1/project/**ProjectID**/database/firestore/rules
and change the rules to:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write;
}
}
}
Thanks to Edric i was able to solve it. He was right. My error was, that I was trying to use AngularFirestore and not AngularFireDatabase and AngularFireDatabaseModule. After i imported theese too it worked.
If you're having the same difficulties as I had, basically your module needs to look like described here
No provider for AngularFireDatabase, AngularFireAuth
Only thing, that I had to change, is you don't add AngularFireDatabase and AngularFireDatabaseModule to imports, but to providers. So in the end your module will look like this
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { AngularFireModule } from 'angularfire2';
import { AngularFireDatabaseModule, AngularFireDatabase } from 'angularfire2/database';
import { environment } from '../environments/environment';
import { AppComponent } from './app.component';
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AngularFireModule.initializeApp(environment.firebase),
],
providers: [AngularFireDatabase, AngularFireDatabaseModule ],
bootstrap: [AppComponent]
})
export class AppModule { }
Thanks again and I hope this will help others too, that struggle with setting up the FireDatabase
The whole matter come up when we had a shared component #Injectable. Every component in the app.module.ts can inject it into his own constructor.
export class SimpleComponent {
constructor (private sharedComponent : SharedComponent){}
}
I write a method in this class SimpleComponent to set a proprety at the shared component:
setPropretyAtSharedComponent {
this.sharedComponent.setProprety("proprety")
}
Presuming that the SharedComponent held:
#Injectable()
export class SharedComponent {
proprety :any
constructor() {}
}
setProprety (proprety){
this.proprety = proprety;
}
At this point every thing is OK.
So I was wondering if I had two components at the same page which sets the proprety value of the SharedComponent at meanwhile (concurent access) how I can handle that?
I guess that's taken care of by the DI framework once you define your Injectable service in providers in your module. Once your service is defined in the module, you can inject it in different components as a singleton.
You can refer to these docs:https://angular.io/docs/ts/latest/guide/dependency-injection.html