5

I am looking for some best practice here. My angular2 app will live inside an existing Content Managment System. As a result I need to capture some "variables" generated by this CMS (like auth tokens etc) and use them with http requests inside my angular2 app.

When the index.html page is displayed by the CMS it is pre-parsed by the CMS and some tokens (ie [ModuleContext:ModuleId]) are replaced before the page is sent to the browser.

Here is an example of my index.html page (abreviated):

<!-- 2. Capture CMS values to pass to app -->
<script type="text/javascript">
    var moduleId = parseInt("[ModuleContext:ModuleId]");
    var portalId = parseInt("[ModuleContext:PortalId]");
    var sf = $.ServicesFramework(moduleId);
</script>

<!-- 3. Configure SystemJS and Bootstrap App-->
<script type="text/javascript">
    System.config({
        packages: {
            //sets the root path of the Angular2 App
            'DesktopModules/KrisisShifts/app': {
                format: 'register',
                defaultExtension: 'js'
            }
        },
        map: { 'app': './app' }
    });
    System.import('app/boot')
            .then(null, console.error.bind(console));
</script>
<shift-app>Loading...</shift-app>

Specifically the $.ServicesFramework is used for generating valid http web.api requests. I want to capture this in a service that can be injected into each component that uses it.

for example (I am using typescript):

import {Injectable} from 'angular2/core';
import {OnInit} from 'angular2/core';

@Injectable()
export class dnnService implements OnInit{

    sf: any;

    constructor() {}

    ngOnInit() {
        if ($.ServicesFramework) {
            this.sf = $.ServicesFramework(moduleId);
        };
    }

}

One problem is that the typescript compiler throws error that it cannot find "$" etc. I can force this to work by using declare before the typescript class declaration like the following:

//Global Variable Declarations
declare var $: any;
declare var moduleId: any;

Question:

What is a better way (if exists) of capturing these "global" variable for use in the app that will scale well.


EDIT - update to RC6

I used the following to work in the RC6:

@NgModule({
declarations: [
    AppComponent,
    FormatDatePipe,
    ShiftPartialPipe 
],
imports: [
    BrowserModule,
    RouterModule.forRoot(AppRoutes),
    FormsModule,
    ReactiveFormsModule,
    HttpModule 
],
bootstrap: [AppComponent],
providers: [
    { provide: LocationStrategy, useClass: HashLocationStrategy },
    { provide: dnnModId, useValue: moduleId },
    { provide: dnnPortalId, useValue: portalId },
    { provide: dnnEditMode, useValue: editMode },
    { provide: dnnSF, useValue: $.ServicesFramework(moduleId) }
]
})
J King
  • 4,108
  • 10
  • 53
  • 103
  • Is you question about `declare var ...` or the `parseInt("[ModuleContext:ModuleId]")`? – SnareChops Feb 05 '16 at 02:44
  • its about best practice. What I show is working, but I am wondering if others have a better way of accomplishing hte same thing, mainly getting global or page level javascript variable into a angular2 service (that way it can be tested) – J King Feb 05 '16 at 02:45
  • @JKing have you found a better method? I'm trying to define a standard using the new cli (with Webpack), but I'm still fighting...it's also for the same use case: DNN, http headers, etc. and ideally automatic module-id detection – iJungleBoy May 15 '17 at 19:02
  • @iJungleBoy I did. I had to abandon the @ inject way of doing it. I'm not by my computer today. I I will post tomorrow how it works. – J King May 15 '17 at 20:43
  • @JKing How you rendering service variable in your client `var moduleId = parseInt("[ModuleContext:ModuleId]");` like this. I'm wondering from yesterday how to make it. I stored my variable in a view, wanna use this variable in client side. – k11k2 Jul 21 '17 at 05:30

3 Answers3

8

Update >= RC.6

