Dapp uses a smart contract that receives ERC20 tokens. At the front end, I want to allow users to approve a smart contract to use their funds. Do I have to have an ABI and an address for each token and create contracts instances or can I somehow call via the IERC20 standard e.g. IERC20(tokenAddress).approve(..)?
ABI and an interface are pretty much the same thing, in different format.
You didn't specify which web3 library you use, but in any case the basic ABI is the same for all ERC20 contracts. It doesn't matter if the ABI doesn't have some of the extra functionality, it's enough if it contains the functions you want to use. So you can either get the ABI from any ERC20 contract or even write the few (one?) function you want manually into an ABI.
In any case, I guess you need to use an ABI, so you need to get it in some fashion. In theory you could just use an interface, but I'm not sure if some library allows you to use a Solidity interface instead of an ABI.