Dynamic Internationalization in Angular 5 (Date and currency) - javascript

I met problem with date and currency interpoation in my app. I tried to find out some workaround but I could not for my case.
My app works this way: It initialized with en-US locale. After user login data comes where one field is User's Region. This one is setted in local. For this purpose I do:
// app.module
providers: [
...
{
provide: LOCALE_ID,
deps: [LocaleService],
useFactory: localeProviderFactory
}
...]
export function localeProviderFactory(provider: LocaleService) {
return provider.getCurrent();
}
//my service
export class LocaleService {
locale: BehaviorSubject<any>;
constructor() {
this.locale = new BehaviorSubject<GroupModel> (this.getGroupFromLocalStorage());
}
private getGroupFromLocalStorage(): GroupModel {
let parse = JSON.parse(localStorage.getItem('currentUser'));
return parse ? parse.Region : 'en-US';
}
setLocale(val) {
this.locale.next(val);
}
getCurrent() {
return this.locale.getValue();
}
}
So after user login I call setLocale() in LocaleService and pass there new User Region.
Problem is that I don't know which region will be setted and i don't want import all locales into app. Angular 5 require all locales imported before app starts. With:
import { registerLocaleData } from '#angular/common';
import localeFr from '#angular/common/locales/fr';
registerLocaleData(localeFr);
Is it possible to import/load and register locales dynamicaly in run time? Or is there some workarounds?

No, unfortunately it's not possible. As Rob McCabe in his answer to his own question here said:
Angular only works with one language at a time, you have to completely reload the application to change the lang. The JIT support only means that it works with JIT, but you still have to provide the translations at bootstrap because it will replace the text in your templates during the compilation whereas this lib uses bindings, which means that you can change the translations at any time.

Related

What's the difference between providing and injecting 'Window' vs Window in Angular 8 and 9?

