Is it good practice to reference services in html templates in Angular 2? - templates

As the question states, is there any downside in referencing the service directly in the template as such :
[disabled]="stateService.selectedClient == null || stateService.currentStep == 1"
In my opinion this doesn't seem like good practice and I'd much rather keep a "selectedClient" object in whatever component needs to use it. How can I get the state and store it into local variables, while observing the changes:
example: I want to move from step1 to step2 by changing "currentStep" in the "stateService", however I want the component that keeps "currentStep" ALSO as a local variable to reflect the change in the state?

Is it good practice to reference services in html templates in Angular
2?
I'd generally avoid it. It seems to bring more chaos than good.
Cons:
Coming from OOP background, this approach looks like it breaks the Law of Demeter, but more importantly,
It's no longer MVC, where your controller (Angular2's Component) acts like a mediator between the view and the services.
Like Ced said, what if a call to a service's member is costly and we need to refer to it multiple times in the view?
At the moment my editor of choice (VS Code) does not fully support Angular2 templates; referencing too many things outside of its own Component's scope in a template makes refactoring not fun anymore.
Pros:
Sometimes it looks more elegant (because it saves you 2 lines of code), but trust me, it's not.
How can I get the state and store it into local variables, while
observing the changes
Madhu Ranjan has a good answer to this. I'll just try to make it more complete here for your particular example:
In your StateService, define:
currentStep : Subject<number> = new Subject<number>();
selectedClient: Subject<Client> = new Subject<Client>();
changeStep(nextStep: number){
this.currentStep.next(nextStep);
}
selectClient(client: Client) {
this.selectedClient.next(client);
}
In your Component:
currentStep: number;
constructor(stateService : StateService){
stateService.currentStep.combineLatest(
stateService.selectedClient,
(currStep, client) => {
if (client == null) {
// I'm assuming you are not showing any step here, replace it with your logic
return -1;
}
return currStep;
})
.subscribe(val => {
this.currentStep = val;
});
}

You may try below,
stateService
currentStep : Subject<number> = new Subject<number>();
somestepChangeMethod(){
this.currentStep.next(<set step here to depending on your logic>);
}
component
// use this in template
currentStep: number;
constructor(stateService : stateServiceClass){
stateService.currentStep.subscribe(val => {
this.currentStep = val;
});
}
Hope this helps!!

It is probably not a good idea to expose your subject inside of your state service. Something like this would be better.
StateService
private currentStep: Subject<number> = new Subject<number>();
changeStep(value: number) {
this.currentStep.next(value);
}
get theCurrentStep(): Observable<number> {
this.currentStep.asObservable();
}
Component
currentStep: number;
constructor(private stateService: StateService) {
this.currentStep = this.stateService.theCurrentStep;
}
Template
[disabled]="(currentStep | async) == 1" // Not sure if this part would work

Related

vue testing vuetify input for disabled