In RC.6 with the introduction of @NgModule() providers are added there instead of in boostrap(...). Alsoprovide()` was deprecated and removed in favor of object literal syntax:

In a shared library define

import {OpaqueToken} from '@angular/core';

export let SF = new OpaqueToken('sf');
@NgModule({
  providers: [{provide: SF, useValue: $.ServicesFramework(moduleId)},
  directives: [...]
  ...
})
class SomeModule {}

Providers can also be added to components and directives

@Component({
   providers: [
    {provide: SF, useValue: $.ServicesFramework(moduleId)},
   ]);
})
class SomeComponent {}

Inject it to components, directives, pipes, or services like

constructor(@Inject(SF) private sf:string) {}

original

In a shared library define

import {OpaqueToken} from '@angular/core';

export let SF = new OpaqueToken('sf');

In bootstrap() add

// import SF from shared library

bootstrap(AppComponent, [
    // other providers
    provide(SF, {useValue: $.ServicesFramework(moduleId)}),
    ]);

Where you want to use it

// import SF from shared library

 constructor(@Inject(SF) private _sf: string){ }

This utilizes Angulars DI and avoids hardcoded dependencies which makes the code hard to test.

See also

Hint: Instead of OpaqueToken a plain string can be used as well. Using OpaqueToken prevents name collisions for example if this is used in an open source package that is used by many users. If you control the full environment then you can ensure yourself that no collisions will happen and should be safe to use a string instead of OpaqueToken.

update

InjectionToken with generics support was introduced to replace OpaqueToken which is now deprecated.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • @Günter Zöchbauer Plain string means ? could you provide some explanation. – k11k2 Jul 21 '17 at 05:05
  • @GünterZöchbauer got it, I'm storing my token in View(asp.net core) as `var message = "@ViewBag.Token"` how could I use this message value in my client side ? – k11k2 Jul 21 '17 at 06:17
  • Sorry, I don't understand your question or how "this message" would be related to dependency injection. I'd suggest to create a new question with enough code that demonstrates what you try to accomplish, what you tried, and where you failed. – Günter Zöchbauer Jul 21 '17 at 06:31
  • @GünterZöchbauer https://stackoverflow.com/questions/44818526/setting-hidden-text-from-server-node-jsexpress-asp-net-core-to-read-it-from-cl – k11k2 Jul 21 '17 at 06:52
2

To expand Gunther's answer a bit, I have done this in RC5 as well, except instead of adding it to providers in bootstrap() (from previous version), you place the token as a provider in the new @ngModule decorator that is doing the bootstrapping. For example:

@NgModule({
    bootstrap: [MyComponent],
    declarations: [MyComponent],
    imports: [BrowserModule],
    providers: [
        { provide: OpaqueToken, useValue: someObject }
    ]
})
export class AppModule { }

browserDynamicPlatform().bootstrapModule(AppModule);
Paul Jerome Bordallo
  • 1,362
  • 10
  • 11
  • thanks Jerome - great clarification for the new rc5 - which I will assume will remain this way to the final release – J King Aug 18 '16 at 19:27
  • 1
    Actually this provide() was deprecated and removed in rc6. The correct way is really: ` providers: [ { provide: yOpaqueToken, useValue: someObject } ] ` – Paul Jerome Bordallo Sep 08 '16 at 06:57
0

Not sure if that is the best practice but I've faced the same problem and the solution that I'm using (RC5 ready) is the following:

Step 1) Create your settings class as @Injectable:

   import { Injectable }                                   from '@angular/core';

   @Injectable()

   export class MyAppSharedSettings {

        appName: string = "My Application Name";
        appTitle: string = "My Application Title";
        appVersion: string = "1.0.0.0 beta";
        welcomeMessage: string = "Welcome";
        userName: string = "";
   }

Step 2) On your main module, import the following if you are not already doing it:

 //Imports required for NgModule directive
 import { NgModule }                          from '@angular/core';
 import { BrowserModule }                     from '@angular/platform-browser';
 import { HttpModule }                        from '@angular/http';

 //Imports required for your code to run
 import { MyAppMainComponent }               from './myapp.component';
 import { MyAppSharedSettings }              from './myapp.shared-settings';

Step 3) So you have marked your class as injectable (step one) and also made it available (providers) for any component that want to use it (step 2). Now, the next step is just put it in the constructor of your component.

 //Imports: Angular & Packages Related
 import { Component, Inject }            from '@angular/core';

 //You must import your injectable class in every component you plan to use it
 import { MyAppSharedSettings }          from '../../myapp.shared-settings';

 //defining how our component will be presented/used
 @Component({
     selector: 'my-content',
     templateUrl: './app/components/content/content.component.html',
     styleUrls: ['./app/components/content/content.component.css']
 })

 //here you name your variable as you please
 export class MyContent {
     yourVariableName: MyAppSharedSettings;

     constructor(private eafSettings: MyAppSharedSettings) {
         this.yourVariableName = eafSettings;
  }
 }

Final Step) And here how we can use it in our HTML:

 <h3 class="page-title">
     {{ yourVariableName.welcomeMessage }}<small> {{ yourVariableName.userName}}</small>
    </h3>
    <div class="row about-header">
        <div class="col-md-12">
            <h1>{{ yourVariableName.appName}}</h1>
            <h2>{{ yourVariableName.appTitle }}</h2>
            <a href="#platform"><button type="button" class="btn btn-danger uppercase">Start</button></a>
        </div>
    </div>
 </div>

Hope it helps.

Daniel Santana
  • 1,493
  • 20
  • 19