I have two Angular projects using these versions:
9.0.0-next.6
8.1.0
In the version 9 I used this to provide and inject the window obhject:
#NgModule({
providers: [
{
provide: Window,
useValue: window
},
]
})
export class TestComponent implements OnInit {
constructor(#Inject(Window) private window: Window)
}
Which works fine.
Taking this approach to version 8 throwed warnings and errors during compilation:
Warning: Can't resolve all parameters for TestComponent …
I solved it by using single quotes, like this:
#NgModule({
providers: [
{
provide: 'Window',
useValue: window
},
]
})
export class TestComponent implements OnInit {
constructor(#Inject('Window') private window: Window)
}
What is the difference between both version?
What is the difference in Angular 8 and 9 that causes this thing?
In order for your app to work with Server Side Rendering I suggest you not only use window through token, but also create this token in SSR friendly manner, without referencing window at all. Angular has built-in DOCUMENT token for accessing document. Here's what I came up with for my projects to use window through tokens:
import {DOCUMENT} from '#angular/common';
import {inject, InjectionToken} from '#angular/core';
export const WINDOW = new InjectionToken<Window>(
'An abstraction over global window object',
{
factory: () => {
const {defaultView} = inject(DOCUMENT);
if (!defaultView) {
throw new Error('Window is not available');
}
return defaultView;
},
},
);
Edit: Since this is something people often need, we've used this technique to create a tiny open-source library with injection tokens for global objects, so you can use it:
https://github.com/ng-web-apis/common
It has a sister library for mocks to be used with SSR in Angular Universal:
https://github.com/ng-web-apis/universal
Overall, check out our hub for native APIs in Angular:
https://ng-web-apis.github.io/
Considering the ValueProvider interface:
export declare interface ValueProvider extends ValueSansProvider {
/**
* An injection token. Typically an instance of `Type` or `InjectionToken`, but can be `any`.
*/
provide: any;
/**
* When true, injector returns an array of instances. This is useful to allow multiple
* providers spread across many files to provide configuration information to a common token.
*/
multi?: boolean;
}
The provide property is of type any. That means any object (included the Window constructor) can go inside it. The object actually doesn't matter, only the reference matters to identify which provider should be used to inject a parameter in a constructor.
It should not be considered as a good practice to use the native Window constructor as an injection token. It fails at compile time because Window exists at run time in a browser environment, it also exists as a TypeScript declare but the Angular 8 compiler cannot do static code analysis to correlate the Window in the providers and the Window in a constructor's parameters, since the assignment of Window is done by the browser, not by the code. Not sure why it works in Angular 9, though...
You should create your own injection token that represents the dependency provider. This injection token should be either:
A dedicated string (like you did with 'Window')
A dedicated InjectionToken. For example export const window = new InjectionToken<Window>('window');
Moreover, the Angular code should be platform agnostic (should be executable in a browser and on a Node.js server as well) so it would be better to use a factory that returns window or undefined/null, then handle the undefined/null case in the components.

Angular - access value from a service in root module

I'm working on internationalisation of an Angular app where I need to dynamically detect the locale that the user has actively selected for the app (the user must actively switch locale for the site to be displayed in their preferred language).
In my root module I specify the following provider for TRANSLATIONS, where the required xlf file (the translations file) is determined dynamically using a factory that depends on the value of my LOCALE_ID:
app.module.ts
... imports go here
export function selectedLocaleFactory(): string {
return LocaleService.getCurrentLanguage();
}
providers:
[
LocaleService,
{
provide: TRANSLATIONS,
useFactory: (locale) => {
locale = locale || 'en'; // default to english if no locale provided
return require(`raw-loader!../locale/messages.${locale}.xlf`);
},
deps: [LOCALE_ID]
},
{
provide: LOCALE_ID,
useFactory: selectedLocaleFactory
}
]
As you can see, in the TRANSLATIONS provider, I'm determining the translation file to use based on the value of LOCALE_ID.
LOCALE_ID is determined by using another factory (selectedLocaleFactory), which simply tries to return a LOCALE_ID value using a method getCurrentLanguage from the LocalService. I'm not sure this is the correct/best way to get a value from my service - I mean selectedLocaleFactory isn't really acting as a true 'factory', and services are designed to be injected.
In any case, I get this compile error in app.module.ts:
Property 'getCurrentLanguage' does not exist on type 'typeof LocaleService'.
Here's an extract from my LocalService:
#Injectable()
export class LocaleService {
private currLanguage: string;
constructor( private router: Router ) {
// this.currLanguage is set here - based on the URL being accessed by the user
}
getCurrentLanguage() {
return this.currLanguage;
}
If I make getCurrentLanguage a static method inside the LocaleService it's still inaccessible:
static getCurrentLanguage() {
return this.currLanguage;
}
UPDATE:
I realised that I need to instantiate the service to access the appropriate method:
export function selectedLocaleFactory(): string {
var localeService = new LocaleService(null);
return localeService.getCurrentLanguage();
}
My LocaleService has a Router dependency, so I just set this to null. It still feels though like I'm trying to do this the wrong way. My LocaleService should really be a singleton and I'm concerned I may end up with issues using my current approach (for example issues with shadowing). Is there a more 'correct' I can access a property from my service inside my root module?
The answer by #Thierry Templier here - What is the best way to declare a global variable in Angular 2 / Typescript suggests bootstrapping the service to get access to one of its variables, however this approach gives me an error saying I can't use the service as an entry point.

Ionic3 / Ionic2+ settings.json file for environment variable configuration [duplicate]

I'm working on an ionic2 project and I need to create a new custom JSON config file. I found some tutorials to create one and access it through http.get but I think it's weird to call it through a get request. I want it in the root folder (where all the config JSONs are) and I open/read the file directly.
I don't know if it's possible, or even recommended ? This is why I'm posting here to have some opinions and solutions :)
Thanks
Personally I don't like the read the config.json file by using the http.get way to handle configuration information, and even though there must be another way to just include and read the json file in your code, since we're using Angular2 and Typescript, why not using classes, interfaces and doing it in a more fancy way?
What I'll show you next may seem more complicated than it should at first (although after reading it you will find it very straightforward and easy to understand), but when I started learning Angular2 I saw an example of how they handled config files in the Dependency Injection guide and I followed that in the apps I've worked on to handle config information (like API endpoints, default values, and so on).
According the docs:
Non-class dependencies
[...]
Applications often define configuration objects with lots of small
facts (like the title of the application or the address of a web API
endpoint) but these configuration objects aren't always instances of a
class.
One solution to choosing a provider token for non-class dependencies
is to define and use an OpaqueToken
So you would need to define a config object with the urls and so on, and then an OpaqueToken to be able to use it when injecting the object with your configuration.
I included all my configuration in the app-config.ts file
// Although the ApplicationConfig interface plays no role in dependency injection,
// it supports typing of the configuration object within the class.
export interface ApplicationConfig {
appName: string;
apiEndpoint: string;
}
// Configuration values for our app
export const MY_CONFIG: ApplicationConfig = {
appName: 'My new App',
apiEndpoint: 'http://www...'
};
// Create a config token to avoid naming conflicts
export const MY_CONFIG_TOKEN = new OpaqueToken('config');
What OpaqueToken is may be confusing at first, but it just a string that will avoid naming conflicts when injecting this object. You can find an amazing post about this here.
Then, you just need to include it in the page you need it like this:
import { NavController } from 'ionic-angular/index';
import { Component, OpaqueToken, Injectable, Inject } from "#angular/core";
// Import the config-related things
import { MY_CONFIG_TOKEN, MY_CONFIG, ApplicationConfig } from 'app-config.ts';
#Component({
templateUrl:"home.html",
providers: [{ provide: MY_CONFIG_TOKEN, useValue: MY_CONFIG }]
})
export class HomePage {
private appName: string;
private endPoint: string;
constructor(#Inject(MY_CONFIG_TOKEN) private config: ApplicationConfig) {
this.appName = config.appName;
this.endPoint = config.apiEndpoint;
}
}
Please notice how to include it in the providers array
providers: [{ provide: MY_CONFIG_TOKEN, useValue: MY_CONFIG }]
And how to tell the injector how it should obtain the instance of the config object
#Inject(MY_CONFIG_TOKEN) config: ApplicationConfig
UPDATE
OpaqueToken has been deprecated since v4.0.0 because it does not support type information, use InjectionToken<?> instead.
So instead of these lines:
import { OpaqueToken } from '#angular/core';
// Create a config token to avoid naming conflicts
export const MY_CONFIG_TOKEN = new OpaqueToken('config');
Now we should use
import { InjectionToken } from '#angular/core';
// Create a config token to avoid naming conflicts
export const MY_CONFIG_TOKEN = new InjectionToken<ApplicationConfig>('config');
After reading and reading different solutions I ended up using this hacky implementation. Hopefully there will be a nice and native solution available soon:
import { NgModule } from '#angular/core';
import { environment as devVariables } from './environment.dev';
import { environment as testVariables } from './environment.test';
import { environment as prodVariables } from './environment.prod';
export function environmentFactory() {
const location = window.location.host;
switch (location) {
case 'www.example.org': {
return prodVariables;
}
case 'test.example.org': {
return testVariables;
}
default: {
return devVariables;
}
}
}
#NgModule({
providers: [
{
provide: 'configuration',
useFactory: environmentFactory
}
]
})
export class EnvironmentsModule {}
and then where ever needed, e.g.:
import { Injectable, Injector, Inject } from '#angular/core';
import { AuthenticationService } from '../authentication';
#Injectable()
export class APIService {
private http: Http;
private apiURL: string;
protected authentication: AuthenticationService;
constructor(
public injector: Injector,
#Inject('configuration') public configuration: any
) {
this.http = injector.get(Http);
this.authentication = injector.get(AuthenticationService);
this.apiURL = configuration.apiURL;
};
...

Migrating a JavaScript\Ionic\Angular 1 App to Typescript\Ionic 2\Angular 2 App

I'm working on migrating an App from JavaScript\Ionic\Angular1 to Typescript\Ionic2\Angular2 one file at a time. I've poured over dozens of how-to's and such about migrating from one to the other, done the Angular 2 quick start and tutorial, and seen how to go from .js to .ts as well as installed all of the npm packages I need. Assuming I have everything I need to start the migration process, I really need help actually starting. I have dozens of files to convert, and it would help me greatly to just get one file converted correctly with the old code commented out to use as a reference to convert the others.
Here is a sample file. If you could convert this for me, or walk me through converting it, I would be very appreciative.
angular.module('myApp.app', ['ionic'])
.controller('myApp.app', function($rootScope, $scope, AService, BService, CService){
$scope.setUserName = function (user){
$scope.user = user;
};
document.addEventListener('deviceready', function() {
$rootScope.$on('$cordovaNetwork:online', function (e, nState) {
BService.setOnline(true);
})
})
})
Thank you.
The code below is not complete, but gives you an idea of the direction you should be heading. It is a modified version of the boilerplate code that is created for you whenever you use the ionic-cli to generate a new app.
You would define your services each in a separate file in a subfolder of your app/ folder called services. For example, your AService would be defined in app/services/a-service.ts. You import app level services at the top of your app.ts file and then include them in an array as the second component to the ionicBootstrap() function at the very bottom of the file. You also have to inject them as private variables in the constructor() of your MyApp component.
There is no longer anything like a $scope or $rootScope where you can store app-wide variables. Instead, you would create a provider (e.g. UserData) that you would use to store data that needs to be persisted across pages or sessions.
I recommend reading through the Ionic 2 Conference Application, which has been developed as a sample app using the Ionic 2 framework by its developers. It shows you how to handle things like user login, and persisting data across the app.
import { Component } from "#angular/core";
import { ionicBootstrap, Platform, Nav } from "ionic-angular";
import { AService } from "./services/a-service";
import { BService } from "./services/b-service";
import { CService } from "./services/c-service";
import { UserData } from "./providers/user-data";
import { HomePage } from "./pages/home/home";
#Component({
templateUrl: "build/app.html"
})
export class MyApp {
// the root nav is a child of the root app component
// #ViewChild(Nav) gets a reference to the app's root nav
#ViewChild(Nav) nav: Nav;
rootPage: any = HomePage;
pages: Array<{ title: string, component: any }>;
constructor(
private platform: Platform,
private aSvc: AService,
private bSvc: BService,
private cSvc: CService,
private userData: UserData
) {
this.initializeApp();
// array of pages in your navigation
this.pages = [
{ title: "Home Page", component: HomePage }
];
}
initializeApp() {
this.platform.ready().then(() => {
// Okay, so the platform is ready and our plugins are available.
// Here you can do any higher level native things you might need.
bSvc.setOnline(true);
});
}
openPage(page) {
// Reset the content nav to have just this page
// we wouldn't want the back button to show in this scenario
this.nav.setRoot(page.component);
}
}
// Pass the main app component as the first argument
// Pass any providers for your app in the second argument
// Set any config for your app as the third argument:
// http://ionicframework.com/docs/v2/api/config/Config/
ionicBootstrap(MyApp, [AService, BService, CService, UserData]);