I am very new to testing and I'm struggling my way through all this new stuff I am learning. Today I want to write a test for a vuetify <v-text-field> component like this:
<v-text-field
v-model="user.caption"
label="Name"
:disabled="!checkPermissionFor('users.write')"
required
/>
my test should handle the following case:
an active, logged in user has a array in vuex store which has his permissions as a array of strings. exactly like this
userRights: ['dashboard', 'imprint', 'dataPrivacy']
the checkPermissionFor() function is doing nothing else then checking the array above with a arr.includes('x')
after it came out the right is not included it gives me a negotiated return which handles the disabled state on that input field.
I want to test this exact scenario.
my test at the moment looks like this:
it('user has no rights to edit other user overview data', () => {
const store = new Vuex.Store({
state: {
ActiveUser: {
userData: {
isLoggedIn: true,
isAdmin: false,
userRights: ['dashboard', 'imprint', 'dataPrivacy']
}
}
}
})
const wrapper = shallowMount(Overview, {
store,
localVue
})
const addUserPermission = wrapper.vm.checkPermissionFor('users.write')
const inputName = wrapper.find(
'HOW TO SELECT A INPUT LIKE THIS? DO I HAVE TO ADD A CLASS FOR IT?'
)
expect(addUserPermission).toBe(false)
expect(inputName.props('disabled')).toBe(false)
})
big questions now:
how can I select a input from vuetify which has no class like in my case
how can I test for "is the input disabled?"
wrapper.find method accepts a query string. You can pass a query string like this :
input[label='Name'] or if you know the exact index you can use this CSS query too : input:nth-of-type(2).
Then find method will return you another wrapper. Wrapper has a property named element which returns the underlying native element.
So you can check if input disabled like this :
const buttonWrapper = wrapper.find("input[label='Name']");
const isDisabled = buttonWrapper.element.disabled === true;
expect(isDisabled ).toBe(true)
For question 1 it's a good idea to put extra datasets into your component template that are used just for testing so you can extract that element - the most common convention is data-testid="test-id".
The reason you should do this instead of relying on the classes and ids and positional selectors or anything like that is because those selectors are likely to change in a way that shouldn't break your test - if in the future you change css frameworks or change an id for some reason, your tests will break even though your component is still working.
If you're (understandably) worried about polluting your markup with all these data-testid attributes, you can use a webpack plugin like https://github.com/emensch/vue-remove-attributes to strip them out of your dev builds. Here's how I use that with laravel mix:
const createAttributeRemover = require('vue-remove-attributes');
if (mix.inProduction()) {
mix.options({
vue: {
compilerOptions: {
modules: [
createAttributeRemover('data-testid')
]
}
}
})
}
as for your second question I don't know I was googling the same thing and I landed here!

How to Unit Test a Method in Sitecore MVC having tightly coupled Dependency on two distinct Sitecore Contexts?

I have to unfortunately write Unit Tests for a legacy Sitecore MVC code base where two distinct Sitecore Contexts are called. I understand this comes under Integration Testing but i don't have the option of educating my project Leads on that front. So i have chosen to use FakeDb for emulating Sitecore Instance and NSubstitute for substituting injected Dependencies (can't use any Profilier API Frameworks like MS Fakes, TypeMock etc because of Budget constraints). I am providing the code below:
Method to be UnitTested
public bool DubiousMethod()
{
// This HttpContext call is pain area 1. This gets resolved when i call it using ItemContextSwitcher in Unit Tests.
string currentUrl = HttpContext.Current.Request.RawUrl;
// This Sitecore Context call to Site Name is pain area 2. This gets resolved when Unit Tests are run under SiteContextSwitcher.
string siteName = Sitecore.Context.Site.Name;
return true/False;
}
Unit Test Method
[Fact]
public void DubiousMethodUT()
{
// create a fake site context
var fakeSite = new Sitecore.FakeDb.Sites.FakeSiteContext(
new Sitecore.Collections.StringDictionary
{
{ "name", "website" }, { "database", "web" }, { "rootPath", "/sitecore/content/home" },
{ "contentStartItem", "home"}, {"hostName","https://www.myorignalsiteurl.com"}
});
using (new Sitecore.Sites.SiteContextSwitcher(fakeSite))
{
//DubiousClassObject.DubiousMethod(home) // When Debugging after uncommenting this line i get correct value in **Sitecore.Context.Site.Name**
using (Sitecore.FakeDb.Db db = new Sitecore.FakeDb.Db
{
new Sitecore.FakeDb.DbItem("home") { { "Title", "Welcome!" } ,
new Sitecore.FakeDb.DbItem("blogs") }
})
{
Sitecore.Data.Items.Item home = db.GetItem("/sitecore/content/home");
//bool abc = confBlogUT.IsBlogItem(home);
using (new ContextItemSwitcher(home))
{
string siteName = Sitecore.Context.Site.Name;
var urlOptions = new Sitecore.Links.UrlOptions();
urlOptions.AlwaysIncludeServerUrl = true;
var pageUrl = Sitecore.Links.LinkManager.GetItemUrl(Sitecore.Context.Item, urlOptions);
HttpContext.Current = new HttpContext(new HttpRequest("", pageUrl.Substring(3), ""), new HttpResponse(new StringWriter()));
Assert.False(DubiousClassObject.DubiousMethod(home); //When Debugging after commenting above DubiousMethodCall i get correct value for **HttpContext.Current.Request.RawUrl**
}
}
}
}
As you can observe that when i try to call the method from FakSiteContext then i am getting the correct value for Sitecore.Context.Site.Name however my code breaks when HttpContext.Current.Request.RawUrl is invoked in the method. Opposite happens when i invoke the method from ContextItemSwitcher(FakeItem) context. So far i have not been able to find a way to merge both the Contexts (which i believe is impossible in Sitecore). Can anyone suggest if i run my Unit Tests in an overarching context where i am able to contrl fakeSite Variables as well as FakeItem context variables as well and by extensions any other Sitecore Context calls?
Any help would be appreciated.
I'd recommend to take a look at Unit testing in Sitecore article as it seem to be what you need.
In short - you'll need to do a few adjustments in your code to make it testable:
1) Replace static HttpContext with abstract HttpContextBase (impl. HttpContextWrapper) so that everything can be arranged - DubiousMethod gets an overload that accepts DubiousMethod(HttpContextBase httpContext).
2) As for Sitecore Context data - it has Sitecore.Caching.ItemsContext-bound semantics (as mentioned in the article), so you could cleanup the collection before/after each test to get a sort of isolation between tests.
Alternatively you could bake a similar wrapper for Sitecore.Context as ASP.NET team had done for HttpContext -> HttpContextBase & impl HttpContextWrapper.

