Instructor: 0:00 Let's take a look at the following functionality. We want to be able to end-to-end test here our form and want to be able to grab the various form fields easily from within our end-to-end tracing framework.
0:10 Let's say we use Cypress. Cypress is a new emerging tool for doing some new end-to-end testing. In Cypress, in order to be able to easily grab a field on your form, what I suggest is to use so-called data attributes.
0:24 In Formly, we can add such various attributes by using here the attributes property. Inside here, we can just specify the attribute you want to add.
0:32 For instance, with Cypress, there is a standard or naming convention which tells you add a data-cy property, because that will be picked up by Cypress easily.
0:42 What I usually do is then I use the...basically is a property, the key here of my Formly field as a data-cy attribute on my Formly field.
0:53 That allows us then to easily grab that field via CSS selector because if you go to the inspection of our HTML part, you can see that data-cy property with first name. Within my cypress test, I know that there will be a data-cy property and I can just grab that via the actual data bound property.
1:12 This is some kind of cross-cutting functionality because I want to add this by default on all of my Formly field configuration. I can obviously go here and copy and paste them all over, but that would mess up my code and make it more unreadable and cluttered.
1:27 With Formly, we can implement something that's called extensions. Extensions are exactly for such cross-cutting functionalities. Again, I'm just creating a new file at the very root of my project, but obviously you would create a different directory.
1:40 Let's call that data-cy.extension. From the very first beginning here, it's just a normal function. I can use here const data-cy.extension. That returns a Formly extension. We can already import that Formly extension here. That will be imported from @ngx-formly/core.
2:02 What that Formly extension gives us, it gives us a prePopulate, onPopulate and postPopulate hook. All of these get the Formly field configuration as a parameter, and so we can manipulate that accordingly.
2:15 Let's, in our case, implement here the prePopulate, because that's one of the earliest one. I got the field. This is the Formly field configuration. With that, we get each field configuration of our form while we pass through this method.
2:31 We can easily create that data-cy attribute. Here, we could simply say field.templateOptions equals to...We want definitely to make sure that we don't lose our template options that are already specified or basically, just create a new one. Then, we add our attributes and data-cy property. Here, we can go to the field.key as actual property.
2:59 A more advanced scenario is we want probably also to make sure that if someone already specified this part, we don't go and overwrite it. Maybe someone wants to specify some particular key, which we don't want to overwrite here. For the simplicity of this example, let's just leave it as it is.
3:13 We also need to register that of course with Formly, in order that Formly knows that it has to execute this as an extension. We go here to our Formly configuration. Similarly, as we have validators, and validation messages, and types, we also have another one which is called extensions.
3:32 We can just go here, extensions. This is an array again. It takes a name. Let's call it data-cy.extension. That takes an extension itself. That's our extension which we just created. This is the data-cy.extension. Obviously, we need to import it as well. Let's import that, data-cy.extension. Let me go back to our app component. Let me remove our manually added extension again.
4:03 If we go here, let's go to the Age property, do inspect. We can see here how the data-cy attribute has been added. It's also been added here, for instance, on our firstName property and all the other properties we have on our form.
4:19 This is where you can implement such a cross-cutting functionality with a very simple function that you deploy centrally at one point. You register it there and you can basically create very powerful mechanisms.