How to include external JavaScript libraries in Angular 2?

I am trying to include an external JS library in my Angular 2 app and trying to make all the methods in that JS file as a service in Angular 2 app.
For eg: lets say my JS file contains.
var hello = {
helloworld : function(){
console.log('helloworld');
},
gmorning : function(){
console.log('good morning');
}
}
So I am trying to use this JS file and reuse all the methods in this object and add it to a service, so that my service has public methods, which in turn calls this JS methods. I am trying to reuse the code, without reimplementing all the methods in my typescript based Angular 2 app. I am dependent on an external library, which I cant modify.
Please help, thank you in advance.
With ES6, you could export your variable:
export var hello = {
(...)
};
and import it like this into another module:
import {hello} from './hello-module';
assuming that the first module is located into the hello-module.js file and in the same folder than the second one. It's not necessary to have them in the same folder (you can do something like that: import {hello} from '../folder/hello-module';). What is important is that the folder is correctly handled by SystemJS (for example with the configuration in the packages block).
When using external libs which are loaded into the browser externally (e.g. by the index.html) you just need to say your services/component that it is defined via "declare" and then just use it. For example I recently used socket.io in my angular2 component:
import { Component, Input, Observable, AfterContentInit } from angular2/angular2';
import { Http } from 'angular2/http';
//needed to use socket.io! io is globally known by the browser!
declare var io:any;
#Component({
selector: 'my-weather-cmp',
template: `...`
})
export class WeatherComp implements AfterContentInit{
//the socket.io connection
public weather:any;
//the temperature stream as Observable
public temperature:Observable<number>;
//#Input() isn't set yet
constructor(public http: Http) {
const BASE_URL = 'ws://'+location.hostname+':'+location.port;
this.weather = io(BASE_URL+'/weather');
//log any messages from the message event of socket.io
this.weather.on('message', (data:any) =>{
console.log(data);
});
}
//#Input() is set now!
ngAfterContentInit():void {
//add Observable
this.temperature = Observable.fromEvent(this.weather, this.city);
}
}

Categories

Resources