Ember's volatile and templates

I have a property mood which is part of the interface for a component. Behind the scenes I have a computed property called _mood:
const { computed, typeOf } = Ember;
_mood: computed('mood','A','B','C' function() {
let mood = this.get('mood');
if (typeOf(mood) === 'function') {
mood = mood(this);
}
return !mood || mood === 'default' ? '' : `mood-${mood}`;
}).volatile(),
I have a situation where with the volatile() that hangs of of Ember's computed object resolves all non DOM unit tests successfully but for some reason it is not triggering the template to update the DOM under any circumstance. It should, at the very least, update if any of properties being watched change (in this case ['mood','A', 'B', 'C']) change. Because it is volatile (aka, doesn't cache results) the new results would show up in a template if the template knew to re-render the component but for some reason it doesn't.
If I remove the volatile() tag it works fine for static/scalar values but if mood is a function then the result is cached and therefore it only works once (not a working solution for this problem).
How do I get the best of both worlds?
I'm still not sure why the volatile() method is turning off updates to templates. This might be a real bug but in terms of solving my problem the important thing to recognise was that the volatile approach was never the best approach.
Instead the important thing to ensure is that when mood comes in as a function that the function's dependencies are included in the CP's dependencies. So, for instance, if mood is passed the following function:
mood: function(context) {
return context.title === 'Monkeys' ? 'happy' : 'sad';
}
For this function to evaluate effectively -- and more importantly to trigger a re-evaluation at the right time -- the title property must be part of the computed property. Hopefully that's straight forward as to why but here's how I thought I might accommodated this:
_moodDependencies: ['title','subHeading','style'],
_mood: computed('mood','size','_moodDependencies', function() {
let mood = this.get('mood');
console.log('mood is: %o', mood);
if (typeOf(mood) === 'function') {
run( ()=> {
mood = mood(this);
});
}
return !mood || mood === 'default' ? '' : `mood-${mood}`;
}),
That's better, as it allows at build time for a static set of properties to be defined per component (the _mood CP for me is part of a mixin used by a few components). Unfortunately this doesn't yet work completely as the it apparently doesn't unpack/destructure the _moodDependencies.
I'll get this sorted and update this unless someone else beats me to the punch.

Can Glass.Mapper V3 support language fallback (field-level and item-level)?

We just updated our project to use Glass.Mapper V3. We LOVE it. But we've encountered an issue. It doesn't seem to respect language fallback.
We have our site set up so that if a user picks a non-default language, they will see the item of that language if it exists. If not, they will see the default ("fallback") language version. We have also set this up at the field level, so that if there is a non-default version of an item but not all the fields are changed, any unchanged fields will fall back to the default language version's value for that field.
Is there anything we can do to enable Glass to use language fallback?
I am updating this with a bit of background on why we do the check. If you ask for a Sitecore item that doesn't exist you get a null value, so that is simple to handle. However if you ask for a Sitecore item that doesn't exist in that particular language returns an item with no versions. This means we have to do this check because otherwise Glass would end up returning empty class which I don't think makes much sense.
This answer will get a little experimental.
First in the the Spherical.cs file you need to disable the check:
protected void Application_BeginRequest()
{
Sitecore.Context.Items["Disable"] = new VersionCountDisabler();
}
We can then move the check to later on to the Object Construction pipeline. First create a task:
public class FallbackCheckTask : IObjectConstructionTask
{
public void Execute(ObjectConstructionArgs args)
{
if (args.Result == null)
{
var scContext = args.AbstractTypeCreationContext as SitecoreTypeCreationContext;
if (scContext.Item == null)
{
args.AbortPipeline();
return;
}
//this checks to see if the item was created by the fallback module
if (scContext.Item is Sitecore.Data.Managers.StubItem)
{
return;
}
// we could be trying to convert rendering parameters to a glass model, and if so, just return.
if (String.Compare(scContext.Item.Paths.FullPath, "[orphan]/renderingParameters", true) == 0)
{
return;
}
if (scContext.Item.Versions.Count == 0)
{
args.AbortPipeline();
return;
}
}
}
}
Then finally register this task in the GlassMapperScCustom class:
public static void CastleConfig(IWindsorContainer container){
var config = new Config();
container.Register(
Component.For<IObjectConstructionTask>().ImplementedBy<FallbackCheckTask>().LifestyleTransient()
);
container.Install(new SitecoreInstaller(config));
}
I haven't tested this but it should in theory work <- disclaimer ;-)
There are few potential issues with provided solution when sitecore 7 (7.2) + IoC + solr + mvc is used.
When using IoC ex Winsdor please make sure that your Global.asax looks like this one <%# Application Codebehind="Global.asax.cs" Inherits="Sitecore.ContentSearch.SolrProvider.CastleWindsorIntegration.WindsorApplication" Language="C#" %>. Once, by mistake this file has been changed to <%# Application Codebehind="Global.asax.cs" Inherits="Merck.Manuals.Web.Global" Language="C#" %> and language fallback was not working. Also the errors we were getting wasn't descriptive as we thought that solr schema is incorrect.
Code Sitecore.Context.Items["Disable"] = new VersionCountDisabler(); can be added to PreprocessRequestProcessor and it works fine which is better solution that modifying global.asax.

Symfony 2 Controller as a Service: container not injected

I want to be able to inject services into my controllers, so I had a look at http://symfony.com/doc/current/cookbook/controller/service.html and after some fiddling with the notation (could be a little more consistent but whatever) I have my WebTestCase using the service definition entry.
But the controller needs the container itself injected (and does indeed extend ContainerAware via the default framework controller), and the ControllerResolver in the FrameworkBundle does not do that.
Looking at the code (Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver::createController()) this is not suprising:
protected function createController($controller)
{
if (false === strpos($controller, '::')) {
$count = substr_count($controller, ':');
if (2 == $count) {
// controller in the a:b:c notation then
$controller = $this->parser->parse($controller);
} elseif (1 == $count) {
// controller in the service:method notation
list($service, $method) = explode(':', $controller, 2);
return array($this->container->get($service), $method);
} else {
throw new \LogicException(sprintf('Unable to parse the controller name "%s".', $controller));
}
}
list($class, $method) = explode('::', $controller, 2);
if (!class_exists($class)) {
throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class));
}
$controller = new $class();
if ($controller instanceof ContainerAwareInterface) {
$controller->setContainer($this->container);
}
return array($controller, $method);
}
Apparently when using service:method notation,, it directly returns the controller from the container, not injecting the container itself.
Is this is a bug or am I missing something?
This is not a bug. It works as expected. This workflow generally "protects" the Controller as a Service concept. This way You need to look at Controller as a regular Service. In regular Service You inject everything what You need - if You need the controller itself - inject it explicitly.
To explain it more clearly, this "protection" I mentioned helps to avoid using service:method notation in one place and controller::method or bundle:controller:method in another.
So if not this "protection" it would be difficult to state that particular Controller is described as a Service or not as this would depend on which notation will be called first in Container build-up.